तो जब आप एकत्रीकरण परिणाम पर "पॉप्युलेट" करने के लिए कहते हैं तो आप वास्तव में यहां कुछ अवधारणाएं खो रहे हैं। आमतौर पर यह वह नहीं है जो आप वास्तव में करते हैं, बल्कि बिंदुओं को समझाने के लिए करते हैं:
-
एग्रीगेट ()
. का आउटपुटModel.find()
. के विपरीत है या इसी तरह की कार्रवाई के बाद से यहाँ उद्देश्य "परिणामों को फिर से आकार देना" है। इसका मूल रूप से मतलब है कि आप जिस मॉडल को एकत्रीकरण के स्रोत के रूप में उपयोग कर रहे हैं उसे अब आउटपुट पर मॉडल नहीं माना जाता है। यह तब भी सही है जब आपने आउटपुट पर ठीक उसी दस्तावेज़ संरचना को बनाए रखा है, लेकिन आपके मामले में आउटपुट वैसे भी स्रोत दस्तावेज़ से स्पष्ट रूप से भिन्न है।किसी भी मामले में यह अब
वारंटी
. का उदाहरण नहीं है जिस मॉडल से आप सोर्सिंग कर रहे हैं, लेकिन सिर्फ एक सादा वस्तु। हम उस पर काम कर सकते हैं जैसा कि हम बाद में स्पर्श करते हैं। -
संभवतः यहाँ मुख्य बिंदु यह है कि
populate()
कुछ हद तक "पुरानी टोपी" . है वैसे भी। यह वास्तव में कार्यान्वयन के शुरुआती दिनों में Mongoose में जोड़ा गया एक सुविधा फ़ंक्शन है। यह वास्तव में संबंधित . पर "दूसरी क्वेरी" निष्पादित करता है एक अलग संग्रह में डेटा, और फिर परिणामों को स्मृति में मूल संग्रह आउटपुट में मिला देता है।कई कारणों से, ज्यादातर मामलों में यह वास्तव में कुशल या वांछनीय भी नहीं है। और लोकप्रिय गलत धारणा के विपरीत, यह नहीं है वास्तव में एक "जुड़ना"।
वास्तविक "जॉइन" के लिए आप वास्तव में
$lookup<का उपयोग करते हैं /कोड>
एकत्रीकरण पाइपलाइन चरण, जिसका उपयोग MongoDB दूसरे संग्रह से मेल खाने वाली वस्तुओं को वापस करने के लिए करता है।पॉप्युलेट ()
के विपरीत यह वास्तव में एक ही प्रतिक्रिया के साथ सर्वर से एक ही अनुरोध में किया जाता है। यह नेटवर्क ओवरहेड्स से बचा जाता है, आम तौर पर तेज़ होता है और "असली जुड़ाव" के रूप में आपको ऐसे काम करने की अनुमति मिलती है जोpopulate()
नहीं कर सकता।
इसके बजाय $lookup का उपयोग करें
बहुत त्वरित यहाँ जो कमी है उसका संस्करण यह है कि populate()
. के प्रयास के बजाय .then()
. में परिणाम वापस आने के बाद, आप इसके बजाय जोड़ते हैं। $लुकअप
पाइपलाइन के लिए:
{ "$lookup": {
"from": Account.collection.name,
"localField": "_id",
"foreignField": "_id",
"as": "accounts"
}},
{ "$unwind": "$accounts" },
{ "$project": {
"_id": "$accounts",
"total": 1,
"lineItems": 1
}}
ध्यान दें कि यहां एक बाधा है कि $ का आउटपुट लुकअप
हमेशा है एक सारणी। इससे कोई फर्क नहीं पड़ता कि केवल एक संबंधित आइटम है या आउटपुट के रूप में कई आइटम लाए जाने हैं। पाइपलाइन चरण "localField"
. के मान की तलाश करेगा प्रस्तुत किए गए वर्तमान दस्तावेज़ से और "विदेशी फ़ील्ड"
. में मानों का मिलान करने के लिए उसका उपयोग करें निर्दिष्ट। इस मामले में यह _id
है एकत्रीकरण से $group
_id
. को लक्षित करें विदेशी संग्रह का।
चूंकि आउटपुट हमेशा एक सरणी होता है जैसा कि उल्लेख किया गया है, इस उदाहरण के लिए इसके साथ काम करने का सबसे कारगर तरीका बस एक $अनविंड
सीधे $lookup
के बाद का चरण
. यह सब करने जा रहा है यह लक्ष्य सरणी में लौटाए गए प्रत्येक आइटम के लिए एक नया दस्तावेज़ लौटाएगा, और इस मामले में आप इसे एक होने की उम्मीद करते हैं। उस मामले में जहां _id
विदेशी संग्रह में मेल नहीं खाता, बिना मिलान वाले परिणाम हटा दिए जाएंगे।
एक छोटे से नोट के रूप में, यह वास्तव में एक अनुकूलित पैटर्न है जैसा कि $लुकअप + $अनविंड सहसंयोजन
मूल दस्तावेज के भीतर। यहां एक खास बात होती है जहां $unwind
निर्देश वास्तव में $lookup
में मर्ज कर दिया गया है
कुशल तरीके से संचालन। आप इसके बारे में वहां और अधिक पढ़ सकते हैं।
आबादी का उपयोग करना
उपरोक्त सामग्री से आपको मूल रूप से यह समझने में सक्षम होना चाहिए कि क्यों populate()
यहाँ गलत काम करना है। मूल तथ्य के अलावा कि आउटपुट में अब वारंटी
शामिल नहीं है मॉडल ऑब्जेक्ट, वह मॉडल वास्तव में केवल _accountId
. पर वर्णित विदेशी वस्तुओं के बारे में जानता है संपत्ति जो वैसे भी आउटपुट में मौजूद नहीं है।
अब आप कर सकते हैं वास्तव में एक मॉडल को परिभाषित करता है जिसका उपयोग आउटपुट ऑब्जेक्ट्स को परिभाषित आउटपुट प्रकार में स्पष्ट रूप से डालने के लिए किया जा सकता है। एक के संक्षिप्त प्रदर्शन में इसके लिए आपके आवेदन में कोड जोड़ना शामिल होगा जैसे:
// Special models
const outputSchema = new Schema({
_id: { type: Schema.Types.ObjectId, ref: "Account" },
total: Number,
lineItems: [{ address: String }]
});
const Output = mongoose.model('Output', outputSchema, 'dontuseme');
यह नया आउटपुट
तब मॉडल का उपयोग परिणामी सादे JavaScript ऑब्जेक्ट को Mongoose Documents में "कास्ट" करने के लिए किया जा सकता है ताकि Model.populate()
वास्तव में कहा जा सकता है:
// excerpt
result2 = result2.map(r => new Output(r)); // Cast to Output Mongoose Documents
// Call populate on the list of documents
result2 = await Output.populate(result2, { path: '_id' })
log(result2);
चूंकि आउटपुट
एक स्कीमा परिभाषित है जो _id
. पर "संदर्भ" से अवगत है इसके दस्तावेज़ों का क्षेत्र Model.populate()
इस बात से अवगत है कि उसे क्या करने की आवश्यकता है और आइटम लौटाता है।
हालांकि सावधान रहें क्योंकि यह वास्तव में एक और प्रश्न उत्पन्न करता है। यानी:
Mongoose: warranties.aggregate([ { '$match': { payStatus: 'Invoiced Next Billing Cycle' } }, { '$group': { _id: '$_accountId', total: { '$sum': '$warrantyFee' }, lineItems: { '$push': { _id: '$_id', address: { '$trim': { input: { '$reduce': { input: { '$objectToArray': '$address' }, initialValue: '', in: { '$concat': [ '$$value', ' ', [Object] ] } } }, chars: ' ' } } } } } } ], {})
Mongoose: accounts.find({ _id: { '$in': [ ObjectId("5bf4b591a06509544b8cf75c"), ObjectId("5bf4b591a06509544b8cf75b") ] } }, { projection: {} })
जहां पहली पंक्ति कुल आउटपुट है, और फिर आप संबंधित खाता
को वापस करने के लिए सर्वर से फिर से संपर्क कर रहे हैं मॉडल प्रविष्टियां।
सारांश
तो ये आपके विकल्प हैं, लेकिन यह बिल्कुल स्पष्ट होना चाहिए कि इसके लिए आधुनिक दृष्टिकोण $लुकअप
और एक असली "शामिल हों" . प्राप्त करें जो populate()
. नहीं है वास्तव में कर रहा है।
इनमें से प्रत्येक दृष्टिकोण वास्तव में व्यवहार में कैसे काम करता है, इसके पूर्ण प्रदर्शन के रूप में एक सूची शामिल है। कुछ कलात्मक लाइसेंस यहां लिया गया है, इसलिए दर्शाए गए मॉडल बिल्कुल . नहीं हो सकते हैं आपके पास जैसा है, लेकिन मूल अवधारणाओं को प्रतिलिपि प्रस्तुत करने योग्य तरीके से प्रदर्शित करने के लिए पर्याप्त है:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/joindemo';
const opts = { useNewUrlParser: true };
// Sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
// Schema defs
const warrantySchema = new Schema({
address: {
street: String,
city: String,
state: String,
zip: Number
},
warrantyFee: Number,
_accountId: { type: Schema.Types.ObjectId, ref: "Account" },
payStatus: String
});
const accountSchema = new Schema({
name: String,
contactName: String,
contactEmail: String
});
// Special models
const outputSchema = new Schema({
_id: { type: Schema.Types.ObjectId, ref: "Account" },
total: Number,
lineItems: [{ address: String }]
});
const Output = mongoose.model('Output', outputSchema, 'dontuseme');
const Warranty = mongoose.model('Warranty', warrantySchema);
const Account = mongoose.model('Account', accountSchema);
// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
// main
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)
// set up data
let [first, second, third] = await Account.insertMany(
[
['First Account', 'First Person', '[email protected]'],
['Second Account', 'Second Person', '[email protected]'],
['Third Account', 'Third Person', '[email protected]']
].map(([name, contactName, contactEmail]) =>
({ name, contactName, contactEmail })
)
);
await Warranty.insertMany(
[
{
address: {
street: '1 Some street',
city: 'Somewhere',
state: 'TX',
zip: 1234
},
warrantyFee: 100,
_accountId: first,
payStatus: 'Invoiced Next Billing Cycle'
},
{
address: {
street: '2 Other street',
city: 'Elsewhere',
state: 'CA',
zip: 5678
},
warrantyFee: 100,
_accountId: first,
payStatus: 'Invoiced Next Billing Cycle'
},
{
address: {
street: '3 Other street',
city: 'Elsewhere',
state: 'NY',
zip: 1928
},
warrantyFee: 100,
_accountId: first,
payStatus: 'Invoiced Already'
},
{
address: {
street: '21 Jump street',
city: 'Anywhere',
state: 'NY',
zip: 5432
},
warrantyFee: 100,
_accountId: second,
payStatus: 'Invoiced Next Billing Cycle'
}
]
);
// Aggregate $lookup
let result1 = await Warranty.aggregate([
{ "$match": {
"payStatus": "Invoiced Next Billing Cycle"
}},
{ "$group": {
"_id": "$_accountId",
"total": { "$sum": "$warrantyFee" },
"lineItems": {
"$push": {
"_id": "$_id",
"address": {
"$trim": {
"input": {
"$reduce": {
"input": { "$objectToArray": "$address" },
"initialValue": "",
"in": {
"$concat": [ "$$value", " ", { "$toString": "$$this.v" } ] }
}
},
"chars": " "
}
}
}
}
}},
{ "$lookup": {
"from": Account.collection.name,
"localField": "_id",
"foreignField": "_id",
"as": "accounts"
}},
{ "$unwind": "$accounts" },
{ "$project": {
"_id": "$accounts",
"total": 1,
"lineItems": 1
}}
])
log(result1);
// Convert and populate
let result2 = await Warranty.aggregate([
{ "$match": {
"payStatus": "Invoiced Next Billing Cycle"
}},
{ "$group": {
"_id": "$_accountId",
"total": { "$sum": "$warrantyFee" },
"lineItems": {
"$push": {
"_id": "$_id",
"address": {
"$trim": {
"input": {
"$reduce": {
"input": { "$objectToArray": "$address" },
"initialValue": "",
"in": {
"$concat": [ "$$value", " ", { "$toString": "$$this.v" } ] }
}
},
"chars": " "
}
}
}
}
}}
]);
result2 = result2.map(r => new Output(r));
result2 = await Output.populate(result2, { path: '_id' })
log(result2);
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
और पूरा आउटपुट:
Mongoose: dontuseme.deleteMany({}, {})
Mongoose: warranties.deleteMany({}, {})
Mongoose: accounts.deleteMany({}, {})
Mongoose: accounts.insertMany([ { _id: 5bf4b591a06509544b8cf75b, name: 'First Account', contactName: 'First Person', contactEmail: '[email protected]', __v: 0 }, { _id: 5bf4b591a06509544b8cf75c, name: 'Second Account', contactName: 'Second Person', contactEmail: '[email protected]', __v: 0 }, { _id: 5bf4b591a06509544b8cf75d, name: 'Third Account', contactName: 'Third Person', contactEmail: '[email protected]', __v: 0 } ], {})
Mongoose: warranties.insertMany([ { _id: 5bf4b591a06509544b8cf75e, address: { street: '1 Some street', city: 'Somewhere', state: 'TX', zip: 1234 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75b, payStatus: 'Invoiced Next Billing Cycle', __v: 0 }, { _id: 5bf4b591a06509544b8cf75f, address: { street: '2 Other street', city: 'Elsewhere', state: 'CA', zip: 5678 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75b, payStatus: 'Invoiced Next Billing Cycle', __v: 0 }, { _id: 5bf4b591a06509544b8cf760, address: { street: '3 Other street', city: 'Elsewhere', state: 'NY', zip: 1928 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75b, payStatus: 'Invoiced Already', __v: 0 }, { _id: 5bf4b591a06509544b8cf761, address: { street: '21 Jump street', city: 'Anywhere', state: 'NY', zip: 5432 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75c, payStatus: 'Invoiced Next Billing Cycle', __v: 0 } ], {})
Mongoose: warranties.aggregate([ { '$match': { payStatus: 'Invoiced Next Billing Cycle' } }, { '$group': { _id: '$_accountId', total: { '$sum': '$warrantyFee' }, lineItems: { '$push': { _id: '$_id', address: { '$trim': { input: { '$reduce': { input: { '$objectToArray': '$address' }, initialValue: '', in: { '$concat': [ '$$value', ' ', [Object] ] } } }, chars: ' ' } } } } } }, { '$lookup': { from: 'accounts', localField: '_id', foreignField: '_id', as: 'accounts' } }, { '$unwind': '$accounts' }, { '$project': { _id: '$accounts', total: 1, lineItems: 1 } } ], {})
[
{
"total": 100,
"lineItems": [
{
"_id": "5bf4b591a06509544b8cf761",
"address": "21 Jump street Anywhere NY 5432"
}
],
"_id": {
"_id": "5bf4b591a06509544b8cf75c",
"name": "Second Account",
"contactName": "Second Person",
"contactEmail": "[email protected]",
"__v": 0
}
},
{
"total": 200,
"lineItems": [
{
"_id": "5bf4b591a06509544b8cf75e",
"address": "1 Some street Somewhere TX 1234"
},
{
"_id": "5bf4b591a06509544b8cf75f",
"address": "2 Other street Elsewhere CA 5678"
}
],
"_id": {
"_id": "5bf4b591a06509544b8cf75b",
"name": "First Account",
"contactName": "First Person",
"contactEmail": "[email protected]",
"__v": 0
}
}
]
Mongoose: warranties.aggregate([ { '$match': { payStatus: 'Invoiced Next Billing Cycle' } }, { '$group': { _id: '$_accountId', total: { '$sum': '$warrantyFee' }, lineItems: { '$push': { _id: '$_id', address: { '$trim': { input: { '$reduce': { input: { '$objectToArray': '$address' }, initialValue: '', in: { '$concat': [ '$$value', ' ', [Object] ] } } }, chars: ' ' } } } } } } ], {})
Mongoose: accounts.find({ _id: { '$in': [ ObjectId("5bf4b591a06509544b8cf75c"), ObjectId("5bf4b591a06509544b8cf75b") ] } }, { projection: {} })
[
{
"_id": {
"_id": "5bf4b591a06509544b8cf75c",
"name": "Second Account",
"contactName": "Second Person",
"contactEmail": "[email protected]",
"__v": 0
},
"total": 100,
"lineItems": [
{
"_id": "5bf4b591a06509544b8cf761",
"address": "21 Jump street Anywhere NY 5432"
}
]
},
{
"_id": {
"_id": "5bf4b591a06509544b8cf75b",
"name": "First Account",
"contactName": "First Person",
"contactEmail": "[email protected]",
"__v": 0
},
"total": 200,
"lineItems": [
{
"_id": "5bf4b591a06509544b8cf75e",
"address": "1 Some street Somewhere TX 1234"
},
{
"_id": "5bf4b591a06509544b8cf75f",
"address": "2 Other street Elsewhere CA 5678"
}
]
}
]