JOIN je jednou z klíčových odlišných funkcí mezi databázemi SQL a NoSQL. V SQL databázích můžeme provést JOIN mezi dvěma tabulkami v rámci stejné nebo různých databází. To však není případ MongoDB, protože umožňuje operace JOIN mezi dvěma kolekcemi ve stejné databázi.
Způsob, jakým jsou data prezentována v MongoDB, téměř znemožňuje jejich propojení z jedné kolekce do druhé, s výjimkou použití základních funkcí skriptových dotazů. MongoDB buď denormalizuje data uložením souvisejících položek do samostatného dokumentu, nebo spojí data v nějakém jiném samostatném dokumentu.
Tato data lze spojit pomocí ručních odkazů, jako je pole _id jednoho dokumentu, které je uloženo v jiném dokumentu jako odkaz. Přesto je potřeba provést více dotazů, aby bylo možné načíst některá požadovaná data, což činí proces trochu únavným.
Rozhodli jsme se proto použít koncept JOIN, který usnadňuje vztah dat. Operace JOIN v MongoDB je dosažena pomocí operátoru $lookup, který byl představen ve verzi 3.2.
operátor $lookup
Hlavní myšlenkou konceptu JOIN je získat korelaci mezi daty v jedné kolekci a druhou. Základní syntaxe operátoru $lookup je:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
Ohledně znalostí SQL vždy víme, že výsledkem operace JOIN je samostatný řádek propojující všechna pole z lokální a zahraniční tabulky. Pro MongoDB je to jiný případ v tom, že výsledné dokumenty jsou přidány jako pole místních dokumentů sbírky. Mějme například dvě sbírky; ‚studenti‘ a ‚jednotky‘
studenti
{"_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}
Jednotky
{"_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"}
Jednotky studentů s příslušnými známkami můžeme načíst pomocí operátoru $lookup s přístupem JOIN .i.e
db.getCollection('students').aggregate([{
$lookup:
{
from: "units",
localField: "_id",
foreignField : "_id",
as: "studentUnits"
}
}])
Což nám poskytne níže uvedené výsledky:
{"_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"}]}
Jak již bylo zmíněno dříve, pokud provedeme JOIN pomocí konceptu SQL, vrátíme se se samostatnými dokumenty na platformě Studio3T .i.e
SELECT *
FROM students
INNER JOIN units
ON students._id = units._id
Je ekvivalentem
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
}
}
]
);
Výše uvedený SQL dotaz vrátí výsledky níže:
{ "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"}}
Doba trvání výkonu bude samozřejmě záviset na struktuře vašeho dotazu. Máte-li například mnoho dokumentů v jedné kolekci nad druhou, měli byste provést agregaci z kolekce s menšími dokumenty a poté vyhledat v kolekci s více dokumenty. Tímto způsobem je vyhledání zvoleného pole z menší kolekce dokumentů zcela optimální a zabere méně času než provádění vícenásobného hledání vybraného pole v kolekci s více dokumenty. Je proto vhodné dát na první místo menší kolekci.
U relačních databází na pořadí databází nezáleží, protože většina interpretů SQL má optimalizátory, které mají přístup k dalším informacím pro rozhodování, která by měla být první.
V případě MongoDB budeme muset použít index pro usnadnění operace JOIN. Všichni víme, že všechny dokumenty MongoDB mají klíč _id, který lze pro relační DBM považovat za primární klíč. Index poskytuje větší šanci na snížení množství dat, ke kterým je potřeba přistupovat, kromě podpory operace při použití v cizím klíči $lookup.
V agregačním kanálu, abychom mohli použít index, musíme zajistit, aby $match bylo provedeno jako první, aby se odfiltrovaly dokumenty, které neodpovídají kritériím. Například pokud chceme získat výsledek pro studenta s hodnotou pole _id rovnou 1:
select *
from students
INNER JOIN units
ON students._id = units._id
WHERE students._id = 1;
Ekvivalentní kód MongoDB, který v tomto případě získáte, je:
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) }}
]);
Vrácený výsledek pro dotaz výše bude:
{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
"studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
Když nepoužíváme fázi $match nebo raději ne v první fázi, pokud to zkontrolujeme pomocí funkce vysvětlení, dostaneme také fázi COLLSCAN. Provedení COLLSCAN pro velkou sadu dokumentů obecně zabere hodně času. Rozhodli jsme se proto použít indexové pole, které ve funkci vysvětlení zahrnuje pouze fázi IXSCAN. To druhé má výhodu, protože kontrolujeme index v dokumentech a neprohledáváme všechny dokumenty; nebude trvat dlouho, než se vrátí výsledky. Můžete mít jinou strukturu dat, například:
{ "_id" : NumberInt(1),
"grades" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"
}
}
Můžeme chtít vrátit hodnocení jako různé entity v poli, nikoli jako celé vložené pole hodnocení.
Po napsání SQL dotazu výše musíme upravit výsledný MongoDB kód. Chcete-li to provést, klikněte na ikonu kopírování vpravo, jak je uvedeno níže, a zkopírujte agregační kód:
Dále přejděte na kartu agregace a v zobrazeném podokně je ikona vložení, kliknutím na ni vložte kód.
Kliknutím na řádek $match a poté na zelenou šipku nahoru přesunete scénu nahoru jako první fázi. Nejprve však budete muset ve své kolekci vytvořit index jako:
db.students.createIndex(
{ _id: 1 },
{ name: studentId }
)
Níže získáte ukázku kódu:
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}}
]
Somenines Staňte se MongoDB DBA – Uvedení MongoDB do produkce Zjistěte, co potřebujete vědět, abyste mohli nasadit, monitorovat, spravovat a škálovat MongoDBDdownload zdarma S tímto kódem získáme výsledek níže:
{ "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"}}}
Ale vše, co potřebujeme, je mít známky jako samostatnou entitu dokumentu ve vráceném dokumentu a ne jako výše uvedený příklad. Proto přidáme fázi $addfields, tedy kód, jak je uvedeno níže.
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"} }]
Výsledné dokumenty pak budou:
{
"students" : {"_id" : NumberInt(1), "name" : "James Washington", "grade" : "A","score" : 10.5},
"units" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"}
}
Vrácená data jsou docela úhledná, protože jsme ze sbírky jednotek vyloučili vložené dokumenty jako samostatné pole.
V našem dalším tutoriálu se podíváme na dotazy s několika spojeními.