sql >> Databasteknik >  >> NoSQL >> MongoDB

Hitta två dokument i MongoDB som delar ett nyckelvärde

Medan jag står vid sidan av kommentarer om att jag inte tror att hur du formulerar din fråga faktiskt är relaterad till ett specifikt problem du har, kommer jag att gå på något sätt för att förklara det idiomatiska SQL-sättet i en MongoDB-typ av lösning. Jag håller fast vid att din faktiska lösning skulle vara annorlunda men du har inte presenterat det problemet för oss, utan bara SQL.

Så betrakta följande dokument som en exempeluppsättning, ta bort _id-fälten i denna lista för tydlighetens skull:

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

Om vi ​​körde SQL som presenterades över samma data skulle vi få detta resultat:

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

Vi kan se att 2 dokument inte stämmer överens, och sedan räkna ut logiken i SQL-operationen. Så det andra sättet att säga det är "Vilka dokument med nyckeln "namn" gör har mer än en möjligt värde i nyckeln "typ".

Med tanke på att vi, med en mongo-strategi, kan fråga efter de artiklar som inte matcha det givna villkoret. Så effektivt omvänt av resultatet:

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

])

Denna operation kommer att ge resultaten:

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

För att nu få samma resultat som SQL-frågan skulle vi ta dessa resultat och kanalisera dem till en annan fråga:

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

Som kommer som det slutliga matchningsresultatet:

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

Så det här kommer att fungera, men det enda som kan göra detta opraktiskt är var antalet dokument som jämförs är mycket stor, träffar vi en arbetsgräns för att komprimera dessa resultat till en array.

Det lider också lite av användningen av en negativ i den sista fyndoperationen som skulle tvinga fram en skanning av samlingen. Men i ärlighetens namn kan samma sak sägas om SQL-frågan som använder samma negativa premiss.

Redigera

Det jag naturligtvis inte nämnde är att om resultatuppsättningen går åt andra hållet och du matchar mer resulterar i de exkluderade objekten från aggregatet, vänd sedan om logiken för att få nycklarna du vill ha. Ändra helt enkelt $match enligt följande:

{$match: {$gt: 1}}

Och det blir resultatet, kanske inte själva dokumenten men det är ett resultat. Så du behöver inte en annan fråga för att matcha negativa fall.

Och i slutändan var detta mitt fel eftersom jag var så fokuserad på den idiomatiska översättningen att jag inte läste sista raden i din fråga, var du gör säg att du letade efter en dokument.

Naturligtvis, för närvarande om den resultatstorleken är större än 16 MB så har du fastnat. Åtminstone tills 2.6 release, där resultaten av aggregeringsoperationer är en markör , så du kan upprepa det som en .find() .

Infördes även i 2.6 är $size operator som används för att hitta storleken på en array i dokumentet. Så detta skulle hjälpa till att ta bort den andra $unwind och $group som används för att få längden på setet. Detta ändrar frågan till en snabbare form:

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"}}
])

Och MongoDB 2.6.0-rc0 är för närvarande tillgängligt om du gör detta bara för personligt bruk eller utveckling/testning.

Sensmoralen i historien. Ja du kan gör det, Men gör du verkligen vill eller behöver att göra på det sättet? Då förmodligen inte, och om du ställde en annan fråga om det specifika affärsfallet kan du få ett annat svar. Men då kan det här vara helt rätt för vad du vill ha.

Obs

Värt att nämna att när du tittar på resultaten från SQL, kommer den felaktigt att duplicera flera objekt på grund av de andra tillgängliga typalternativen om du inte använde en DISTINCT för dessa värderingar eller i huvudsak en annan grupp. Men det är resultatet som producerades av den här processen med MongoDB.

För Alexander

Detta är utdata från aggregatet i skalet från nuvarande 2.4.x-versioner:

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

Så gör detta för att få en var att passera som argument till $nor-villkoret i det andra fyndet, så här:

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

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

Och du bör få samma resultat. Kontakta annars din förare.



  1. Kapslad objekttextsökning i mongoDB

  2. Meteor $och med $or

  3. Kan inte lösa Assembly- eller Windows-metadatafilen 'System.Data.dll'

  4. Installera Apache CouchDB på CentOS 8