MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Query di intersezione di array nidificati MongoDB

Ci sono un paio di modi per farlo usando il framework di aggregazione

Solo un semplice insieme di dati per esempio:

{
    "_id" : ObjectId("538181738d6bd23253654690"),
    "movies": [
        { "_id": 1, "rating": 5 },
        { "_id": 2, "rating": 6 },
        { "_id": 3, "rating": 7 }
    ]
},
{
    "_id" : ObjectId("538181738d6bd23253654691"),
    "movies": [
        { "_id": 1, "rating": 5 },
        { "_id": 4, "rating": 6 },
        { "_id": 2, "rating": 7 }
    ]
},
{
    "_id" : ObjectId("538181738d6bd23253654692"),
    "movies": [
        { "_id": 2, "rating": 5 },
        { "_id": 5, "rating": 6 },
        { "_id": 6, "rating": 7 }
    ]
}

Usando il primo "utente" come esempio, ora vuoi scoprire se qualcuno degli altri due utenti ha almeno due degli stessi film.

Per MongoDB 2.6 e versioni successive puoi semplicemente utilizzare $setIntersection operatore insieme a $size operatore:

db.users.aggregate([

    // Match the possible documents to reduce the working set
    { "$match": {
        "_id": { "$ne": ObjectId("538181738d6bd23253654690") },
        "movies._id": { "$in": [ 1, 2, 3 ] },
        "$and": [
            { "movies": { "$not": { "$size": 1 } } }
        ]
    }},

    // Project a copy of the document if you want to keep more than `_id`
    { "$project": {
        "_id": {
            "_id": "$_id",
            "movies": "$movies"
        },
        "movies": 1,
    }},

    // Unwind the array
    { "$unwind": "$movies" },

    // Build the array back with just `_id` values
    { "$group": {
        "_id": "$_id",
        "movies": { "$push": "$movies._id" }
    }},

    // Find the "set intersection" of the two arrays
    { "$project": {
        "movies": {
            "$size": {
                "$setIntersection": [
                   [ 1, 2, 3 ],
                   "$movies"
                ]
            }
        }
    }},

    // Filter the results to those that actually match
    { "$match": { "movies": { "$gte": 2 } } }

])

Ciò è ancora possibile nelle versioni precedenti di MongoDB che non dispongono di tali operatori, utilizzando solo alcuni passaggi in più:

db.users.aggregate([

    // Match the possible documents to reduce the working set
    { "$match": {
        "_id": { "$ne": ObjectId("538181738d6bd23253654690") },
        "movies._id": { "$in": [ 1, 2, 3 ] },
        "$and": [
            { "movies": { "$not": { "$size": 1 } } }
        ]
    }},

    // Project a copy of the document along with the "set" to match
    { "$project": {
        "_id": {
            "_id": "$_id",
            "movies": "$movies"
        },
        "movies": 1,
        "set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
    }},

    // Unwind both those arrays
    { "$unwind": "$movies" },
    { "$unwind": "$set" },

    // Group back the count where both `_id` values are equal
    { "$group": {
        "_id": "$_id",
        "movies": {
           "$sum": {
               "$cond":[
                   { "$eq": [ "$movies._id", "$set" ] },
                   1,
                   0
               ]
           }
        } 
    }},

    // Filter the results to those that actually match
    { "$match": { "movies": { "$gte": 2 } } }
])

In dettaglio

Potrebbe essere un po' difficile da comprendere, quindi possiamo dare un'occhiata a ogni fase e suddividerle per vedere cosa stanno facendo.

$corrispondenza :Non vuoi operare su tutti i documenti della collezione, quindi questa è un'opportunità per rimuovere gli elementi che non corrispondono eventualmente anche se c'è ancora più lavoro da fare per trovare l'esatto quelli. Quindi le cose più ovvie sono escludere lo stesso "utente" e quindi abbinare solo i documenti che hanno almeno uno degli stessi filmati trovati per quell'"utente".

La prossima cosa che ha senso è considerarlo quando vuoi abbinare n voci quindi solo i documenti che hanno un array "film" che è più grande di n-1 può effettivamente contenere corrispondenze. L'uso di $and qui sembra divertente e non è richiesto in modo specifico, ma se le corrispondenze richieste fossero 4 quindi quella parte effettiva della dichiarazione sarebbe simile a questa:

        "$and": [
            { "movies": { "$not": { "$size": 1 } } },
            { "movies": { "$not": { "$size": 2 } } },
            { "movies": { "$not": { "$size": 3 } } }
        ]

Quindi in pratica "escludi" array che non sono probabilmente abbastanza lunghi da avere n partite. Notando qui che questo $size l'operatore nel modulo di query è diverso da $size per il quadro di aggregazione. Non c'è modo, ad esempio, di usarlo con un operatore di disuguaglianza come $gt il suo scopo è quello di corrispondere in modo specifico alla "taglia" richiesta. Quindi questo modulo di query per specificare tutte le dimensioni possibili che sono inferiori a.

$progetto :Ci sono alcuni scopi in questa affermazione, alcuni dei quali differiscono a seconda della versione di MongoDB che hai. In primo luogo, e facoltativamente, una copia del documento viene conservata sotto il _id valore in modo che questi campi non vengano modificati dal resto dei passaggi. L'altra parte qui è mantenere l'array "film" nella parte superiore del documento come copia per la fase successiva.

Quello che sta succedendo anche nella versione presentata per le versioni precedenti alla 2.6 è che c'è un array aggiuntivo che rappresenta il _id valori per i "film" da abbinare. L'utilizzo di $cond operatore qui è solo un modo per creare una rappresentazione "letterale" dell'array. Abbastanza divertente, MongoDB 2.6 introduce un operatore noto come $literal per fare esattamente questo senza il modo divertente in cui stiamo usando $cond proprio qui.

$rilassati :Per fare qualsiasi altra cosa, l'array movies deve essere svolto poiché in entrambi i casi è l'unico modo per isolare il _id esistente valori per le voci che devono essere confrontate con il "set". Quindi per la versione precedente alla 2.6 è necessario "rilassare" entrambi gli array presenti.

$gruppo :Per MongoDB 2.6 e versioni successive stai semplicemente raggruppando in un array che contiene solo _id valori dei film con i "rating" rimossi.

Pre 2.6 poiché tutti i valori sono presentati "affiancati" (e con molte duplicazioni) stai facendo un confronto tra i due valori per vedere se sono gli stessi. Dove è true , questo dice a $cond operatore per restituire un valore di 1 o 0 dove la condizione è false . Questo viene ritrasmesso direttamente tramite $sum per sommare il numero di elementi corrispondenti nell'array al "set" richiesto.

$progetto :Dove questa è la parte diversa per MongoDB 2.6 e versioni successive è che dal momento che hai respinto un array di "film" _id valori che stai quindi utilizzando $setIntersection per confrontare direttamente quegli array. Come risultato di ciò è un array contenente gli elementi che sono gli stessi, questo viene quindi racchiuso in un $size per determinare quanti elementi sono stati restituiti in quel set corrispondente.

$corrispondenza :è la fase finale che è stata implementata qui che fa il passo chiaro di abbinare solo quei documenti il ​​cui numero di elementi intersecanti era maggiore o uguale al numero richiesto.

Finale

Fondamentalmente è così che lo fai. Prima della 2.6 è un po' più ingombrante e richiederà un po' più di memoria a causa dell'espansione che viene eseguita duplicando ogni membro dell'array trovato da tutti i possibili valori del set, ma è comunque un modo valido per farlo.

Tutto quello che devi fare è applicarlo con il n maggiore valori corrispondenti per soddisfare le tue condizioni e, naturalmente, assicurati che la tua corrispondenza utente originale abbia il n richiesto possibilità. Altrimenti basta generarlo su n-1 dalla lunghezza dell'array di "film" dell'"utente".