क्या MongoDB का क्लॉज गारंटी ऑर्डर में $ है


90

MongoDB के $inक्लॉज का उपयोग करते समय , लौटाए गए दस्तावेजों का क्रम हमेशा सरणी तर्क के क्रम के अनुरूप होता है?


8
इस सुविधा के लिए MongoDB टिकट
मैत्री

जवाबों:


80

जैसा कि उल्लेख किया गया है, खंड में एक $ के सरणी में तर्कों का क्रम दस्तावेजों को कैसे पुनर्प्राप्त किया जाता है के आदेश को प्रतिबिंबित नहीं करता है। यह निश्चित रूप से प्राकृतिक क्रम होगा या चयनित सूचकांक क्रम के अनुसार दिखाया जाएगा।

यदि आपको इस आदेश को संरक्षित करने की आवश्यकता है, तो आपके पास मूल रूप से दो विकल्प हैं।

तो मान लीजिए कि आप के मूल्यों पर मिलान रहे थे _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 );

MapReduce का उपयोग करके दृष्टिकोण


बेशक अगर यह सब आपकी संवेदनाओं के लिए भारी लगता है, तो आप मैपआरड्यूस का उपयोग करके वही काम कर सकते हैं, जो सरल दिखता है, लेकिन संभवतः थोड़ा धीमा चलेगा।

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स्थिति में बनाए रखने के आपके तरीके हैं जहां आपके पास पहले से ही एक निर्धारित क्रम में वह सूची है।


2
बहुत बढ़िया जवाब। जिन लोगों को इसकी आवश्यकता है, उनके लिए यहां
लॉरेंस जोन्स

1
@NeilLunn मैंने एग्रीगेट का उपयोग करते हुए दृष्टिकोण की कोशिश की, लेकिन मुझे आईडी और वजन मिलता है। क्या आप जानते हैं कि पदों (ऑब्जेक्ट) को कैसे पुनः प्राप्त करें?
जुआनजो लाएन्ज़ रेचे

1
@NeilLunn मैंने वास्तव में किया था (यह यहाँ है stackoverflow.com/questions/27525235/… ) लेकिन केवल टिप्पणी यहाँ उल्लेख कर रही थी, हालांकि मैंने अपना प्रश्न पोस्ट करने से पहले यह जाँच की थी। क्या आप वहां मेरी मदद कर सकते हैं? धन्यवाद!
जुआनजो लाएन्ज़ रेचे

1
पता है कि यह पुराना है, लेकिन मैंने बहुत समय बर्बाद कर दिया है क्यों inputs.indexOf () इस के साथ मेल नहीं खा रहा था। यदि आप ऑब्जेक्ट आईडी का मूल्य वापस कर रहे हैं, तो आपको इस सिंटैक्स का विकल्प चुनना पड़ सकता है: obj.map = function () {for (var i = 0; i <inputs.length; i ++) {if (यह) _id.equals (इनपुट्स [i])) {var ऑर्डर = i; }} उत्सर्जन (आदेश, {डॉक्टर: यह}); };
NoobSter

1
आप "$ परियोजना" के बजाय "$ addFields" का उपयोग कर सकते हैं यदि आप सभी मूल फ़ील्ड भी चाहते हैं
जोडो

40

केवल 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फ़ील्ड हमारे द्वारा प्रदान किए गए हमारे सरणी के मूल क्रम का प्रतिनिधित्व करता है। फिर हम इस क्षेत्र के आधार पर दस्तावेजों को क्रमबद्ध करते हैं।


क्या क्वेरी में एक चर के रूप में आदेश सरणी को संग्रहीत करने का एक तरीका है, इसलिए यदि सरणी बड़ी है तो हमारे पास एक ही सरणी के इस बड़े पैमाने पर दो बार क्वेरी नहीं है?
एथन एसके

27

यदि आप उपयोग नहीं करना चाहते हैं 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); });
});

समान फ़ंक्शन को किसी आइडेंट प्रॉपर्टी की तुलना आइडी 'रिटर्न a.equals (आईडी)?' करने के लिए कैसे की जाती है, जो उस मॉडल के लिए लौटाए गए सभी गुणों को रखता है?
लोबेल

1
@ लॉबेल का यह मतलब नहीं था कि वह चालाक :-) है, लेकिन उसने काम किया क्योंकि वह डोकलाम Document#equalsके _idक्षेत्र की तुलना करने के लिए मोंगोसे का उपयोग कर रहा था । _idतुलना स्पष्ट करने के लिए अद्यतन किया गया । पूछने के लिए धन्यवाद।
जॉनीएचके

6

करने के लिए इसी तरह के JonnyHK के समाधान, आप दस्तावेज़ से लौटे क्रम बदल सकते हैं findअपने ग्राहक में के संयोजन के साथ (यदि आपके ग्राहक जावास्क्रिप्ट में है) mapऔर Array.prototype.findECMAScript 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);
        });
    });

});

नोटों की एक जोड़ी:

  • उपरोक्त कोड Mongo Node ड्राइवर का उपयोग कर रहा है न कि Mongoose का
  • की idArrayएक सरणी हैObjectId
  • मैंने इस पद्धति के प्रदर्शन को बनाम सॉर्ट नहीं किया है, लेकिन यदि आपको प्रत्येक लौटी हुई वस्तु (जो बहुत सामान्य है) में हेरफेर करने की आवश्यकता है, तो आप इसे mapकॉलबैक में अपने कोड को सरल बनाने के लिए कर सकते हैं ।

रनिंग टाइम O (n * n) है, जैसे कि आंतरिक findके प्रत्येक तत्व के लिए एरे को ट्रैवर्स करता है (बाहरी से map)। यह बहुत ही अयोग्य है, क्योंकि लुकअप टेबल का उपयोग करके ओ (एन) समाधान है।
कर्ण

5

मुझे पता है कि यह सवाल 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

5

मानगो रिटर्न के बाद परिणाम ऑर्डर करने का एक आसान तरीका यह है कि आईडी के साथ एक ऑब्जेक्ट को कुंजी के रूप में बनाया जाए और फिर दिए गए _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
}

1
यह वही है जो मुझे चाहिए और शीर्ष टिप्पणी की तुलना में बहुत सरल है।
डाइरब्राउट

@dyarbrough यह समाधान केवल उन प्रश्नों के लिए काम करता है जो सभी दस्तावेजों (बिना सीमा या छोड़ें) को प्राप्त करते हैं। शीर्ष टिप्पणी अधिक जटिल है लेकिन हर परिदृश्य के लिए काम करती है।
marian2js

3

हमेशा? कभी नहीँ। आदेश हमेशा समान होता है: अपरिभाषित (शायद भौतिक क्रम जिसमें दस्तावेज़ संग्रहीत हैं)। जब तक आप इसे क्रमबद्ध नहीं करते।


$naturalक्रम सामान्य रूप से जो भौतिक के बजाय तार्किक है
सम्मे

1

मुझे पता है कि यह एक पुराना धागा है, लेकिन अगर आप सरणी में केवल आईडी का मान लौटा रहे हैं, तो आपको इस वाक्यविन्यास का विकल्प चुनना पड़ सकता है। जैसा कि मैं एक 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 ()' रैपर को शामिल किए बिना - केवल मान?



0

मानगो से परिणाम प्राप्त होने के बाद यह एक कोड समाधान है। इंडेक्स को स्टोर करने के लिए मैप का उपयोग करना और फिर मानों की अदला-बदली करना।

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++
        }
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.