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

Corresponder a pelo menos N elementos de uma matriz a uma lista de condições


Sua pergunta tem duas possibilidades para mim, mas talvez alguma explicação para você começar.

Antes de tudo, preciso explicar a você que você não entendeu a intenção de $elemMatch e é mal utilizado neste caso.

A ideia de $elemMatch é criar um "documento de consulta" que é realmente aplicado aos elementos do array. A intenção é onde você tem "múltiplas condições" em um documento dentro da matriz para combiná-lo discretamente no documento membro, e não em toda a matriz do documento externo. ou seja:
{
   "data": [
       { "a": 1, "b": 3 },
       { "a": 2, "b": 2 }
   ]
}

E a consulta a seguir funcionará, mesmo que nenhum elemento único real nessa matriz corresponda, mas o documento inteiro sim:
db.collection.find({ "data.a": 1, "data.b": 2 })

Mas para verificar se um elemento real corresponde a ambas as condições, é aqui que você usa $elemMatch :
db.collection.find({ "data": { "a": 1, "b": 2 } })

Portanto, não há correspondência nessa amostra e ela corresponderá apenas onde um elemento de matriz específico teve ambos os elementos.

Agora temos $elemMatch explicado, aqui está sua consulta simplificada:
db.collection.find({ "tracks.artist": { "$in": arr } })

Muito mais simples, e funciona examinando todos os membros do array por um único campo e retornando onde qualquer elemento do documento contém pelo menos um desses resultados possíveis.

Mas não o que você está perguntando, então continue com sua pergunta. Se você ler essa última declaração, deverá perceber que $in é na verdade um $or doença. É apenas uma forma abreviada para perguntar "ou" sobre o mesmo elemento no documento.

Com isso em mente, o cerne do que você está pedindo é um "e" operação onde todos os "três" valores estão contidos. Supondo que você estivesse enviando apenas "três" itens no teste, você poderia usar um formulário de $e que está na forma abreviada de $all :
db.collection.find({ "tracks.artist": { "$all": arr } })

Isso retornaria apenas os documentos que tinham o elemento dentro dos membros dessa matriz correspondendo a "todos" os elementos especificados na condição de teste. Isso pode muito bem ser o que você deseja, mas há o caso em que, é claro, você deseja especificar uma lista de, digamos, "quatro ou mais" artistas para testar e deseja apenas "três" ou um número menor, nesse caso um $all operador é muito conciso.

Mas existe uma maneira lógica de resolver isso, basta um pouco mais de processamento com operadores não disponíveis para consultas básicas, mas que estão disponíveis para o estrutura de agregação :
var arr = ["A","B","C","D"];     // List for testing

db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Test the array conditions
    { "$project": {
        "user": 1,
        "tracks": 1,                         // any fields you want to keep
        "matched": {
            "$gte": [
                 { "$size": {
                     "$setIntersection": [
                         { "$map": {
                             "input": "$tracks",
                             "as": "t",
                             "in": { "$$t.artist" }
                         }},
                         arr
                     ]
                 }},
                 3
             ]
        }
    }},

    // Filter out anything that did not match
    { "$match": { "matched": true } }
])

O primeiro estágio implementa uma consulta padrão $match condição para filtrar os documentos apenas para aqueles que são "prováveis" de corresponder às condições. O caso lógico aqui é usar $in como antes, ele encontrará os documentos em que pelo menos um dos elementos presentes em seu array "teste" está presente em pelo menos um dos campos de membro no próprio array de documentos.

A próxima cláusula é algo que você deve idealmente construir no código no que se refere ao "comprimento" do array. A ideia aqui é onde você deseja pelo menos "três" correspondências, então a matriz que você está testando no documento deve ter pelo menos "três" elementos para atender a isso, portanto, não faz sentido recuperar documentos com "dois" ou menos elementos da matriz uma vez que eles nunca podem combinar "três".

Como todas as consultas do MongoDB são essencialmente apenas uma representação de uma estrutura de dados, isso facilita muito a construção. ou seja, para JavaScript:
var matchCount = 3;    // how many matches we want

var match1 = { "$match": { "tracks.artist": { "$in": arr } } };

match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };

A lógica é que o formulário de "notação de ponto" com $existe testa a presença de um elemento no índice especificado ( n-1 ), e ele precisa estar lá para que a matriz tenha pelo menos esse comprimento.

O restante do estreitamento idealmente usa o $ setInterseção método para retornar os elementos correspondentes entre a matriz real e a matriz testada. Como o array no documento não corresponde à estrutura do "test array", ele precisa ser transformado por meio do $map operação que está configurada para retornar apenas o campo "artista" de cada elemento da matriz.

À medida que a "interseção" dessas duas matrizes é feita, ela é finalmente testada para o $size dessa lista resultante de elementos comuns onde o teste é aplicado para verificar que "pelo menos três" desses elementos foram encontrados em comum.

Finalmente, você apenas "filtra" qualquer coisa que não seja verdadeira usando um $match doença.

Idealmente, você está usando o MongoDB 2.6 ou superior para ter esses operadores disponíveis. Para as versões anteriores de 2.2.xe 2.4.x, ainda é possível, mas apenas um pouco mais de trabalho e sobrecarga de processamento:
db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Unwind the document array
    { "$unwind": "$tracks" },

    // Filter the content
    { "$match": { "tracks.artist": { "$in": arr } }},

    // Group for distinct values
    { "$group": {
        "_id": { 
           "_id": "$_id",
           "artist": "$tracks.artist"
        }
    }},

    // Make arrays with length
    { "$group": {
        "_id": "$_id._id",
        "artist": { "$push": "$_id.artist" },
        "length": { "$sum": 1 }
    }},

    // Filter out the sizes
    { "$match": { "length": { "$gte": 3 } }}
])