Der er et par måder at gøre dette på ved hjælp af aggregeringsrammen
Bare et simpelt sæt data for eksempel:
{
"_id" : ObjectId("538181738d6bd23253654690"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 2, "rating": 6 },
{ "_id": 3, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654691"),
"movies": [
{ "_id": 1, "rating": 5 },
{ "_id": 4, "rating": 6 },
{ "_id": 2, "rating": 7 }
]
},
{
"_id" : ObjectId("538181738d6bd23253654692"),
"movies": [
{ "_id": 2, "rating": 5 },
{ "_id": 5, "rating": 6 },
{ "_id": 6, "rating": 7 }
]
}
Ved at bruge den første "bruger" som eksempel, vil du nu finde ud af, om nogen af de to andre brugere har mindst to af de samme film.
For MongoDB 2.6 og nyere kan du blot bruge $setIntersection
operatør sammen med $size
operatør:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document if you want to keep more than `_id`
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
}},
// Unwind the array
{ "$unwind": "$movies" },
// Build the array back with just `_id` values
{ "$group": {
"_id": "$_id",
"movies": { "$push": "$movies._id" }
}},
// Find the "set intersection" of the two arrays
{ "$project": {
"movies": {
"$size": {
"$setIntersection": [
[ 1, 2, 3 ],
"$movies"
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
Dette er stadig muligt i tidligere versioner af MongoDB, der ikke har disse operatører, blot ved at bruge et par flere trin:
db.users.aggregate([
// Match the possible documents to reduce the working set
{ "$match": {
"_id": { "$ne": ObjectId("538181738d6bd23253654690") },
"movies._id": { "$in": [ 1, 2, 3 ] },
"$and": [
{ "movies": { "$not": { "$size": 1 } } }
]
}},
// Project a copy of the document along with the "set" to match
{ "$project": {
"_id": {
"_id": "$_id",
"movies": "$movies"
},
"movies": 1,
"set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
}},
// Unwind both those arrays
{ "$unwind": "$movies" },
{ "$unwind": "$set" },
// Group back the count where both `_id` values are equal
{ "$group": {
"_id": "$_id",
"movies": {
"$sum": {
"$cond":[
{ "$eq": [ "$movies._id", "$set" ] },
1,
0
]
}
}
}},
// Filter the results to those that actually match
{ "$match": { "movies": { "$gte": 2 } } }
])
I detaljer
Det kan være lidt at tage fat på, så vi kan tage et kig på hver fase og dele dem ned for at se, hvad de laver.
$match :Du ønsker ikke at operere på alle dokumenter i samlingen, så dette er en mulighed for at fjerne de elementer, der muligvis ikke matcher, selvom der stadig er mere arbejde at gøre for at finde den nøjagtige dem. Så de åbenlyse ting er at ekskludere den samme "bruger" og derefter kun matche de dokumenter, der har mindst en af de samme film, som blev fundet for den "bruger".
Den næste ting, der giver mening, er at overveje det, når du vil matche n
indgange så kun dokumenter, der har en "film"-array, der er større end n-1
kan muligvis faktisk indeholde tændstikker. Brugen af $and
her ser sjovt ud og er ikke påkrævet specifikt, men hvis de nødvendige matches var 4
så ville den faktiske del af udsagnet se sådan ud:
"$and": [
{ "movies": { "$not": { "$size": 1 } } },
{ "movies": { "$not": { "$size": 2 } } },
{ "movies": { "$not": { "$size": 3 } } }
]
Så du "udelukker" grundlæggende arrays, der muligvis ikke er lange nok til at have n
Tændstikker. Bemærk her, at denne $size
operatoren i forespørgselsformularen er forskellig fra $size
for aggregeringsrammen. Der er for eksempel ingen måde at bruge dette med en ulighedsoperator såsom $gt
er dets formål er specifikt at matche den ønskede "størrelse". Derfor denne forespørgselsformular til at specificere alle de mulige størrelser, der er mindre end.
$project :Der er et par formål med denne erklæring, hvoraf nogle er forskellige afhængigt af den MongoDB-version du har. For det første, og valgfrit, opbevares en dokumentkopi under _id
værdi, så disse felter ikke ændres af resten af trinene. Den anden del her er at holde "film"-arrayet øverst i dokumentet som en kopi til næste trin.
Hvad der også sker i versionen præsenteret for tidligere 2.6 versioner er, at der er et ekstra array, der repræsenterer _id
værdier for at "filmene" matcher. Brugen af $cond
operator her er blot en måde at skabe en "bogstavelig" repræsentation af arrayet. Sjovt nok introducerer MongoDB 2.6 en operator kendt som $literal
at gøre præcis dette uden den sjove måde, vi bruger $cond
lige her.
$unwind :For at gøre noget yderligere skal filmarrayet afvikles, da det i begge tilfælde er den eneste måde at isolere det eksisterende _id
værdier for de poster, der skal matches mod "sættet". Så for den før 2.6 version skal du "afvikle" begge de arrays, der er til stede.
$group :For MongoDB 2.6 og nyere grupperer du bare tilbage til et array, der kun indeholder _id
værdier for filmene med "vurderinger" fjernet.
Før 2.6, da alle værdier præsenteres "side om side" (og med masser af duplikering), laver du en sammenligning af de to værdier for at se, om de er ens. Hvor det er true
, fortæller dette $cond
operator-sætning for at returnere en værdi på 1
eller 0
hvor betingelsen er false
. Dette sendes direkte tilbage gennem $sum
at summere antallet af matchende elementer i arrayet til det nødvendige "sæt".
$project :Hvor dette er den anderledes del for MongoDB 2.6 og nyere er, at siden du har skubbet en række af "filmene" _id
tilbage værdier du så bruger $setIntersection
for direkte at sammenligne disse arrays. Da resultatet af dette er et array, der indeholder de elementer, der er ens, pakkes dette derefter ind i en $size
operatør for at bestemme, hvor mange elementer der blev returneret i det matchende sæt.
$match :Er det sidste trin, der er blevet implementeret her, som gør det klare trin med kun at matche de dokumenter, hvis antal af krydsende elementer var større end eller lig med det påkrævede antal.
Finale
Det er i bund og grund sådan, man gør det. Før 2.6 er en smule mere klodset og vil kræve lidt mere hukommelse på grund af udvidelsen, der udføres ved at duplikere hvert array-medlem, der findes af alle de mulige værdier i sættet, men det er stadig en gyldig måde at gøre dette på.
Alt du skal gøre er at anvende dette med den større n
matchende værdier for at opfylde dine betingelser, og sørg selvfølgelig for, at dit oprindelige brugermatch har det påkrævede n
muligheder. Ellers skal du bare generere dette på n-1
fra længden af "brugerens" række af "film".