sql >> Databasteknik >  >> NoSQL >> MongoDB

Matcha minst N element i en array till en lista med villkor

Din fråga har två möjligheter för mig, men kanske någon förklaring för att komma igång.

Först och främst måste jag förklara för dig att du missförstår avsikten med $elemMatch och det missbrukas i det här fallet.

Idén med $elemMatch är att skapa ett "frågedokument" som faktiskt tillämpas på elementen i arrayen. Avsikten är att du har "flera villkor" på ett dokument inom arrayen för att matcha det diskret inom medlemsdokumentet, och inte inom hela arrayen av det yttre dokumentet. dvs:

{
   "data": [
       { "a": 1, "b": 3 },
       { "a": 2, "b": 2 }
   ]
}

Och följande fråga kommer att fungera, även om inget enskilt element i den arrayen matchar, men hela dokumentet gör det:

db.collection.find({ "data.a": 1, "data.b": 2 })

Men för att kontrollera om ett faktiskt element matchar båda dessa villkor är det här du använder $elemMatch :

db.collection.find({ "data": { "a": 1, "b": 2 } })

Så ingen matchning i det provet, och det kommer bara att matcha där ett specifikt arrayelement hade båda dessa element.

Nu har vi $elemMatch förklarat, här är din förenklade fråga:

db.collection.find({ "tracks.artist": { "$in": arr } })

Mycket enklare, och det fungerar genom att titta på alla arraymedlemmar med ett enda fält och returnera där ett element i dokumentet innehåller minst ett av dessa möjliga resultat.

Men inte vad du frågar, så vidare med din fråga. Om du läser igenom det sista påståendet bör du inse att $in är faktiskt en $or skick. Det är bara en förkortad form för att fråga "eller" över samma element i dokumentet.

Med det i åtanke är kärnan i det du frågar efter ett "och" operation där alla "tre" värden finns. Om du antar att du bara skickade "tre" objekt i testet så kan du använda en form av $and som är i den förkortade formen $all :

db.collection.find({ "tracks.artist": { "$all": arr } })

Det skulle bara returnera de dokument som hade elementet inom medlemmarna i den arrayen som matchade "alla" de element som specificerades i testvillkoret. Det kan mycket väl vara vad du vill, men det finns ett fall där du naturligtvis vill specificera en lista med säg "fyra eller fler" artister att testa och bara vill ha "tre" eller något mindre antal av det, i så fall en $all operatören är för kortfattad.

Men det finns ett logiskt sätt att lösa detta, det krävs bara lite mer bearbetning med operatörer som inte är tillgängliga för grundläggande frågor men som är tillgängliga för aggregationsramverk :

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

Det första steget implementerar en standardfråga $match villkor för att filtrera dokumenten till endast de som "sannolikt" matchar villkoren. Det logiska fallet här är att använda $in som tidigare kommer den att hitta de dokument där minst ett av elementen som finns i din "test"-array finns inom minst ett av medlemsfälten i dokumentets egen array.

Nästa klausul är något du helst bör bygga i kod eftersom den relaterar till "längden" på arrayen. Tanken här är där du vill ha minst "tre" matchningar, då måste arrayen du testar i dokumentet ha minst "tre" element för att uppfylla det, så det är ingen idé att hämta dokument med "två" eller färre arrayelement eftersom de aldrig kan matcha "tre".

Eftersom alla MongoDB-frågor i huvudsak bara är en representation av en datastruktur, gör det detta mycket enkelt att bygga. dvs för JavaScript:

var matchCount = 3;    // how many matches we want

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

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

Logiken där är att "dot notation"-formen med $finns testar förekomsten av ett element vid det angivna indexet ( n-1 ), och det måste finnas där för att arrayen åtminstone ska vara av den längden.

Resten av avgränsningen använder helst $ setIntersection metod för att returnera de matchade elementen mellan den faktiska arrayen och den testade arrayen. Eftersom arrayen i dokumentet inte matchar strukturen för "test array" måste den transformeras via $map operation som är inställd för att endast returnera "artist"-fältet från varje arrayelement.

När "korsningen" mellan dessa två arrayer görs testas den slutligen för $size av den resulterande listan över vanliga element där testet tillämpas för att se att "minst tre" av dessa element befanns vara gemensamma.

Slutligen "filtrerar du bara bort" allt som inte var sant med en $match skick.

Helst använder du MongoDB 2.6 eller senare för att ha dessa operatörer tillgängliga. För de tidigare versionerna av 2.2.x och 2.4.x är det fortfarande möjligt, men bara lite mer arbete och bearbetningskostnader:

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



  1. Om Mongo $lookup är en vänster yttre koppling, hur kommer det sig då att den utesluter icke-matchande dokument?

  2. Skicka data från NodeJS tillbaka till samma HTML-sida efter formuläret

  3. MongoDB hittar där nyckel är lika med sträng från array

  4. GraphQL-fel som returnerar resultatet av mongoose-aggregatkommandot