3.2 से अधिक एक आधुनिक MongoDB के साथ आप ज्यादातर मामलों में $lookup
वैकल्पिक के रूप में उपयोग कर सकते हैं .populate()
। यह भी वास्तव में "सर्वर पर" के रूप में शामिल होने का विरोध किया गया .populate()
है, जो कि वास्तव में "एकाधिक प्रश्न" में शामिल होने के लिए "अनुकरण" करने का विरोध करता है ।
तो .populate()
है नहीं वास्तव में "शामिल हों" कैसे एक संबंधपरक डेटाबेस यह करता है के तौर पर। $lookup
दूसरी ओर ऑपरेटर, वास्तव में सर्वर पर काम करता है, और अधिक या कम एक के अनुरूप है "वाम शामिल हों" :
Item.aggregate(
[
{ "$lookup": {
"from": ItemTags.collection.name,
"localField": "tags",
"foreignField": "_id",
"as": "tags"
}},
{ "$unwind": "$tags" },
{ "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
{ "$group": {
"_id": "$_id",
"dateCreated": { "$first": "$dateCreated" },
"title": { "$first": "$title" },
"description": { "$first": "$description" },
"tags": { "$push": "$tags" }
}}
],
function(err, result) {
}
)
नायब.collection.name
यहाँ वास्तव में "स्ट्रिंग" के रूप में मॉडल करने के लिए सौंपा MongoDB संग्रह का वास्तविक नाम है कि करने के लिए मूल्यांकन करता है। चूंकि mongoose डिफ़ॉल्ट रूप से "संग्रह नाम" का बहुवचन करता है और $lookup
उसे एक तर्क के रूप में वास्तविक MongoDB संग्रह नाम की आवश्यकता होती है (क्योंकि यह एक सर्वर ऑपरेशन है), तो यह mongoose कोड में उपयोग करने के लिए एक आसान ट्रिक है, जैसा कि सीधे "हार्ड कोडिंग" संग्रह नाम के विपरीत है। ।
वैसे तो हम भी इस्तेमाल कर सकते हैं $filter
सरणियों पर अवांछित आइटम हटाने के लिए, यह वास्तव में की वजह से सबसे कारगर तरीका है एकत्रीकरण पाइपलाइन अनुकूलन के रूप में की विशेष स्थिति के लिए $lookup
दोनों एक के बाद $unwind
और एक$match
शर्त के बाद।
यह वास्तव में तीन पाइपलाइन चरणों को एक में लुढ़का देता है:
{ "$lookup" : {
"from" : "itemtags",
"as" : "tags",
"localField" : "tags",
"foreignField" : "_id",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"tagName" : {
"$in" : [
"funny",
"politics"
]
}
}
}}
यह अत्यधिक इष्टतम है क्योंकि वास्तविक ऑपरेशन "पहले शामिल होने के लिए संग्रह को फ़िल्टर करता है", फिर यह परिणाम लौटाता है और सरणी को "अनइंड्स" करता है। दोनों विधियों को नियोजित किया जाता है, इसलिए परिणाम 16MB की BSON सीमा को नहीं तोड़ते हैं, जो एक बाधा है जो क्लाइंट के पास नहीं है।
एकमात्र समस्या यह है कि यह कुछ तरीकों से "काउंटर-सहज" लगता है, खासकर जब आप किसी सरणी में परिणाम चाहते हैं, लेकिन यह वही है जो $group
यहां के लिए है, क्योंकि यह मूल दस्तावेज़ फ़ॉर्म में पुनर्निर्माण करता है।
यह भी दुर्भाग्यपूर्ण है कि हम इस समय वास्तव $lookup
में उसी क्रमिक सिंटैक्स में नहीं लिख सकते जो सर्वर उपयोग करता है। IMHO, यह सही होने के लिए एक निरीक्षण है। लेकिन अभी के लिए, केवल अनुक्रम का उपयोग करना काम करेगा और सबसे अच्छा प्रदर्शन और मापनीयता के साथ सबसे व्यवहार्य विकल्प है।
परिशिष्ट - MongoDB 3.6 और ऊपर की ओर
यद्यपि यहां दिखाया गया पैटर्न अन्य चरणों में लुढ़का हुआ होने के कारण काफी अनुकूलित है $lookup
, लेकिन इसमें कोई भी ऐसा नहीं है जो "LEFT JOIN" है जो आम तौर पर दोनों के लिए अंतर्निहित है $lookup
और क्रियाओं populate()
को "इष्टतम" उपयोग द्वारा नकार दिया जाता है $unwind
यहाँ जो खाली सरणियों को संरक्षित नहीं करता है। आप preserveNullAndEmptyArrays
विकल्प जोड़ सकते हैं , लेकिन यह ऊपर वर्णित "अनुकूलित" अनुक्रम को नकार देता है और अनिवार्य रूप से सभी तीन चरणों को बरकरार रखता है जो सामान्य रूप से अनुकूलन में जोड़ा जाएगा।
MongoDB 3.6 "उप-पाइपलाइन" अभिव्यक्ति की अनुमति देने के "अधिक अभिव्यंजक" रूप के साथ फैलता है $lookup
। जो न केवल "LEFT JOIN" को बरकरार रखने के लक्ष्य को पूरा करता है, बल्कि फिर भी लौटे हुए परिणामों को कम करने और एक बहुत ही सरल वाक्य रचना के साथ एक इष्टतम क्वेरी की अनुमति देता है:
Item.aggregate([
{ "$lookup": {
"from": ItemTags.collection.name,
"let": { "tags": "$tags" },
"pipeline": [
{ "$match": {
"tags": { "$in": [ "politics", "funny" ] },
"$expr": { "$in": [ "$_id", "$$tags" ] }
}}
]
}}
])
$expr
आदेश से मिलान करने में प्रयोग किया जाता घोषित "विदेशी" मूल्य के साथ "स्थानीय" मूल्य वास्तव में क्या MongoDB करता है "आंतरिक" अब मूल के साथ है $lookup
वाक्य रचना। इस रूप में व्यक्त करके हम प्रारंभिक $match
अभिव्यक्ति "उप-पाइपलाइन" के भीतर खुद को दर्जी कर सकते हैं।
वास्तव में, एक सच्चे "एकत्रीकरण पाइपलाइन" के रूप में आप इस "उप-पाइपलाइन" अभिव्यक्ति के भीतर एकत्रीकरण पाइपलाइन के साथ कुछ भी कर सकते हैं, जिसमें "घोंसले के शिकार" शामिल हैं $lookup
संबंधित अन्य संबंधित संग्रह ।
आगे का उपयोग इस सवाल के दायरे से थोड़ा सा अधिक है, लेकिन यहां तक कि "नेस्टेड आबादी" के संबंध में भी, तो नया उपयोग पैटर्न $lookup
इसे बहुत समान करने की अनुमति देता है, और इसमें "बहुत" पूर्ण उपयोग में अधिक शक्तिशाली है।
काम करने का उदाहरण
निम्नलिखित मॉडल पर एक स्थिर विधि का उपयोग करके एक उदाहरण देता है। एक बार उस स्थैतिक विधि को लागू करने के बाद कॉल सरल हो जाती है:
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
या थोड़ा और अधिक आधुनिक बनने के लिए बढ़ाना:
let results = await Item.lookup({
path: 'tags',
query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }
})
के समान बनाना .populate()
संरचना में , लेकिन यह वास्तव में इसके बजाय सर्वर पर शामिल हो रहा है। पूर्णता के लिए, यहाँ उपयोग माता-पिता और बच्चे दोनों के मामलों के अनुसार दस्तावेज़ों के उदाहरणों के लिए डेटा वापस लौटाता है।
यह काफी तुच्छ और आसान है या ज्यादातर सामान्य मामलों के लिए उपयोग करने के लिए आसान है।
नायब यहाँ async का उपयोग केवल संलग्न उदाहरण को चलाने की संक्षिप्तता के लिए है। वास्तविक कार्यान्वयन इस निर्भरता से मुक्त है।
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/looktest');
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
dateCreated: { type: Date, default: Date.now },
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});
itemSchema.statics.lookup = function(opt,callback) {
let rel =
mongoose.model(this.schema.path(opt.path).caster.options.ref);
let group = { "$group": { } };
this.schema.eachPath(p =>
group.$group[p] = (p === "_id") ? "$_id" :
(p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": opt.path,
"localField": opt.path,
"foreignField": "_id"
}},
{ "$unwind": `$${opt.path}` },
{ "$match": opt.query },
group
];
this.aggregate(pipeline,(err,result) => {
if (err) callback(err);
result = result.map(m => {
m[opt.path] = m[opt.path].map(r => rel(r));
return this(m);
});
callback(err,result);
});
}
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
function log(body) {
console.log(JSON.stringify(body, undefined, 2))
}
async.series(
[
(callback) => async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
(callback) =>
async.waterfall(
[
(callback) =>
ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
callback),
(tags, callback) =>
Item.create({ "title": "Something","description": "An item",
"tags": tags },callback)
],
callback
),
(callback) =>
Item.lookup(
{
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
},
callback
)
],
(err,results) => {
if (err) throw err;
let result = results.pop();
log(result);
mongoose.disconnect();
}
)
या नोड 8.x और उससे अधिक के लिए थोड़ा आधुनिक async/await
और कोई अतिरिक्त निर्भरता नहीं:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/looktest';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
dateCreated: { type: Date, default: Date.now },
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});
itemSchema.statics.lookup = function(opt) {
let rel =
mongoose.model(this.schema.path(opt.path).caster.options.ref);
let group = { "$group": { } };
this.schema.eachPath(p =>
group.$group[p] = (p === "_id") ? "$_id" :
(p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": opt.path,
"localField": opt.path,
"foreignField": "_id"
}},
{ "$unwind": `$${opt.path}` },
{ "$match": opt.query },
group
];
return this.aggregate(pipeline).exec().then(r => r.map(m =>
this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
));
}
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
const log = body => console.log(JSON.stringify(body, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
const tags = await ItemTag.create(
["movies", "funny"].map(tagName =>({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
const result = (await Item.lookup({
path: 'tags',
query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
})).pop();
log(result);
mongoose.disconnect();
} catch (e) {
console.error(e);
} finally {
process.exit()
}
})()
और MongoDB 3.6 और ऊपर से, बिना $unwind
और $group
भवन के भी:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/looktest';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const itemTagSchema = new Schema({
tagName: String
});
const itemSchema = new Schema({
title: String,
description: String,
tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
},{ timestamps: true });
itemSchema.statics.lookup = function({ path, query }) {
let rel =
mongoose.model(this.schema.path(path).caster.options.ref);
let pipeline = [
{ "$lookup": {
"from": rel.collection.name,
"as": path,
"let": { [path]: `$${path}` },
"pipeline": [
{ "$match": {
...query,
"$expr": { "$in": [ "$_id", `$$${path}` ] }
}}
]
}}
];
return this.aggregate(pipeline).exec().then(r => r.map(m =>
this({ ...m, [path]: m[path].map(r => rel(r)) })
));
};
const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);
const log = body => console.log(JSON.stringify(body, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
const tags = await ItemTag.insertMany(
["movies", "funny"].map(tagName => ({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
let result = (await Item.lookup({
path: 'tags',
query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
})).pop();
log(result);
await mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()