MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

A cláusula $in do MongoDB garante o pedido


Conforme observado, a ordem dos argumentos na matriz de uma cláusula $in não reflete a ordem de como os documentos são recuperados. Isso, obviamente, será a ordem natural ou pela ordem do índice selecionado, conforme mostrado.

Se você precisar preservar essa ordem, terá basicamente duas opções.

Então, digamos que você estava combinando os valores de _id em seus documentos com um array que será passado para o $in como [ 4, 2, 8 ] .

Abordagem usando o Agregado

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

])

Então essa seria a forma expandida. O que basicamente acontece aqui é que assim como o array de valores é passado para $in você também constrói um $cond "aninhado" para testar os valores e atribuir um peso apropriado. Como esse valor de "peso" reflete a ordem dos elementos na matriz, você pode passar esse valor para um estágio de classificação para obter os resultados na ordem necessária.

É claro que você realmente "cria" a instrução do pipeline no código, mais ou menos assim:
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 );

Aborde usando mapReduce


É claro que se tudo isso parece pesado para sua sensibilidade, você pode fazer a mesma coisa usando mapReduce, que parece mais simples, mas provavelmente funcionará um pouco mais devagar.
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;
        }
    }
)

E isso basicamente depende dos valores de "chave" emitidos estarem na "ordem de índice" de como eles ocorrem na matriz de entrada.

Então, essas são essencialmente suas maneiras de manter a ordem de uma lista de entrada para um $in condição onde você já tem essa lista em uma determinada ordem.