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

Trovare due documenti in MongoDB che condividono un valore chiave

Mentre resto in attesa di commenti che non penso che il modo in cui stai formulando la tua domanda sia effettivamente correlato a un problema specifico che hai, andrò in qualche modo a spiegare il modo idiomatico di SQL in un tipo di soluzione MongoDB. Sono convinto che la tua soluzione attuale sarebbe diversa, ma non ci hai presentato quel problema, ma solo SQL.

Quindi considera i seguenti documenti come un set di esempio, rimuovendo i campi _id in questo elenco per chiarezza:

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }
{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }
{ "name" : "z", "type" : "z" }

Se eseguissimo l'SQL presentato sugli stessi dati otterremmo questo risultato:

a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c

Possiamo vedere che 2 documenti non corrispondono e quindi elaborare la logica dell'operazione SQL. Quindi l'altro modo di dirlo è "Quali documenti hanno una chiave di "nome" do averne più di uno possibile valore nella chiave "tipo".

Dato che, adottando un approccio mongo, possiamo interrogare gli elementi che non corrispondere alla condizione data. Quindi effettivamente il rovescio del risultato:

db.sample.aggregate([

    // Store unique documents grouped by the "name"
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type" 
            }
        } 
    }},

    // Unwind the "set" results
    {$unwind: "$comp"},

    // Push the results back to get the unique count
    // *note* you could not have done this with alongside $addtoSet
    {$group: {
        _id: "$_id",
        comp: {
            $push: { 
                name: "$comp.name",
                type: "$comp.type" 
            }
        },
        count: {$sum: 1} 
    }},

    // Match only what was counted once
    {$match: {count: 1}},

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

    // Clean up to "name" and "type" only
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}

])

Questa operazione produrrà i risultati:

{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }

Ora, per ottenere lo stesso risultato della query SQL, prenderemo quei risultati e li canalizzeremo in un'altra query:

db.sample.find({$nor: [{ name: "f", type: "e"},{ name: "z", type: "z"}] })

Che arriva come risultato finale della corrispondenza:

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }

Quindi funzionerà, tuttavia l'unica cosa che potrebbe renderlo impraticabile è dove il numero di documenti da confrontare è molto grande, abbiamo raggiunto un limite di lavoro per compattare quei risultati fino a un array.

Soffre anche un po' dell'uso di un negativo nell'operazione di ritrovamento finale che forzerebbe una scansione della collezione. Ma in tutta franchezza si potrebbe dire lo stesso della query SQL che utilizza lo stesso negativo premessa.

Modifica

Ovviamente quello che non ho menzionato è che se il set di risultati va al contrario e stai abbinando di più risulta negli elementi esclusi dall'aggregato, quindi è sufficiente invertire la logica per ottenere le chiavi desiderate. Cambia semplicemente $match come segue:

{$match: {$gt: 1}}

E quello sarà il risultato, forse non i documenti veri ma è un risultato. Quindi non hai bisogno di un'altra query per abbinare i casi negativi.

E, alla fine, è stata colpa mia perché ero così concentrato sulla traduzione idiomatica che non ho letto l'ultima riga della tua domanda, dove fare dì che stavi cercando uno documento.

Naturalmente, attualmente se la dimensione del risultato è maggiore di 16 MB, sei bloccato. Almeno fino al 2.6 release, dove i risultati delle operazioni di aggregazione sono un cursore , quindi puoi iterarlo come un .find() .

Introdotto anche in 2.6 è il $size operatore che viene utilizzato per trovare la dimensione di un array nel documento. Quindi questo aiuterebbe a rimuovere il secondo $unwind e $group che vengono utilizzati per ottenere la lunghezza del set. Questo altera la query in una forma più veloce:

db.sample.aggregate([
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type"
            }
        } 
    }},
    {$project: { 
        comp: 1,
        count: {$size: "$comp"} 
    }},
    {$match: {count: {$gt: 1}}},
    {$unwind: "$comp"},
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}
])

E MongoDB 2.6.0-rc0 è attualmente disponibile se lo fai solo per uso personale o sviluppo/test.

Morale della storia. Sì, puoi fallo, Ma davvero vuoi o bisogno farlo in quel modo? Quindi probabilmente no, e se hai posto una domanda diversa sul caso aziendale specifico, potresti ottenere una risposta diversa. Ma poi di nuovo questo potrebbe essere esattamente quello che vuoi.

Nota

Vale la pena ricordare che quando guardi i risultati dell'SQL, erroneamente duplica diversi articoli a causa delle altre opzioni di tipo disponibili se non hai utilizzato un DISTINCT per quei valori o essenzialmente un altro raggruppamento. Ma questo è il risultato che veniva prodotto da questo processo usando MongoDB.

Per Alessandro

Questo è l'output dell'aggregato nella shell dalle attuali versioni 2.4.x:

{
    "result" : [
            {
                    "name" : "f",
                    "type" : "e"
            },
            {
                    "name" : "z",
                    "type" : "z"
            }
    ],
    "ok" : 1
}

Quindi fallo per far passare una var come argomento alla condizione $nor nella seconda ricerca, in questo modo:

var cond = db.sample.aggregate([ .....

db.sample.find({$nor: cond.result })

E dovresti ottenere gli stessi risultati. Altrimenti consulta il tuo autista.