sql >> Database >  >> NoSQL >> MongoDB

Match ten minste N elementen van een array met een lijst met voorwaarden

Je vraag heeft voor mij twee mogelijkheden, maar misschien wat uitleg om je op weg te helpen.

Allereerst moet ik je uitleggen dat je de bedoeling van $elemMatch en het wordt in dit geval misbruikt.

Het idee van $elemMatch is om een ​​"querydocument" te maken dat daadwerkelijk wordt toegepast op de elementen van de array. De bedoeling is dat u "meerdere voorwaarden" op een document binnen de array hebt om het discreet te matchen binnen het liddocument, en niet binnen de hele array van het buitenste document. dat wil zeggen:

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

En de volgende query zal werken, ook al komt geen enkel element in die array overeen, maar het hele document wel:

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

Maar om te controleren of een daadwerkelijk element aan beide voorwaarden voldoet, gebruik je hier $elemMatch :

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

Dus geen overeenkomst in dat voorbeeld, en het komt alleen overeen waar een specifiek array-element beide elementen had.

Nu hebben we $elemMatch uitgelegd, hier is uw vereenvoudigde vraag:

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

Veel eenvoudiger, en het werkt door naar alle arrayleden te kijken met een enkel veld en terug te keren waar een element in het document ten minste één van die mogelijke resultaten bevat.

Maar niet wat je vraagt, ga zo maar door met je vraag. Als je die laatste verklaring doorleest, zou je moeten beseffen dat $in is eigenlijk een $or voorwaarde. Het is gewoon een verkorte vorm om "of" te vragen over hetzelfde element in het document.

Met dat in gedachten, is de kern van wat je vraagt ​​een "en" bewerking waarbij alle "drie" waarden zijn opgenomen. Ervan uitgaande dat u slechts "drie" items in de test verzendt, kunt u een vorm van $en wat in de verkorte vorm is van $all :

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

Dat zou u alleen de documenten retourneren die het element binnen leden van die array hadden die overeenkomen met "alle" elementen die in de testvoorwaarde zijn gespecificeerd. Dat is misschien wel wat je wilt, maar er is een geval waarin je natuurlijk een lijst wilt specificeren van bijvoorbeeld "vier of meer" artiesten om te testen en er slechts "drie" of een kleiner aantal van wilt, in welk geval een $all operator is te kort.

Maar er is een logische manier om dit op te lossen, het kost alleen wat meer verwerking met operators die niet beschikbaar zijn voor basisvragen, maar die beschikbaar zijn voor de aggregatieraamwerk :

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

De eerste fase implementeert een standaardquery $match voorwaarde om de documenten te filteren naar alleen die documenten die "waarschijnlijk" aan de voorwaarden voldoen. Het logische geval hier is om $in zoals eerder zal het die documenten vinden waar ten minste één van de elementen die aanwezig zijn in uw "test"-array aanwezig is in ten minste één van de lidvelden in de eigen array van het document.

De volgende clausule is iets dat u idealiter in code zou moeten bouwen omdat het betrekking heeft op de "lengte" van de array. Het idee hier is waar u ten minste "drie" overeenkomsten wilt, dan moet de array die u in het document test ten minste "drie" elementen hebben om daaraan te voldoen, dus het heeft geen zin om documenten op te halen met "twee" of minder array-elementen omdat ze nooit "drie" kunnen matchen.

Aangezien alle MongoDB-query's in wezen slechts een weergave zijn van een gegevensstructuur, is dit zeer eenvoudig te bouwen. d.w.z. voor JavaScript:

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

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

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

De logica daar is dat de "puntnotatie"-vorm met $bestaat test op de aanwezigheid van een element met de gespecificeerde index ( n-1 ), en het moet aanwezig zijn om de array op zijn minst van die lengte te laten zijn.

De rest van de vernauwing gebruikt idealiter de $ setIntersection methode om de overeenkomende elementen tussen de werkelijke array en de geteste array te retourneren. Aangezien de array in het document niet overeenkomt met de structuur voor de "testarray" moet deze worden getransformeerd via de $map bewerking die is ingesteld om alleen het veld "artiest" van elk array-element te retourneren.

Als de "kruising" van die twee arrays is gemaakt, wordt deze uiteindelijk getest op de $maat van die resulterende lijst van gemeenschappelijke elementen waarbij de test wordt toegepast om te zien dat "ten minste drie" van die elementen gemeenschappelijk bleken te zijn.

Ten slotte "filter" je gewoon alles wat niet waar was met een $match staat.

Idealiter gebruikt u MongoDB 2.6 of hoger om deze operators beschikbaar te hebben. Voor de eerdere versies van 2.2.x en 2.4.x is het nog steeds mogelijk, maar met iets meer werk en verwerkingsoverhead:

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. Maakt django met mongodb migraties tot het verleden?

  2. Hoe de laatste N-records in Mongodb te krijgen?

  3. Vul het geselecteerde veld uit de verzameling en filter volgens de geselecteerde waarde in meteor

  4. Zoek na het bevolken mangoest