MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Garantiert die $in-Klausel von MongoDB die Reihenfolge

Wie bereits erwähnt, spiegelt die Reihenfolge der Argumente im Array einer $in-Klausel nicht die Reihenfolge wider, in der die Dokumente abgerufen werden. Das ist natürlich die natürliche Reihenfolge oder die ausgewählte Indexreihenfolge wie gezeigt.

Wenn Sie diese Reihenfolge beibehalten müssen, haben Sie grundsätzlich zwei Möglichkeiten.

Nehmen wir also an, Sie haben die Werte von _id abgeglichen in Ihren Dokumenten mit einem Array, das an $in übergeben wird als [ 4, 2, 8 ] .

Ansatz mit Aggregate

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 } }

])

Das wäre also die erweiterte Form. Was hier im Grunde passiert, ist, dass genau wie das Array von Werten an $in übergeben wird Sie konstruieren auch ein "verschachteltes" $cond -Anweisung, um die Werte zu testen und eine entsprechende Gewichtung zuzuweisen. Da dieser "Gewichts"-Wert die Reihenfolge der Elemente im Array widerspiegelt, können Sie diesen Wert dann an eine Sortierstufe übergeben, um Ihre Ergebnisse in der erforderlichen Reihenfolge zu erhalten.

Natürlich "bauen" Sie die Pipeline-Anweisung tatsächlich im Code, etwa so:

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 );

Annäherung mit mapReduce

Wenn Ihnen das alles zu schwer erscheint, können Sie natürlich dasselbe mit mapReduce tun, das einfacher aussieht, aber wahrscheinlich etwas langsamer läuft.

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;
        }
    }
)

Und das hängt im Grunde davon ab, dass die ausgegebenen "Schlüssel"-Werte in der "Indexreihenfolge" sind, wie sie im Eingabe-Array vorkommen.

Das sind also im Wesentlichen Ihre Möglichkeiten, die Reihenfolge einer Eingabeliste zu einem $in beizubehalten Zustand, in dem Sie diese Liste bereits in einer bestimmten Reihenfolge haben.