आपके उपलब्ध MongoDB संस्करण के आधार पर निश्चित रूप से कुछ दृष्टिकोण हैं। ये $lookup
. के विभिन्न उपयोगों से भिन्न होते हैं .populate()
. पर ऑब्जेक्ट मैनिपुलेशन को सक्षम करने के माध्यम से .lean()
. के माध्यम से परिणाम ।
मैं आपसे अनुरोध करता हूं कि आप अनुभागों को ध्यान से पढ़ें, और इस बात से अवगत रहें कि आपके कार्यान्वयन समाधान पर विचार करते समय सब कुछ वैसा नहीं हो सकता जैसा लगता है।
MongoDB 3.6, "नेस्टेड" $लुकअप
MongoDB 3.6 के साथ $lookup
ऑपरेटर को pipeline
. शामिल करने की अतिरिक्त क्षमता प्राप्त होती है अभिव्यक्ति केवल "स्थानीय" को "विदेशी" कुंजी मान में शामिल करने के विरोध में है, इसका मतलब यह है कि आप अनिवार्य रूप से प्रत्येक $lookup
कर सकते हैं इन पाइपलाइन अभिव्यक्तियों के भीतर "नेस्टेड" के रूप में
Venue.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
{ "$lookup": {
"from": Review.collection.name,
"let": { "reviews": "$reviews" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$reviews" ] } } },
{ "$lookup": {
"from": Comment.collection.name,
"let": { "comments": "$comments" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$comments" ] } } },
{ "$lookup": {
"from": Author.collection.name,
"let": { "author": "$author" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$author" ] } } },
{ "$addFields": {
"isFollower": {
"$in": [
mongoose.Types.ObjectId(req.user.id),
"$followers"
]
}
}}
],
"as": "author"
}},
{ "$addFields": {
"author": { "$arrayElemAt": [ "$author", 0 ] }
}}
],
"as": "comments"
}},
{ "$sort": { "createdAt": -1 } }
],
"as": "reviews"
}},
])
यह वास्तव में काफी शक्तिशाली हो सकता है, जैसा कि आप मूल पाइपलाइन के परिप्रेक्ष्य से देखते हैं, यह वास्तव में केवल "reviews"
में सामग्री जोड़ने के बारे में जानता है सरणी और फिर प्रत्येक बाद की "नेस्टेड" पाइपलाइन अभिव्यक्ति भी केवल कभी भी शामिल होने से इसके "आंतरिक" तत्वों को देखती है।
यह शक्तिशाली है और कुछ मामलों में यह थोड़ा स्पष्ट हो सकता है क्योंकि सभी क्षेत्र पथ घोंसले के स्तर के सापेक्ष हैं, लेकिन यह बीएसओएन संरचना में इंडेंटेशन रेंगना शुरू कर देता है, और आपको इस बात से अवगत होने की आवश्यकता है कि आप सरणी से मेल खाते हैं या नहीं या संरचना को पार करने में एकवचन मान।
ध्यान दें कि हम यहां "लेखक की संपत्ति को समतल करना" जैसे काम भी कर सकते हैं जैसा कि "comments"
में देखा गया है सरणी प्रविष्टियाँ। सभी $lookup
लक्ष्य आउटपुट एक "सरणी" हो सकता है, लेकिन एक "उप-पाइपलाइन" के भीतर हम उस एकल तत्व सरणी को केवल एक मान में फिर से आकार दे सकते हैं।
मानक MongoDB $लुकअप
अभी भी "सर्वर पर शामिल हों" रखते हुए आप वास्तव में $lookup
. के साथ ऐसा कर सकते हैं , लेकिन यह सिर्फ मध्यवर्ती प्रसंस्करण लेता है। $unwind
. के साथ एक सरणी के पुनर्निर्माण के साथ यह लंबे समय तक चलने वाला दृष्टिकोण है और $group
. का उपयोग करके सरणियों के पुनर्निर्माण के चरण:
Venue.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
{ "$lookup": {
"from": Review.collection.name,
"localField": "reviews",
"foreignField": "_id",
"as": "reviews"
}},
{ "$unwind": "$reviews" },
{ "$lookup": {
"from": Comment.collection.name,
"localField": "reviews.comments",
"foreignField": "_id",
"as": "reviews.comments",
}},
{ "$unwind": "$reviews.comments" },
{ "$lookup": {
"from": Author.collection.name,
"localField": "reviews.comments.author",
"foreignField": "_id",
"as": "reviews.comments.author"
}},
{ "$unwind": "$reviews.comments.author" },
{ "$addFields": {
"reviews.comments.author.isFollower": {
"$in": [
mongoose.Types.ObjectId(req.user.id),
"$reviews.comments.author.followers"
]
}
}},
{ "$group": {
"_id": {
"_id": "$_id",
"reviewId": "$review._id"
},
"name": { "$first": "$name" },
"addedBy": { "$first": "$addedBy" },
"review": {
"$first": {
"_id": "$review._id",
"createdAt": "$review.createdAt",
"venue": "$review.venue",
"author": "$review.author",
"content": "$review.content"
}
},
"comments": { "$push": "$reviews.comments" }
}},
{ "$sort": { "_id._id": 1, "review.createdAt": -1 } },
{ "$group": {
"_id": "$_id._id",
"name": { "$first": "$name" },
"addedBy": { "$first": "$addedBy" },
"reviews": {
"$push": {
"_id": "$review._id",
"venue": "$review.venue",
"author": "$review.author",
"content": "$review.content",
"comments": "$comments"
}
}
}}
])
यह वास्तव में उतना कठिन नहीं है जितना आप पहले सोच सकते हैं और $lookup
के एक सरल पैटर्न का अनुसरण करते हैं और $unwind
जैसा कि आप प्रत्येक सरणी के माध्यम से आगे बढ़ते हैं।
"author"
पाठ्यक्रम का विवरण एकवचन है, इसलिए एक बार जब वह "अवांछित" हो जाता है, तो आप बस इसे उसी तरह छोड़ना चाहते हैं, फ़ील्ड को जोड़ दें और सरणियों में "वापस लुढ़कने" की प्रक्रिया शुरू करें।
केवल दो हैं स्तरों को मूल Venue
पर वापस लाने के लिए दस्तावेज़, इसलिए पहला विवरण स्तर Review
. द्वारा है "comments"
. के पुनर्निर्माण के लिए सरणी। आपको बस $push
. करना है "$reviews.comments"
. का पथ इन्हें एकत्र करने के लिए, और जब तक "$reviews._id"
फ़ील्ड "ग्रुपिंग _id" में है, केवल अन्य चीजें जिन्हें आपको रखने की आवश्यकता है वे अन्य सभी फ़ील्ड हैं। आप इन सभी को _id
. में डाल सकते हैं साथ ही, या आप $first
. का उपयोग कर सकते हैं ।
उसके साथ केवल एक और $group
है Venue
. पर वापस जाने के लिए चरण अपने आप। इस बार समूहीकरण कुंजी "$_id"
. है बेशक, आयोजन स्थल के सभी गुणों के साथ $first
. का उपयोग किया जा रहा है और शेष "$review"
विवरण $push
. के साथ एक सरणी में वापस जा रहा है . बेशक "$comments"
पिछले $group
. से आउटपुट "review.comments"
. बन जाता है पथ।
एक दस्तावेज़ और उसके संबंधों पर काम करना, यह वास्तव में इतना बुरा नहीं है। $unwind
पाइपलाइन ऑपरेटर आम तौर पर . कर सकते हैं एक प्रदर्शन समस्या हो, लेकिन इस उपयोग के संदर्भ में यह वास्तव में इतना अधिक प्रभाव पैदा नहीं करना चाहिए।
चूंकि डेटा अभी भी "सर्वर पर शामिल" किया जा रहा है, वहां अभी भी . है अन्य शेष विकल्प की तुलना में बहुत कम ट्रैफ़िक।
जावास्क्रिप्ट हेरफेर
बेशक यहां दूसरा मामला यह है कि सर्वर पर ही डेटा बदलने के बजाय, आप वास्तव में परिणाम में हेरफेर करते हैं। अधिकांश . में मामलों में मैं इस दृष्टिकोण के पक्ष में रहूंगा क्योंकि डेटा में कोई भी "जोड़" शायद क्लाइंट पर सबसे अच्छा संभाला जाता है।
populate()
. का उपयोग करने में निश्चित रूप से समस्या जबकि यह 'जैसा दिख सकता है' . हो सकता है एक बहुत अधिक सरल प्रक्रिया, यह वास्तव में शामिल नहीं है किसी भी तरह से। सभी populate()
वास्तव में "छिपाना" . है एकाधिक . सबमिट करने की अंतर्निहित प्रक्रिया डेटाबेस के लिए क्वेरी, और फिर async हैंडलिंग के माध्यम से परिणामों की प्रतीक्षा करना।
तो "उपस्थिति" शामिल होने का वास्तव में सर्वर से कई अनुरोधों का परिणाम है और फिर "क्लाइंट साइड मैनिपुलेशन" कर रहा है डेटा को सरणियों के भीतर विवरण एम्बेड करने के लिए।
तो इसके अलावा स्पष्ट चेतावनी कि प्रदर्शन विशेषताएँ सर्वर $lookup
. के बराबर होने के करीब कहीं नहीं हैं , दूसरी चेतावनी निश्चित रूप से यह है कि परिणाम में "नेवला दस्तावेज़" वास्तव में आगे के हेरफेर के अधीन सादे जावास्क्रिप्ट ऑब्जेक्ट नहीं हैं।
तो इस दृष्टिकोण को अपनाने के लिए, आपको .lean()
. जोड़ना होगा निष्पादन से पहले क्वेरी के लिए विधि, नेवले को Document
के बजाय "सादे जावास्क्रिप्ट ऑब्जेक्ट" वापस करने का निर्देश देने के लिए प्रकार जो मॉडल से जुड़ी स्कीमा विधियों के साथ डाली जाती हैं। निश्चित रूप से यह देखते हुए कि परिणामी डेटा की अब किसी भी "उदाहरण विधियों" तक पहुंच नहीं है, जो अन्यथा संबंधित मॉडल से स्वयं संबद्ध होंगे:
let venue = await Venue.findOne({ _id: id.id })
.populate({
path: 'reviews',
options: { sort: { createdAt: -1 } },
populate: [
{ path: 'comments', populate: [{ path: 'author' }] }
]
})
.lean();
अब Venue
एक सादा वस्तु है, हम बस आवश्यकतानुसार संसाधित और समायोजित कर सकते हैं:
venue.reviews = venue.reviews.map( r =>
({
...r,
comments: r.comments.map( c =>
({
...c,
author: {
...c.author,
isAuthor: c.author.followers.map( f => f.toString() ).indexOf(req.user.id) != -1
}
})
)
})
);
तो यह वास्तव में प्रत्येक आंतरिक सरणियों के माध्यम से साइकिल चलाने की बात है जब तक कि आप उस स्तर तक नीचे नहीं आते जहाँ आप followers
को देख सकते हैं author
. के भीतर सरणी विवरण। तब तुलना ObjectId
. से की जा सकती है पहले .map()
. का उपयोग करने के बाद उस सरणी में संग्रहीत मान req.user.id
. से तुलना करने के लिए "स्ट्रिंग" मान लौटाने के लिए जो एक स्ट्रिंग भी है (यदि नहीं है तो .toString()
. भी जोड़ें उस पर), चूंकि सामान्य तौर पर जावास्क्रिप्ट कोड के माध्यम से इन मानों की इस तरह तुलना करना आसान होता है।
फिर भी मुझे इस बात पर जोर देने की आवश्यकता है कि यह "सरल दिखता है" लेकिन वास्तव में यह उस तरह की चीज है जिसे आप वास्तव में सिस्टम प्रदर्शन से बचना चाहते हैं, क्योंकि उन अतिरिक्त प्रश्नों और सर्वर और क्लाइंट के बीच स्थानांतरण प्रसंस्करण के समय में बहुत अधिक खर्च होता है और यहां तक कि ओवरहेड अनुरोध के कारण यह होस्टिंग प्रदाताओं के बीच परिवहन में वास्तविक लागत को जोड़ता है।
सारांश
वे मूल रूप से आपके दृष्टिकोण हैं जिन्हें आप ले सकते हैं, "अपना खुद का रोलिंग" से कम जहां आप वास्तव में "एकाधिक प्रश्न" करते हैं .populate()
. हेल्पर का उपयोग करने के बजाय डेटाबेस में स्वयं है।
जब तक आप .lean()
लागू करते हैं, तब तक आप किसी भी अन्य डेटा संरचना की तरह ही आउटपुट आउटपुट का उपयोग करके डेटा में हेरफेर कर सकते हैं। लौटाए गए नेवला दस्तावेज़ों से सादे वस्तु डेटा को परिवर्तित करने या अन्यथा निकालने के लिए क्वेरी के लिए।
जबकि समग्र दृष्टिकोण कहीं अधिक शामिल दिखते हैं, "बहुत कुछ" . हैं सर्वर पर यह काम करने के अधिक फायदे। बड़े परिणाम सेट को सॉर्ट किया जा सकता है, आगे फ़िल्टरिंग के लिए गणना की जा सकती है, और निश्चित रूप से आपको "एकल प्रतिक्रिया" मिलती है एक "एकल अनुरोध" . के लिए सर्वर के लिए बनाया गया, सभी बिना किसी अतिरिक्त ओवरहेड के।
यह पूरी तरह से बहस योग्य है कि पाइपलाइनों का निर्माण केवल स्कीमा पर पहले से संग्रहीत विशेषताओं के आधार पर किया जा सकता है। तो संलग्न स्कीमा के आधार पर इस "निर्माण" को करने के लिए अपनी खुद की विधि लिखना बहुत मुश्किल नहीं होना चाहिए।
लंबी अवधि में $lookup
बेहतर समाधान है, लेकिन आपको शायद प्रारंभिक कोडिंग में थोड़ा और काम करने की आवश्यकता होगी, यदि निश्चित रूप से आप यहां सूचीबद्ध चीज़ों से केवल कॉपी नहीं करते हैं;)