MongoDB के $in
क्लॉज का उपयोग करते समय , लौटाए गए दस्तावेजों का क्रम हमेशा सरणी तर्क के क्रम के अनुरूप होता है?
MongoDB के $in
क्लॉज का उपयोग करते समय , लौटाए गए दस्तावेजों का क्रम हमेशा सरणी तर्क के क्रम के अनुरूप होता है?
जवाबों:
जैसा कि उल्लेख किया गया है, खंड में एक $ के सरणी में तर्कों का क्रम दस्तावेजों को कैसे पुनर्प्राप्त किया जाता है के आदेश को प्रतिबिंबित नहीं करता है। यह निश्चित रूप से प्राकृतिक क्रम होगा या चयनित सूचकांक क्रम के अनुसार दिखाया जाएगा।
यदि आपको इस आदेश को संरक्षित करने की आवश्यकता है, तो आपके पास मूल रूप से दो विकल्प हैं।
तो मान लीजिए कि आप के मूल्यों पर मिलान रहे थे _id
एक सरणी है कि करने के लिए पारित किया जा रहा है के साथ अपने दस्तावेजों में $in
के रूप में [ 4, 2, 8 ]
।
var list = [ 4, 2, 8 ];
db.collection.aggregate([
// Match the selected documents by "_id"
{ "$match": {
"_id": { "$in": [ 4, 2, 8 ] },
},
// Project a "weight" to each document
{ "$project": {
"weight": { "$cond": [
{ "$eq": [ "$_id", 4 ] },
1,
{ "$cond": [
{ "$eq": [ "$_id", 2 ] },
2,
3
]}
]}
}},
// Sort the results
{ "$sort": { "weight": 1 } }
])
तो वह विस्तारित रूप होगा। मूल रूप से यहां क्या होता है, जैसा कि मूल्यों के सरणी को पारित किया जाता है, वैसे ही मूल्यों का परीक्षण करने और एक उचित वजन प्रदान करने के लिए $in
"नेस्टेड" $cond
बयान का निर्माण करें । जैसा कि "वजन" मान सरणी में तत्वों के क्रम को दर्शाता है, तो आप आवश्यक क्रम में अपने परिणाम प्राप्त करने के लिए उस मूल्य को एक प्रकार के चरण में पारित कर सकते हैं।
बेशक आप कोड में पाइपलाइन स्टेटमेंट को वास्तव में "बिल्ड" करते हैं, इस तरह से:
var list = [ 4, 2, 8 ];
var stack = [];
for (var i = list.length - 1; i > 0; i--) {
var rec = {
"$cond": [
{ "$eq": [ "$_id", list[i-1] ] },
i
]
};
if ( stack.length == 0 ) {
rec["$cond"].push( i+1 );
} else {
var lval = stack.pop();
rec["$cond"].push( lval );
}
stack.push( rec );
}
var pipeline = [
{ "$match": { "_id": { "$in": list } }},
{ "$project": { "weight": stack[0] }},
{ "$sort": { "weight": 1 } }
];
db.collection.aggregate( pipeline );
बेशक अगर यह सब आपकी संवेदनाओं के लिए भारी लगता है, तो आप मैपआरड्यूस का उपयोग करके वही काम कर सकते हैं, जो सरल दिखता है, लेकिन संभवतः थोड़ा धीमा चलेगा।
var list = [ 4, 2, 8 ];
db.collection.mapReduce(
function () {
var order = inputs.indexOf(this._id);
emit( order, { doc: this } );
},
function() {},
{
"out": { "inline": 1 },
"query": { "_id": { "$in": list } },
"scope": { "inputs": list } ,
"finalize": function (key, value) {
return value.doc;
}
}
)
और यह मूल रूप से इनपुट सरणी में कैसे होता है के "इंडेक्स ऑर्डर" में उत्सर्जित "कुंजी" मूल्यों पर निर्भर करता है।
इसलिए वे अनिवार्य रूप से एक इनपुट सूची के क्रम को एक ऐसी $in
स्थिति में बनाए रखने के आपके तरीके हैं जहां आपके पास पहले से ही एक निर्धारित क्रम में वह सूची है।
केवल MongoDB verion> = 3.4 के लिए लागू एकत्रीकरण क्वेरी का उपयोग करने का दूसरा तरीका -
इस अच्छी ब्लॉग पोस्ट का श्रेय जाता है ।
इस क्रम में लाए जाने वाले उदाहरण दस्तावेज -
var order = [ "David", "Charlie", "Tess" ];
पूछताछ -
var query = [
{$match: {name: {$in: order}}},
{$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}},
{$sort: {"__order": 1}}
];
var result = db.users.aggregate(query);
उपयोग किए गए इन एकत्रीकरण ऑपरेटरों को समझाते हुए पोस्ट से एक और उद्धरण -
3.4 में "$ addFields" चरण नया है और यह आपको अन्य सभी मौजूदा क्षेत्रों को जाने बिना मौजूदा दस्तावेज़ों के लिए "$ प्रोजेक्ट" नए फ़ील्ड की अनुमति देता है। नया "$ indexOfArray" अभिव्यक्ति किसी दिए गए सरणी में विशेष तत्व की स्थिति देता है।
मूल रूप से addFields
ऑपरेटर order
हर दस्तावेज़ के लिए एक नया फ़ील्ड जोड़ता है जब वह इसे पाता है और यह order
फ़ील्ड हमारे द्वारा प्रदान किए गए हमारे सरणी के मूल क्रम का प्रतिनिधित्व करता है। फिर हम इस क्षेत्र के आधार पर दस्तावेजों को क्रमबद्ध करते हैं।
यदि आप उपयोग नहीं करना चाहते हैं aggregate
, तो एक अन्य समाधान का उपयोग करना है find
और उसके बाद ग्राहक-पक्ष का उपयोग करके डॉक्स परिणाम सॉर्ट करें array#sort
:
यदि $in
मान आदिम प्रकार हैं जैसे संख्याएँ आप एक दृष्टिकोण का उपयोग कर सकते हैं जैसे:
var ids = [4, 2, 8, 1, 9, 3, 5, 6];
MyModel.find({ _id: { $in: ids } }).exec(function(err, docs) {
docs.sort(function(a, b) {
// Sort docs by the order of their _id values in ids.
return ids.indexOf(a._id) - ids.indexOf(b._id);
});
});
यदि $in
मान एस जैसे गैर-आदिम प्रकार हैं ObjectId
, तो indexOf
उस मामले में संदर्भ द्वारा तुलना के रूप में एक और दृष्टिकोण की आवश्यकता होती है।
यदि आप Node.js 4.x + का उपयोग कर रहे हैं, तो आप फ़ंक्शन को बदलकर Array#findIndex
और ObjectID#equals
इसे संभालने के लिए उपयोग कर सकते हैं sort
:
docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) -
ids.findIndex(id => b._id.equals(id)));
या किसी भी Node.js संस्करण के साथ, अंडरस्कोर / लॉश के साथ findIndex
:
docs.sort(function (a, b) {
return _.findIndex(ids, function (id) { return a._id.equals(id); }) -
_.findIndex(ids, function (id) { return b._id.equals(id); });
});
Document#equals
के _id
क्षेत्र की तुलना करने के लिए मोंगोसे का उपयोग कर रहा था । _id
तुलना स्पष्ट करने के लिए अद्यतन किया गया । पूछने के लिए धन्यवाद।
करने के लिए इसी तरह के JonnyHK के समाधान, आप दस्तावेज़ से लौटे क्रम बदल सकते हैं find
अपने ग्राहक में के संयोजन के साथ (यदि आपके ग्राहक जावास्क्रिप्ट में है) map
और Array.prototype.find
ECMAScript 2015 में समारोह:
Collection.find({ _id: { $in: idArray } }).toArray(function(err, res) {
var orderedResults = idArray.map(function(id) {
return res.find(function(document) {
return document._id.equals(id);
});
});
});
नोटों की एक जोड़ी:
idArray
एक सरणी हैObjectId
map
कॉलबैक में अपने कोड को सरल बनाने के लिए कर सकते हैं ।find
के प्रत्येक तत्व के लिए एरे को ट्रैवर्स करता है (बाहरी से map
)। यह बहुत ही अयोग्य है, क्योंकि लुकअप टेबल का उपयोग करके ओ (एन) समाधान है।
मुझे पता है कि यह सवाल Mongoose JS फ्रेमवर्क से संबंधित है, लेकिन डुप्लिकेटेड एक सामान्य है, इसलिए मुझे उम्मीद है कि यहां Python (PyMongo) समाधान पोस्ट करना ठीक है।
things = list(db.things.find({'_id': {'$in': id_array}}))
things.sort(key=lambda thing: id_array.index(thing['_id']))
# things are now sorted according to id_array order
मानगो रिटर्न के बाद परिणाम ऑर्डर करने का एक आसान तरीका यह है कि आईडी के साथ एक ऑब्जेक्ट को कुंजी के रूप में बनाया जाए और फिर दिए गए _id पर एक सरणी वापस करने के लिए मैप करें जो सही तरीके से ऑर्डर किया गया है।
async function batchUsers(Users, keys) {
const unorderedUsers = await Users.find({_id: {$in: keys}}).toArray()
let obj = {}
unorderedUsers.forEach(x => obj[x._id]=x)
const ordered = keys.map(key => obj[key])
return ordered
}
मुझे पता है कि यह एक पुराना धागा है, लेकिन अगर आप सरणी में केवल आईडी का मान लौटा रहे हैं, तो आपको इस वाक्यविन्यास का विकल्प चुनना पड़ सकता है। जैसा कि मैं एक mongo ObjectId प्रारूप के साथ मिलान करने के लिए indexOf मान प्राप्त नहीं कर सका।
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(this._id.equals(inputs[i])) {
var order = i;
}
}
emit(order, {doc: this});
};
Mongo ObjectId को कैसे कन्वर्ट करें। 'ObjectId ()' रैपर को शामिल किए बिना - केवल मान?
आप $ या खंड के साथ आदेश की गारंटी दे सकते हैं।
इसलिए $or: [ _ids.map(_id => ({_id}))]
इसके बजाय उपयोग करें ।
$or
वैकल्पिक हल काम नहीं किया है v2.6 के बाद से ।
मानगो से परिणाम प्राप्त होने के बाद यह एक कोड समाधान है। इंडेक्स को स्टोर करने के लिए मैप का उपयोग करना और फिर मानों की अदला-बदली करना।
catDetails := make([]CategoryDetail, 0)
err = sess.DB(mdb).C("category").
Find(bson.M{
"_id": bson.M{"$in": path},
"is_active": 1,
"name": bson.M{"$ne": ""},
"url.path": bson.M{"$exists": true, "$ne": ""},
}).
Select(
bson.M{
"is_active": 1,
"name": 1,
"url.path": 1,
}).All(&catDetails)
if err != nil{
return
}
categoryOrderMap := make(map[int]int)
for index, v := range catDetails {
categoryOrderMap[v.Id] = index
}
counter := 0
for i := 0; counter < len(categoryOrderMap); i++ {
if catId := int(path[i].(float64)); catId > 0 {
fmt.Println("cat", catId)
if swapIndex, exists := categoryOrderMap[catId]; exists {
if counter != swapIndex {
catDetails[swapIndex], catDetails[counter] = catDetails[counter], catDetails[swapIndex]
categoryOrderMap[catId] = counter
categoryOrderMap[catDetails[swapIndex].Id] = swapIndex
}
counter++
}
}
}