Flere JOINS i en enkelt forespørgsel
Multiple JOINS er normalt forbundet med flere samlinger, men du skal have en grundlæggende forståelse af, hvordan INNER JOIN fungerer (se mine tidligere indlæg om dette emne). Udover vores to kollektioner vi havde før; enheder og studerende, lad os tilføje en tredje samling og mærke den sport. Udfyld sportssamlingen med dataene nedenfor:
{
"_id" : 1,"tournamentsPlayed" : 6,
"gamesParticipated" : [{"hockey" : "midfielder","football" : "stricker","handball" : "goalkeeper"}],
"sportPlaces" : ["Stafford Bridge","South Africa", "Rio Brazil"]
}
{
"_id" : 2,"tournamentsPlayed" : 3,
"gamesParticipated" : [{"hockey" : "goalkeeper","football" : "stricker", "handball" : "midfielder"}],
"sportPlaces" : ["Ukraine","India", "Argentina"]
}
{
"_id" : 3,"tournamentsPlayed" : 10,
"gamesParticipated" : [{"hockey" : "stricker","football" : "goalkeeper","tabletennis" : "doublePlayer"}],
"sportPlaces" : ["China","Korea","France"]
}
Vi vil for eksempel gerne returnere alle data for en elev med _id feltværdi lig med 1. Normalt ville vi skrive en forespørgsel for at hente til _id feltværdien fra elevsamlingen og derefter bruge den returnerede værdi til at forespørge efter data i de to andre samlinger. Derfor vil dette ikke være den bedste løsning, især hvis et stort sæt dokumenter er involveret. En bedre tilgang ville være at bruge Studio3T-programmet SQL-funktion. Vi kan forespørge på vores MongoDB med det normale SQL-koncept og derefter prøve at groft tune den resulterende Mongo-shell-kode, så den passer til vores specifikation. Lad os f.eks. hente alle data med _id lig med 1 fra alle samlingerne:
SELECT *
FROM students
INNER JOIN units
ON students._id = units._id
INNER JOIN sports
ON students._id = sports._id
WHERE students._id = 1;
Det resulterende dokument vil være:
{
"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"}
},
"sports" : {
"_id" : NumberInt(1),"tournamentsPlayed" : NumberInt(6),
"gamesParticipated" : [{"hockey" : "midfielder", "football" : "striker","handball" : "goalkeeper"}],
"sportPlaces" : ["Stafford Bridge","South Africa","Rio Brazil"]
}
}
Fra fanen Forespørgselskode vil den korresponderende MongoDB-kode være:
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}},
{ "$lookup" : {"localField" : "students._id","from" : "sports", "foreignField" : "_id","as" : "sports"}},
{ "$unwind" : {"path" : "$sports", "preserveNullAndEmptyArrays" : false}},
{ "$match" : {"students._id" : NumberLong(1)}}
]
);
Når jeg ser på det returnerede dokument, er jeg personligt ikke så tilfreds med datastrukturen, især med indlejrede dokumenter. Som du kan se, er der returneret _id-felter, og for enhederne har vi muligvis ikke brug for, at karakterfeltet er indlejret i enhederne.
Vi vil gerne have et enhedsfelt med indlejrede enheder og ikke andre felter. Dette fører os til den grove melodi-del. Ligesom i de tidligere indlæg, kopier koden ved hjælp af det medfølgende kopi-ikon og gå til samlingsruden, indsæt indholdet ved hjælp af indsæt-ikonet.
Først og fremmest skal $match-operatoren være den første fase, så flyt den til den første position og få noget som dette:
Klik på fanen første fase, og rediger forespørgslen til:
{
"_id" : NumberLong(1)
}
Vi skal derefter ændre forespørgslen yderligere for at fjerne mange indlejringsstadier af vores data. For at gøre det tilføjer vi nye felter for at fange data for de felter, vi ønsker at fjerne, dvs.:
db.getCollection("students").aggregate(
[
{ "$project" : { "_id" : NumberInt(0), "students" : "$$ROOT"}},
{ "$match" : {"students._id" : NumberLong(1)}},
{ "$lookup" : { "localField" : "students._id", "from" : "units","foreignField" : "_id", "as" : "units"}},
{ "$addFields" : { "_id": "$students._id","units" : "$units.grades"}},
{ "$unwind" : { "path" : "$units", "preserveNullAndEmptyArrays" : false}},
{ "$lookup" : {"localField" : "students._id", "from" : "sports", "foreignField" : "_id", "as" : "sports"}},
{ "$unwind" : { "path" : "$sports","preserveNullAndEmptyArrays" : false}},
{ "$project" : {"sports._id" : 0.0}}
]
);
Som du kan se, har vi i finjusteringsprocessen introduceret nye feltenheder, som vil overskrive indholdet af den tidligere aggregeringspipeline med karakterer som et indlejret felt. Yderligere har vi lavet et _id-felt for at indikere, at dataene var i relation til eventuelle dokumenter i samlingerne med samme værdi. Det sidste $project-stadium er at fjerne _id-feltet i sportsdokumentet, så vi kan have pænt præsenteret data som nedenfor.
{ "_id" : NumberInt(1),
"students" : {"name" : "James Washington", "age" : 15.0, "grade" : "A", "score" : 10.5},
"units" : {"Maths" : "A","English" : "A", "Science" : "A","History" : "B"},
"sports" : {
"tournamentsPlayed" : NumberInt(6),
"gamesParticipated" : [{"hockey" : "midfielder","football" : "striker","handball" : "goalkeeper"}],
"sportPlaces" : ["Stafford Bridge", "South Africa", "Rio Brazil"]
}
}
Vi kan også begrænse, hvilke felter der skal returneres fra SQL-synspunktet. For eksempel kan vi returnere elevens navn, enheder, denne elev laver, og antallet af turneringer, der spilles med flere JOINS med koden nedenfor:
SELECT students.name, units.grades, sports.tournamentsPlayed
FROM students
INNER JOIN units
ON students._id = units._id
INNER JOIN sports
ON students._id = sports._id
WHERE students._id = 1;
Dette giver os ikke det mest passende resultat. Så som sædvanlig, kopier det og indsæt i samlingsruden. Vi finjusterer med koden nedenfor for at få det rigtige resultat.
db.getCollection("students").aggregate(
[
{ "$project" : { "_id" : NumberInt(0), "students" : "$$ROOT"}},
{ "$match" : {"students._id" : NumberLong(1)}},
{ "$lookup" : { "localField" : "students._id", "from" : "units","foreignField" : "_id", "as" : "units"}},
{ "$addFields" : {"units" : "$units.grades"}},
{ "$unwind" : { "path" : "$units", "preserveNullAndEmptyArrays" : false}},
{ "$lookup" : {"localField" : "students._id", "from" : "sports", "foreignField" : "_id", "as" : "sports"}},
{ "$unwind" : { "path" : "$sports","preserveNullAndEmptyArrays" : false}},
{ "$project" : {"name" : "$students.name", "grades" : "$units.grades", "tournamentsPlayed" : "$sports.tournamentsPlayed"}
}}
]
);
Dette aggregeringsresultat fra SQL JOIN-konceptet giver os en pæn og præsentabel datastruktur vist nedenfor.
{
"name" : "James Washington",
"grades" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"},
"tournamentsPlayed" : NumberInt(6)
}
Ret simpelt, ikke? Dataene er ret præsentable, som om de var gemt i en enkelt samling som et enkelt dokument.
VENSTRE YDRE JOIN
LEFT OUTER JOIN bruges normalt til at vise dokumenter, der ikke stemmer overens med det mest portrætterede forhold. Det resulterende sæt af en LEFT OUTER join indeholder alle rækker fra begge samlinger, der opfylder WHERE-sætningskriterierne, det samme som et INNER JOIN resultatsæt. Desuden vil alle dokumenter fra den venstre samling, der ikke har matchende dokumenter i den højre samling, også blive inkluderet i resultatsættet. De felter, der vælges fra den højre sidetabel, vil returnere NULL-værdier. Alle dokumenter i den højre samling, som ikke har matchende kriterier fra den venstre samling, returneres dog ikke.
Tag et kig på disse to kollektioner:
studerende
{"_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" : 4,"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5}
Enheder
{"_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"}
I elevsamlingen har vi ikke _id feltværdi sat til 3, men i enhedssamlingen har vi. Ligeledes er der ingen _id-feltværdi 4 i i enhedssamlingen. Hvis vi bruger elevsamlingen som vores venstre mulighed i JOIN-tilgangen med forespørgslen nedenfor:
SELECT *
FROM students
LEFT OUTER JOIN units
ON students._id = units._id
Med denne kode får vi følgende resultat:
{
"students" : {"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5},
"units" : {"_id" : 1,"grades" : {"Maths" : "A","English" : "A", "Science" : "A","History" : "B"}}
}
{
"students" : {"_id" : 2,"name" : "Clinton Ariango", "age" : 14,"grade" : "B", "score" : 7.5 }
}
{
"students" : {"_id" : 3,"name" : "Mary Muthoni","age" : 16,"grade" : "A","score" : 11.5},
"units" : {"_id" : 3,"grades" : {"Maths" : "A","English" : "A","Science" : "A","History" : "A"}}
}
Det andet dokument har ikke enhedsfeltet, fordi der ikke var noget matchende dokument i enhedssamlingen. For denne SQL-forespørgsel vil den korresponderende Mongo-kode være
db.getCollection("students").aggregate(
[
{
"$project" : {"_id" : NumberInt(0), "students" : "$$ROOT"}},
{
"$lookup" : {"localField" : "students._id", "from" : "units", "foreignField" : "_id", "as" : "units"}
},
{
"$unwind" : { "path" : "$units", "preserveNullAndEmptyArrays" : true}
}
]
);
Vi har selvfølgelig lært om finjustering, så du kan gå videre og omstrukturere aggregeringspipelinen, så den passer til det slutresultat, du gerne vil have. SQL er et meget kraftfuldt værktøj, hvad angår databasestyring. Det er et bredt emne i sig selv, du kan også prøve at bruge IN og GROUP BY klausulerne for at få korrespondentkoden til MongoDB og se, hvordan det virker.
Konklusion
At vænne sig til en ny (database)teknologi udover den, man er vant til at arbejde med, kan tage meget tid. Relationelle databaser er stadig mere almindelige end de ikke-relationelle. Ikke desto mindre, med introduktionen af MongoDB, har tingene ændret sig, og folk vil gerne lære det så hurtigt som muligt på grund af dens tilknyttede kraftfulde ydeevne.
At lære MongoDB fra bunden kan være lidt kedeligt, men vi kan bruge viden om SQL til at manipulere data i MongoDB, hente den relative MongoDB-kode og finjustere den for at få de mest passende resultater. Et af de værktøjer, der er tilgængelige for at forbedre dette, er Studio 3T. Det tilbyder to vigtige funktioner, der letter driften af komplekse data, det vil sige:SQL-forespørgselsfunktion og Aggregation-editoren. Finjustering af forespørgsler vil ikke kun sikre, at du får det bedste resultat, men også forbedre ydeevnen i form af tidsbesparelse.