sql >> Databasteknik >  >> NoSQL >> MongoDB

Länka och skapa MongoDB-anslutningar med SQL:Del 2

JOIN är en av de viktigaste distinkta funktionerna mellan SQL- och NoSQL-databaser. I SQL-databaser kan vi utföra en JOIN mellan två tabeller inom samma eller olika databaser. Detta är dock inte fallet för MongoDB eftersom det tillåter JOIN-operationer mellan två samlingar i samma databas.

Sättet som data presenteras i MongoDB gör det nästan omöjligt att relatera det från en samling till en annan förutom när man använder grundläggande skriptfrågefunktioner. MongoDB avnormaliserar antingen data genom att lagra relaterade objekt i ett separat dokument eller så relaterar det data till något annat separat dokument.

Man skulle kunna relatera dessa data genom att använda manuella referenser såsom _id-fältet för ett dokument som sparas i ett annat dokument som referens. Ändå måste man göra flera frågor för att hämta vissa nödvändiga data, vilket gör processen lite tråkig.

Vi beslutar oss därför för att använda JOIN-konceptet som underlättar relationen mellan data. JOIN-operationen i MongoDB uppnås genom användningen av $lookup-operatorn, som introducerades i version 3.2.

$lookup operator

Huvudtanken bakom JOIN-konceptet är att få korrelation mellan data i en samling till en annan. Den grundläggande syntaxen för operatorn $lookup är:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

När det gäller SQL-kunskapen vet vi alltid att resultatet av en JOIN-operation är en separat rad som länkar alla fält från den lokala och utländska tabellen. För MongoDB är detta ett annat fall genom att resultatdokumenten läggs till som en samling av lokala insamlingsdokument. Låt oss till exempel ha två samlingar; "studenter" och "enheter"

studenter

{"_id" : 1,"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5}

Enheter

{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}
{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}
{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}

Vi kan hämta elevernas enheter med respektive betyg med $lookup-operatorn med JOIN-metoden .i.e

db.getCollection('students').aggregate([{
$lookup:
    {
        from: "units",
        localField: "_id",
        foreignField : "_id",
        as: "studentUnits"
    }
}])

Vilket ger oss resultaten nedan:

{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
    "studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14,"grade" : "B","score" : 7.5,
    "studentUnits" : [{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}]}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16,"grade" : "A","score" : 11.5,
    "studentUnits" : [{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}]}

Som nämnts tidigare, om vi gör en JOIN med SQL-konceptet kommer vi att returneras med separata dokument i Studio3T-plattformen .i.e

SELECT *
  FROM students
    INNER JOIN units
      ON students._id = units._id

Är en motsvarighet till

db.getCollection("students").aggregate(
    [
        { 
            "$project" : {
                "_id" : NumberInt(0), 
                "students" : "$$ROOT"
            }
        }, 
        { 
            "$lookup" : {
                "localField" : "students._id", 
                "from" : "units", 
                "foreignField" : "_id", 
                "as" : "units"
            }
        }, 
        { 
            "$unwind" : {
                "path" : "$units", 
                "preserveNullAndEmptyArrays" : false
            }
        }
    ]
);

Ovanstående SQL-fråga kommer att returnera resultaten nedan:

{ "students" : {"_id" : NumberInt(1),"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}, 
    "units" : {"_id" : NumberInt(1),"Maths" : "A","English" : "A","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(2), "name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5 }, 
    "units" : {"_id" : NumberInt(2),"Maths" : "B","English" : "B","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(3),"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5},
"units" : {"_id" : NumberInt(3),"Maths" : "A","English" : "A","Science" : "A","History" : "A"}}

Prestandavaraktigheten kommer uppenbarligen att vara beroende av strukturen på din fråga. Till exempel, om du har många dokument i en samling framför den andra, bör du göra aggregeringen från samlingen med färre dokument och sedan slå upp i den med fler dokument. På så sätt är en uppslagning av det valda fältet från samlingen av mindre dokument ganska optimal och tar mindre tid än att göra flera uppslagningar för ett valt fält i samlingen med fler dokument. Det är därför lämpligt att sätta den mindre samlingen först.

För en relationsdatabas spelar ordningen på databaserna ingen roll eftersom de flesta SQL-tolkare har optimerare, som har tillgång till extra information för att avgöra vilken som ska vara först.

I fallet med MongoDB kommer vi att behöva använda ett index för att underlätta JOIN-operationen. Vi vet alla att alla MongoDB-dokument har en _id-nyckel som för en relationell DBM kan betraktas som den primära nyckeln. Ett index ger en bättre chans att minska mängden data som behöver nås förutom att stödja operationen när den används i den främmande nyckeln $lookup.

I aggregeringspipelinen, för att använda ett index, måste vi se till att $matchningen görs första steget för att filtrera bort dokument som inte matchar kriterierna. Till exempel om vi vill hämta resultatet för studenten med _id-fältvärdet lika med 1:

select * 
from students 
  INNER JOIN units 
    ON students._id = units._id 
      WHERE students._id = 1;

Den motsvarande MongoDB-koden du får i det här fallet är:

db.getCollection("students").aggregate(
[{"$project" : { "_id" : NumberInt(0), "students" : "$$ROOT" }}, 
     {  "$lookup" : {"localField" : "students._id", "from" : "units",  "foreignField" : "_id",  "as" : "units"} }, 
     { "$unwind" : { "path" : "$units","preserveNullAndEmptyArrays" : false } }, 
      { "$match" : {"students._id" : NumberLong(1) }}
    ]);

Det returnerade resultatet för frågan ovan blir:

{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
    "studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}

När vi inte använder $match-stadiet eller snarare inte i det första steget, om vi kontrollerar med förklara-funktionen, kommer vi att få COLLSCAN-stadiet också inkluderat. Att göra en COLLSCAN för en stor uppsättning dokument tar i allmänhet mycket tid. Vi beslutar oss därmed för att använda ett indexfält som i förklara-funktionen endast involverar IXSCAN-steget. Det sistnämnda har en fördel eftersom vi kontrollerar ett register i dokumenten och inte skannar igenom alla dokument; det kommer inte att ta lång tid att returnera resultaten. Du kan ha en annan datastruktur som:

{    "_id" : NumberInt(1), 
    "grades" : {"Maths" : "A", "English" : "A",  "Science" : "A", "History" : "B"
    }
}

Vi kanske vill returnera betygen som olika enheter i en array snarare än ett helt inbäddat betygsfält.

Efter att ha skrivit SQL-frågan ovan måste vi modifiera den resulterande MongoDB-koden. För att göra det, klicka på kopieringsikonen till höger enligt nedan för att kopiera aggregeringskoden:

Gå sedan till aggregeringsfliken och i den presenterade rutan finns en klistra in-ikon, klicka på den för att klistra in koden.

Klicka på $match-raden och sedan på den gröna uppåtpilen för att flytta scenen till toppen som den första etappen. Du måste dock skapa ett index i din samling först som:

db.students.createIndex(
   { _id: 1 },
   { name: studentId }
)

Du får kodexemplet nedan:

db.getCollection("students").aggregate(
    [{ "$match" : {"_id" : 1.0}},
  { "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}}, 
      { "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}}, 
      { "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}}
    ]
Severalnines Become a MongoDB DBA - Bringing MongoDB to ProductionLäs om vad du behöver veta för att distribuera, övervaka, hantera och skala MongoDBDownload gratis

Med denna kod får vi resultatet nedan:

{ "students" : {"_id" : NumberInt(1), "name" : "James Washington","age" : 15.0,"grade" : "A", "score" : 10.5}, 
    "units" : {"_id" : NumberInt(1), "grades" : {"Maths" : "A", "English" : "A", "Science" : "A",  "History" : "B"}}}

Men allt vi behöver är att ha betygen som en separat dokumentenhet i det returnerade dokumentet och inte som exemplet ovan. Vi kommer därför att lägga till $addfields-steget därav koden enligt nedan.

db.getCollection("students").aggregate(
    [{ "$match" : {"_id" : 1.0}},
  { "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}}, 
      { "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}}, 
      { "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}},
      { "$addFields" : {"units" : "$units.grades"} }]

De resulterande dokumenten blir då:

{
"students" : {"_id" : NumberInt(1), "name" : "James Washington", "grade" : "A","score" : 10.5}, 
     "units" : {"Maths" : "A", "English" : "A",  "Science" : "A", "History" : "B"}
}

De returnerade uppgifterna är ganska snygga, eftersom vi har tagit bort inbäddade dokument från enheternas samling som ett separat fält.

I vår nästa självstudie kommer vi att undersöka frågor med flera kopplingar.


  1. Hantera Mongoose-valideringsfel – var och hur?

  2. Mongodb Explain for Aggregation framework

  3. Hur man atomiskt raderar nycklar som matchar ett mönster med Redis

  4. mongodb TTL tar inte bort dokument