3.2 से अधिक आधुनिक MongoDB के साथ आप $lookup
. का उपयोग कर सकते हैं .populate()
. के विकल्प के रूप में अधिकतर परिस्थितियों में। इसका वास्तव में .populate()
के विपरीत "सर्वर पर" शामिल होने का लाभ भी है। जो वास्तव में "एकाधिक प्रश्न" का "अनुकरण" करने के लिए है एक शामिल हों।
तो .populate()
है नहीं वास्तव में एक "जुड़ें" इस अर्थ में कि एक रिलेशनल डेटाबेस कैसे करता है। $lookup
दूसरी ओर ऑपरेटर, वास्तव में सर्वर पर काम करता है, और कमोबेश "LEFT JOIN" के समान होता है :
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) {
// "tags" is now filtered by condition and "joined"
}
)
<ब्लॉकक्वॉट>
N.B. .collection.name
यहां वास्तव में "स्ट्रिंग" का मूल्यांकन किया जाता है जो मॉडल को असाइन किए गए मोंगोडीबी संग्रह का वास्तविक नाम है। चूंकि नेवला डिफ़ॉल्ट रूप से संग्रह नामों को "बहुवचन" करता है और $lookup
एक तर्क के रूप में वास्तविक MongoDB संग्रह नाम की आवश्यकता है (चूंकि यह एक सर्वर ऑपरेशन है), तो यह सीधे संग्रह नाम "हार्ड कोडिंग" के विपरीत, नेवला कोड में उपयोग करने के लिए एक आसान चाल है।
जबकि हम $filter
. का भी उपयोग कर सकते हैं अवांछित वस्तुओं को हटाने के लिए सरणियों पर, यह वास्तव में $lookup
की विशेष स्थिति के लिए एकत्रीकरण पाइपलाइन अनुकूलन के कारण सबसे कुशल रूप है दोनों के बाद $unwind
और एक $match
हालत।
इसके परिणामस्वरूप वास्तव में तीन पाइपलाइन चरणों को एक में बदल दिया जाता है:
{ "$lookup" : {
"from" : "itemtags",
"as" : "tags",
"localField" : "tags",
"foreignField" : "_id",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"tagName" : {
"$in" : [
"funny",
"politics"
]
}
}
}}
यह अत्यधिक इष्टतम है क्योंकि वास्तविक ऑपरेशन "पहले शामिल होने के लिए संग्रह को फ़िल्टर करता है", फिर यह परिणाम देता है और सरणी को "अनइंड" करता है। दोनों विधियों को नियोजित किया जाता है ताकि परिणाम 16 एमबी की बीएसओएन सीमा को न तोड़ें, जो एक बाधा है जो क्लाइंट के पास नहीं है।
एकमात्र समस्या यह है कि यह कुछ मायनों में "प्रति-सहज" लगता है, खासकर जब आप किसी सरणी में परिणाम चाहते हैं, लेकिन $group
यही है यहाँ के लिए है, क्योंकि यह मूल दस्तावेज़ के रूप में पुनर्निर्माण करता है।
यह भी दुर्भाग्यपूर्ण है कि हम इस समय वास्तव में $lookup
. नहीं लिख सकते हैं उसी अंतिम सिंटैक्स में सर्वर उपयोग करता है। IMHO, यह ठीक करने के लिए एक निरीक्षण है। लेकिन अभी के लिए, केवल अनुक्रम का उपयोग करना काम करेगा और सर्वोत्तम प्रदर्शन और मापनीयता के साथ सबसे व्यवहार्य विकल्प है।
परिशिष्ट - MongoDB 3.6 और ऊपर की ओर
हालांकि यहां दिखाया गया पैटर्न काफी अनुकूलित है अन्य चरणों को $lookup
. में कैसे शामिल किया जाता है, इसकी वजह से , इसमें "बाएं जॉइन" में एक असफल रहा है जो आम तौर पर $lookup
दोनों के लिए अंतर्निहित है और populate()
. की कार्रवाइयां "इष्टतम" . द्वारा अस्वीकृत है $unwind
. का उपयोग यहाँ जो खाली सरणियों को संरक्षित नहीं करता है। आप preserveNullAndEmptyArrays
. जोड़ सकते हैं विकल्प, लेकिन यह "अनुकूलित" . को नकारता है ऊपर वर्णित अनुक्रम और अनिवार्य रूप से सभी तीन चरणों को बरकरार रखता है जो सामान्य रूप से अनुकूलन में संयुक्त हो जाते हैं।
MongoDB 3.6 एक "अधिक अभिव्यंजक" . के साथ विस्तृत होता है $lookup
. का रूप एक "उप-पाइपलाइन" अभिव्यक्ति की अनुमति देता है। जो न केवल "लेफ्ट जॉइन" को बनाए रखने के लक्ष्य को पूरा करता है बल्कि फिर भी एक इष्टतम क्वेरी को लौटाए गए परिणामों को कम करने और बहुत सरल सिंटैक्स के साथ अनुमति देता है:
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(
[
// Clean data
(callback) => async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Create tags and items
(callback) =>
async.waterfall(
[
(callback) =>
ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
callback),
(tags, callback) =>
Item.create({ "title": "Something","description": "An item",
"tags": tags },callback)
],
callback
),
// Query with our static
(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();
}
)
या async/await
. के साथ Node 8.x और इसके बाद के संस्करण के लिए थोड़ा और आधुनिक और कोई अतिरिक्त निर्भरता नहीं:
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);
// Clean data
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Create tags and items
const tags = await ItemTag.create(
["movies", "funny"].map(tagName =>({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
// Query with our static
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);
// MongoDB 3.6 and up $lookup with sub-pipeline
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);
// Clean data
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Create tags and items
const tags = await ItemTag.insertMany(
["movies", "funny"].map(tagName => ({ tagName }))
);
const item = await Item.create({
"title": "Something",
"description": "An item",
tags
});
// Query with our static
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()
}
})()