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

Match mindst N elementer i en matrix til en liste over betingelser

Dit spørgsmål har to muligheder for mig, men måske en forklaring til at komme i gang.

Først og fremmest skal jeg forklare dig, at du misforstår hensigten med $elemMatch og det er misbrugt i dette tilfælde.

Idéen med $elemMatch er at oprette et "forespørgselsdokument", som faktisk anvendes på elementerne i arrayet. Hensigten er, at du har "flere betingelser" på et dokument i arrayet for at matche det diskret i medlemsdokumentet og ikke inden for hele arrayet af det ydre dokument. dvs.:

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

Og følgende forespørgsel fungerer, selvom intet enkelt element i det array matcher, men hele dokumentet gør det:

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

Men for at kontrollere, om et faktisk element matcher begge disse betingelser, er det her, du bruger $elemMatch :

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

Så ingen match i den prøve, og den vil kun matche, hvor et specifikt array-element havde begge disse elementer.

Nu har vi $elemMatch forklaret, her er din forenklede forespørgsel:

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

Meget mere enkelt, og det fungerer ved at se på alle array-medlemmer efter et enkelt felt og returnere, hvor ethvert element i dokumentet indeholder mindst et af disse mulige resultater.

Men ikke det du spørger om, så videre med dit spørgsmål. Hvis du læser det sidste udsagn igennem, skulle du komme til at indse, at $in er faktisk en $or tilstand. Det er blot en forkortet form til at spørge "eller" over det samme element i dokumentet.

Med det i tankerne er kernen i det, du beder om, et "og" operation, hvor alle "tre" værdier er indeholdt. Hvis du antager, at du kun sendte "tre" elementer i testen, kunne du bruge en form for $og som er i den forkortede form af $all :

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

Det ville kun returnere de dokumenter, der havde elementet inden for medlemmer af det array, der matcher "alle" elementerne specificeret i testbetingelsen. Det kan godt være, hvad du ønsker, men der er tilfældet, hvor du selvfølgelig vil specificere en liste over "fire eller flere" kunstnere, der skal teste, og kun vil have "tre" eller et mindre antal ud af det, i så fald en $all operatoren er for kortfattet.

Men der er en logisk måde at løse dette på, det kræver bare lidt mere behandling med operatører, der ikke er tilgængelige for grundlæggende forespørgsler, men som er tilgængelige for aggregeringsramme :

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ørste trin implementerer en standardforespørgsel $match betingelse for at filtrere dokumenterne til kun dem, der "sandsynligvis" matcher betingelserne. Det logiske tilfælde her er at bruge $in som før vil den finde de dokumenter, hvor mindst et af elementerne i dit "test"-array er til stede i mindst et af medlemsfelterne i dokumentets eget array.

Den næste klausul er noget, du ideelt set bør bygge i kode, da den vedrører "længden" af arrayet. Ideen her er, hvor du vil have mindst "tre" matches, så skal den matrix, du tester i dokumentet, have mindst "tre" elementer for at opfylde det, så det giver ingen mening at hente dokumenter med "to" eller færre matrixelementer da de aldrig kan matche "tre".

Da alle MongoDB-forespørgsler i det væsentlige kun er en repræsentation af en datastruktur, gør det dette meget nemt at bygge. dvs. for JavaScript:

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

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

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

Logikken der er, at "punktnotation"-formen med $ findes tester for tilstedeværelsen af ​​et element ved det specificerede indeks ( n-1 ), og det skal være der for at arrayet i det mindste har den længde.

Resten af ​​indsnævringen bruger ideelt set $ sæt kryds metode for at returnere de matchede elementer mellem det faktiske array og det testede array. Da arrayet i dokumentet ikke matcher strukturen for "test-arrayet", skal det transformeres via $map operation, som er indstillet til kun at returnere "kunstner"-feltet fra hvert array-element.

Efterhånden som "krydset" mellem disse to arrays er lavet, testes det endelig for $størrelse af den resulterende liste over fælles elementer, hvor testen anvendes for at se, at "mindst tre" af disse elementer viste sig at være fælles.

Til sidst "filtrerer du bare alt fra, der ikke var sandt, ved at bruge en $match tilstand.

Ideelt set bruger du MongoDB 2.6 eller nyere for at have disse operatører tilgængelige. For de tidligere versioner af 2.2.x og 2.4.x er det stadig muligt, men bare lidt mere arbejde og behandlingsomkostninger:

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. Kører db.repairDatabase() fra mongodb-native i node.js

  2. Komprimer (forkort) PHP-streng fra 24 tegn til 20

  3. Indstilling af MongoDB-forbindelse med Airflow

  4. Geonear sorter efter afstand og tid