sql >> Databasteknik >  >> NoSQL >> MongoDB

Hur man går med i ytterligare två samlingar med villkor

Det du saknar här är $lookup producerar en "array" i utdatafältet specificerat av as i sina argument. Detta är det allmänna konceptet för MongoDB "relationer", i det att en "relation" mellan dokument representeras som en "underegenskap" som är "inom" själva dokumentet, och är antingen singular eller en "array" för många.

Eftersom MongoDB är "schemaless", det allmänna antagandet om $lookup är att du menar "många" och resultatet är därför "alltid" en array. Så letar du efter "samma resultat som i SQL" måste du $unwind den arrayen efter $lookup . Om det är "en" eller "många" spelar ingen roll, eftersom det fortfarande "alltid" är en array:

db.getCollection.('tb1').aggregate([
  // Filter conditions from the source collection
  { "$match": { "status": { "$ne": "closed" } }},

  // Do the first join
  { "$lookup": {
    "from": "tb2",
    "localField": "id",
    "foreignField": "profileId",
    "as": "tb2"
  }},

  // $unwind the array to denormalize
  { "$unwind": "$tb2" },

  // Then match on the condtion for tb2
  { "$match": { "tb2.profile_type": "agent" } },

  // join the second additional collection
  { "$lookup": {
    "from": "tb3",
    "localField": "tb2.id",
    "foreignField": "id",
    "as": "tb3"
  }},

  // $unwind again to de-normalize
  { "$unwind": "$tb3" },

  // Now filter the condition on tb3
  { "$match": { "tb3.status": 0 } },

  // Project only wanted fields. In this case, exclude "tb2"
  { "$project": { "tb2": 0 } }
])

Här måste du notera de andra sakerna du saknar i översättningen:

Sekvensen är "viktig"

Aggregationspipelines är mer "uttrycksfulla" än SQL. De anses faktiskt bäst som "en sekvens av steg" tillämpas på datakällan för att sammanställa och transformera data. Den bästa analogen till detta är "piped" kommandoradsinstruktioner, såsom:

ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'

Där "röret" | kan betraktas som ett "pipeline-steg" i en MongoDB-aggregations-"pipeline".

Som sådana vill vi $match för att filtrera saker från "källa"-samlingen som vår första operation. Och detta är generellt sett god praxis eftersom det tar bort alla dokument som inte uppfyllde kraven från ytterligare villkor. Precis som vad som händer i vårt "command line pipe"-exempel, där vi tar "input" och sedan "pipe" till en grep för att "ta bort" eller "filtrera".

Vägarna är viktiga

Där nästa sak du gör här är att "gå med" via $lookup . Resultatet är en "array" av objekten från "from" samlingsargument som matchas av de angivna fälten för att matas ut i "as" "fältsökväg" som en "matris".

Namnet som valts här är viktigt, eftersom nu "dokumentet" från källsamlingen anser att alla objekt från "join" nu finns på den givna vägen. För att göra detta enkelt använder jag samma "samlings" namn som "join" för den nya "sökvägen".

Så från första "join" är utdata till "tb2" och som kommer att hålla alla resultat från den insamlingen. Det finns också en viktig sak att notera med följande sekvens av $unwind och sedan $match , om hur MongoDB faktiskt bearbetar frågan.

Vissa sekvenser spelar "verkligen" roll

Eftersom det "ser ut som" finns det "tre" pipeline-steg, nämligen $lookup sedan $unwind och sedan $match . Men i "faktum" gör MongoDB verkligen något annat, vilket visas i utdata från { "explain": true } läggs till i .aggregate() kommando:

    {
        "$lookup" : {
            "from" : "tb2",
            "as" : "tb2",
            "localField" : "id",
            "foreignField" : "profileId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "profile_type" : {
                    "$eq" : "agent"
                }
            }
        }
    }, 
    {
        "$lookup" : {
            "from" : "tb3",
            "as" : "tb3",
            "localField" : "tb2.id",
            "foreignField" : "id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "status" : {
                    "$eq" : 0.0
                }
            }
        }
    }, 

Så bortsett från den första punkten i "sekvens" som gäller där du behöver sätta $match uttalanden där de behövs och gör "mest nytta", detta blir faktiskt "riktigt viktigt" med begreppet "ansluter sig". Saken att notera här är att våra sekvenser av $lookup sedan $unwind och sedan $match , faktiskt bearbetas av MongoDB som bara $lookup steg, med de andra operationerna "upprullade" till ett pipelinesteg för varje.

Detta är en viktig skillnad mot andra sätt att "filtrera" resultaten som erhålls av $lookup . Eftersom i det här fallet, de faktiska "query"-villkoren på "join" från $match utförs på samlingen för att gå med "innan" resultaten returneras till föräldern.

Detta i kombination med $unwind (som översätts till unwinding ) som visas ovan är hur MongoDB faktiskt hanterar möjligheten att "join" kan resultera i att en mängd innehåll produceras i källdokumentet som gör att det överskrider 16MB BSON-gränsen. Detta skulle bara hända i fall där resultatet som kopplas till är mycket stort, men samma fördel är där "filtret" faktiskt tillämpas, att vara på målsamlingen "innan" resultat returneras.

Det är den typen av hantering som bäst "korrelerar" till samma beteende som en SQL JOIN. Det är därför också det mest effektiva sättet att få resultat från en $lookup där det finns andra villkor att gälla för JOIN förutom bara de "lokala" av "utländska" nyckelvärden.

Observera också att den andra beteendeförändringen är från vad som i huvudsak är en LEFT JOIN utförd av $lookup där "källdokumentet" alltid skulle behållas oavsett förekomsten av ett matchande dokument i "mål"-samlingen. Istället $unwind lägger till detta genom att "kassera" alla resultat från "källan" som inte hade något som matchade "målet" enligt de ytterligare villkoren i $match .

Faktum är att de till och med kasseras i förväg på grund av den underförstådda preserveNullAndEmptyArrays: false som ingår och skulle kassera allt där de "lokala" och "utländska" nycklarna inte ens matchade mellan de två samlingarna. Detta är bra för den här typen av fråga eftersom "join" är avsett att vara "lika" på dessa värden.

Avsluta

Som nämnts tidigare behandlar MongoDB generellt "relationer" mycket annorlunda än hur du skulle använda en "Relational Database" eller RDBMS. Det allmänna konceptet "relationer" är i själva verket att "bädda in" data, antingen som en enskild egenskap eller som en array.

Du kanske faktiskt vill ha sådan utdata, vilket också är en del av anledningen till att det utan $unwind sekvens här utdata från $lookup är faktiskt en "array". Men med $unwind i detta sammanhang är faktiskt det mest effektiva att göra, samt ger en garanti för att den "anslutna" data faktiskt inte orsakar att ovannämnda BSON-gräns överskrids som ett resultat av den "anslutna".

Om du verkligen vill ha matriser av utdata, då är det bästa du kan göra här att använda $group pipeline-steg, och möjligen som flera steg för att "normalisera" och "ångra resultaten" av $unwind

  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}

Där du faktiskt i det här fallet skulle lista alla fält du behövde från "tb1" genom deras egenskapsnamn med $first att bara behålla den "första" förekomsten (i huvudsak upprepad av resultaten av "tb2" och "tb3" unwound ) och sedan $push "detaljen" från "tb3" till en "array" för att representera relationen till "tb1" .

Men den generella formen av aggregeringspipelinen som ges är den exakta representationen av hur resultat skulle erhållas från den ursprungliga SQL, som är "denormaliserad" utdata som ett resultat av "join". Om du vill "normalisera" resultaten igen efter detta är upp till dig.




  1. Kan inte få allowDiskUse:True att fungera med pymongo

  2. Installera Redis på Ubuntu 16.04/18.04

  3. Ändra och spela om MongoDB oplog

  4. Hur man migrerar Redis™-data med Redis-Shake