MongoDB - Aggregation (Agregacija)
Agregacija predstavlja proces u sklopu koga se vrše određene operacije nad dokumentima i vraća obrađen razultat.
Za vršenje agregacije unutar MongoDB koristi se MongoDB metoda aggregate()
.
Sintaksa je db.COLLECTION-NAME.aggregate({PIPELINE-OPTIONS}, {PIPELINE-OPTIONS}, ....)
.
Pre svega treba biti upoznat sa Pipeline (cevovod) konceptom.To predstavlja mogućnost da se izvrše određene operaije nad input-om a zatim da se taj output iskoristi kao input za sledeću komandu i tako dalje. Jednostavno rečeno - nadovezivanje.
"Aggregation Pipeline" je "framework" koji izvršava agregaciju za nas. Kada se koristi Aggregation framework, MongoDB prosledi dokument kroz "pipeline", unutar njega dokument prolazi kroz određene "faze", u svakoj fazi se dokument transformiše na odgovarajući način da bi se na kraju dobio željeno obrađeni rezultat.
Aggregation Pipeline Stages
U sledećoj tabeli prikazane su faze agregacionog "pipeline-a". (Za celokupnu listu posetite MongoDB sajt).
Stage | Opis |
---|---|
$project | Prosleđuje polje sledećoj fazi sa postojećim ili novim poljima. Polja se mogu dodavati dinamičkim. |
$match | Filtrira dokumenta i proseđuje samo ona odgovarajuća sledećoj fazi. |
$limit | Prosleđuje X nemodifikovanih dokumenata i prosleđuje ih sledećoj fazi. X predstavlja broj (limit) dokumenata koji će biti prosleđeni. |
$group | Grupiše određene dokumente i prosleđuje ih sledećoj fazi agregacionog pipeline-a. |
$sort | Menja redosled dokumenata. (Rastući ili opadajući) |
$sum | Sabiranje numeričkih vrednosti. |
$lookup | Izvršava "left out join" sa drugom kolekcijom unutar iste baze podataka. |
Zadatak 32 - Prikazati sve studente kolekcije student koji studiraju na smeru "ISiT" (koristeći aggregation)
U ovakvim slučajevima kada je potrebno filtrirati datu i proslediti isfiltrirani deo u sledeću fazu agregacionog "pipeline-a" koristimo $match
. $match
filtrira datu i prosleđuje odgovarajuću datu dalje.
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$match":{"smer":"ISiT"}}]).pretty()
{
"_id" : ObjectId("5902012ebb26ec62d0b6bd2e"),
"student_id" : "2012/0161",
"ime" : "Daniel Kostic",
"smer" : "ISiT",
"polozeni_ispiti" : [
{
"predmet" : "AROS",
"ocena" : "6",
"datumPolaganja" : "4.3.2013",
"semestar" : "III"
},
{
"predmet" : "Strukture podataka i algoritmi",
"ocena" : "9",
"datumPolaganja" : "13.4.2014",
"semestar" : "IV"
},
{
"predmet" : "PIS",
"ocena" : "10",
"datumPolaganja" : "10.1.2016",
"semestar" : "VII"
},
{
"predmet" : "Upravljanje kvalitetom",
"ocena" : "8",
"datumPolaganja" : "16.9.2016",
"semestar" : "VIII"
}
],
"grad" : "Brzece",
"Postanski_broj" : "54000",
"srednja_skola" : {
"grad" : "Brus",
"tip" : "Gimnazija",
"naziv" : "Gimnazija u Brusu",
"poeni_iz_skole" : "37.5"
}
}
...
Zadatak 33 - Vratiti sve studente iz kolekcije _student _koji studiraju smer "ISiT" i koji su imali više od 38 poena u srednjoj školi (koristeći aggregation)
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$match":{"$and":[{"smer":"ISiT"}, {"srednja_skola.poeni_iz_skole":{"$gt": "38"}}]}}]).pretty()
{
"_id" : ObjectId("5902012ebb26ec62d0b6bd2f"),
"student_id" : "2012/0112",
"ime" : "Emilie Eric",
"smer" : "ISiT",
"polozeni_ispiti" : [
{
"predmet" : "EPOS",
"ocena" : "10",
"datumPolaganja" : "20.3.2015",
"semestar" : "V"
},
{
"predmet" : "ITEH",
"ocena" : "9",
"datumPolaganja" : "20.4.2016",
"semestar" : "VII"
},
{
"predmet" : "IIU",
"ocena" : "7",
"datumPolaganja" : "21.5.2016",
"semestar" : "VIII"
},
{
"predmet" : "Internet Marketing",
"ocena" : "8",
"datumPolaganja" : "15.7.2016",
"semestar" : "VIII"
}
],
"grad" : "Cacak",
"Postanski_broj" : "32000",
"srednja_skola" : {
"grad" : "Cacak",
"tip" : "Gimnazija",
"naziv" : "Gimnazija u Cacku",
"poeni_iz_skole" : "40"
}
}
....
Zadatak 34 - Vratiti imena i grad iz kojeg dolaze svih studenata iz kolekcije student (koristeći aggregation)
Za ovakve potrebe koristi se $project
. On omogućava oblikovanje dokumenata dodavanjem, brisanjem ili menjanjem imena polja i takve prosleđuje u sledeće faze "pipeline-a". Koristi se 1 ili true
da se uračuna polje, odnosno 0 ili false
da se obriše.
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$project":{"ime":1, "grad":1}}])
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd2e"), "ime" : "Daniel Kostic", "grad" : "Brzece" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd2f"), "ime" : "Emilie Eric", "grad" : "Cacak" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd30"), "ime" : "Milos Nikolic", "grad" : "Beograd" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd31"), "ime" : "Jovana Pavlovic", "grad" : "Kraljevo" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd32"), "ime" : "Petar Jelenkovic", "grad" : "Beograd" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd33"), "ime" : "Nadica Dimitrijevic", "grad" : "Petrovac na Mlavi" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd34"), "ime" : "Nikola Music", "grad" : "Sarajevo" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd35"), "ime" : "Nadja Jelicic", "grad" : "Brus" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd36"), "ime" : "Marija Jankovic", "grad" : "Nis" }
{ "_id" : ObjectId("5902012ebb26ec62d0b6bd37"), "ime" : "Rastko Cerge", "grad" : "Beograd" }
Zadatak 35 - Vratiti imena i grad iz kojeg dolaze svih studenata koji studiraju na smeru "Menadzemnt" iz kolekcije student (koristeći aggregation)
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$match":{"smer":"Menadzment"}},{"$project":{"ime":1, "grad":1,"_id":0}}])
{ "ime" : "Petar Jelenkovic", "grad" : "Beograd" }
{ "ime" : "Rastko Cerge", "grad" : "Beograd" }
Zadatak 36 - Prikazati svaki položeni predmet studenta sa imenom "Daniel Kostic" unutar kolekcije student kao poseban dokument
Za ovakve potrebe koristi se faza $unwind
. $unwind
kreira novi dokument sa svaki element u nizu. On vraća novi dokument koji predstavlja listu novih dokumenata, a svaki novi dokument je u stvari element niza nad kojim se $unwind
izvršio.
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$match": {"ime": "Daniel Kostic"}},{"$unwind": "$polozeni_ispiti"}]).pretty()
{
"_id" : ObjectId("5902012ebb26ec62d0b6bd2e"),
"student_id" : "2012/0161",
"ime" : "Daniel Kostic",
"smer" : "ISiT",
"polozeni_ispiti" : {
"predmet" : "AROS",
"ocena" : "6",
"datumPolaganja" : "4.3.2013",
"semestar" : "III"
},
"grad" : "Brzece",
"Postanski_broj" : "54000",
"srednja_skola" : {
"grad" : "Brus",
"tip" : "Gimnazija",
"naziv" : "Gimnazija u Brusu",
"poeni_iz_skole" : "37.5"
}
}
{
"_id" : ObjectId("5902012ebb26ec62d0b6bd2e"),
"student_id" : "2012/0161",
"ime" : "Daniel Kostic",
"smer" : "ISiT",
"polozeni_ispiti" : {
"predmet" : "Strukture podataka i algoritmi",
"ocena" : "9",
"datumPolaganja" : "13.4.2014",
"semestar" : "IV"
},
"grad" : "Brzece",
"Postanski_broj" : "54000",
"srednja_skola" : {
"grad" : "Brus",
"tip" : "Gimnazija",
"naziv" : "Gimnazija u Brusu",
"poeni_iz_skole" : "37.5"
}
}
...
Dodatak
Kako je MongoDB "Schema less" moguće je da dokument ne sadrži određeni niz, ili je taj niz prazan, u tom slučaju svakako ne morate da brinete o tome šta $unwind
radi u tim slučajevima, pre je MongoDB prikazivao grešku ali od MongoDB 3.2. ti dokumenti se jednostavno samo preskoče.
Zadatak 37 - Prikazati svaki položeni predmet studenta sa imenom "Daniel Kostic" unutar kolekcije student kao poseban dokument pri čemu će uz predmet prikazati samo smer na kojem studira i grad iz kojeg dolazi
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$match":{"ime": "Daniel Kostic"}},{"$unwind":"$polozeni_ispiti"},{"$project":{"ime":1, "smer":1, "grad":1, "polozeni_ispiti":1, "_id": 0}}]).pretty()
{
"ime" : "Daniel Kostic",
"smer" : "ISiT",
"polozeni_ispiti" : {
"predmet" : "AROS",
"ocena" : "6",
"datumPolaganja" : "4.3.2013",
"semestar" : "III"
},
"grad" : "Brzece"
}
{
"ime" : "Daniel Kostic",
"smer" : "ISiT",
"polozeni_ispiti" : {
"predmet" : "Strukture podataka i algoritmi",
"ocena" : "9",
"datumPolaganja" : "13.4.2014",
"semestar" : "IV"
},
"grad" : "Brzece"
}
...
Zadatak 38 - Prikazati ukupan broj položenih ispita svih studenata smera "ISiT" i smera "Menadzment" kolekcije student
Za ovakve slučajeve možemo koristi $group
. MongoDB koristi $group
za grupisanje dokumenata po nekim specifičnim zahtevima. Grupisanje nije moguće obaviti u MongoDB bez agregacione funkcije.
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$group":{"_id":{"smer": "$smer"}, "ukupno_polozenih_ispita":{"$sum": {"$size": "$polozeni_ispiti"}}}}])
{ "_id" : { "smer" : "Menadzment" }, "ukupno_polozenih_ispita" : 8 }
{ "_id" : { "smer" : "ISiT" }, "ukupno_polozenih_ispita" : 32 }
Dodatak
Ovde je _id
obavezno polje. Unutar njega prosleđujemo polje na osnovu koga želimo da grupišemo dokument.
Zadatak 39 - Prikazati ukupan broj položenih ispita svih studenata smera "ISiT" kolekcije student
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$match":{"smer": "ISiT"}},{"$group":{"_id":{"smer": "$smer"}, "ukupno_polozenih_ispita":{"$sum": {"$size": "$polozeni_ispiti"}}}}])
{ "_id" : { "smer" : "ISiT" }, "ukupno_polozenih_ispita" : 32 }
Zadatak 40 - Iskoristi prethodni upit ali ime "smer" promeniti u "ime_kursa" i "ukupno_polozenih_ispita" u "broj_ispita"
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$match":{"smer": "ISiT"}},{"$group":{"_id":{"smer": "$smer"}, "ukupno_polozenih_ispita":{"$sum": {"$size": "$polozeni_ispiti"}}}},{"$project": {"ime_kursa": "$_id.smer", "broj_ispita": "$ukupno_polozenih_ispita"}}])
{ "_id" : { "smer" : "ISiT" }, "ime_kursa" : "ISiT", "broj_ispita" : 32 }
Zadatak 41 - Prikazati imena studenata i ukupan broj njihovih položenih ispita u opadajućem redosledu (redosled na osnovu imena) iz kolekcije student
Za određivanje redosleda koristi se $sort
koji sadrži ime polja na osnovu koga se vrši sortiranje i vrednost koja može biti 1 za rastući odnosno -1 za opadajući redosled.
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$group":{"_id":{"ime_studenta": "$ime"}, "ukupno_polozenih_ispita":{"$sum":{"$size": "$polozeni_ispiti"}}}},{"$sort": {"_id.ime_studenta": -1}}])
{ "_id" : { "ime_studenta" : "Rastko Cerge" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Petar Jelenkovic" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Nikola Music" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Nadja Jelicic" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Nadica Dimitrijevic" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Milos Nikolic" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Marija Jankovic" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Jovana Pavlovic" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Emilie Eric" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Daniel Kostic" }, "ukupno_polozenih_ispita" : 4 }
Zadatak 42 - Prikazati samo prva dva studenta iz prethodnog upita
Za limitiranje broja dokumenata koje aggregate
vraća koristi se $limit
koji prima samo vrednost, tj broj dokumenata koji treba da se vrati.
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$group":{"_id":{"ime_studenta": "$ime"}, "ukupno_polozenih_ispita":{"$sum":{"$size": "$polozeni_ispiti"}}}},{"$sort": {"_id.ime_studenta": -1}},{$limit: 2}])
{ "_id" : { "ime_studenta" : "Rastko Cerge" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Petar Jelenkovic" }, "ukupno_polozenih_ispita" : 4 }
Zadatak 43 - Prikazati četvrtog i petog studenta iz rezultata upita zadataka 41
Ovo ćemo ostvariti tako što ćemo preskočiti prva tri studenta iz rezultata koristeći $skip
Rešenje
use studenti
switched to db studenti
> db.student.aggregate([{"$group":{"_id":{"ime_studenta": "$ime"}, "ukupno_polozenih_ispita":{"$sum":{"$size": "$polozeni_ispiti"}}}},{"$sort": {"_id.ime_studenta": -1}},{"$skip":3},{"$limit": 2}])
{ "_id" : { "ime_studenta" : "Nadja Jelicic" }, "ukupno_polozenih_ispita" : 4 }
{ "_id" : { "ime_studenta" : "Nadica Dimitrijevic" }, "ukupno_polozenih_ispita" : 4 }
$lookup
$lookup
dolazi sa verzijom MongoDB 3.2. i on je jedan od dugo očekivanih "features"-a, jer predstavlja isto što i joins
u SQL-u. Pomoću $lookup
moguće je spojiti dokumenta iz dve različite kolekcije.
Da bi odradili sledeće zadatake napravićemo novu bazu studenti_2
koja će imate dve kolekcije grad
i student
.
Fajlove grad.json i student_2.json skinite sa moodle naloga. https://github.com/elab-office/mongoDB/tree/master/json
User:~/ $ mongoimport --db studenti_2 --drop --collection student --file ~/desktop/student_2.json --jsonArray
2017-05-05T15:06:41.634+0200 connected to: localhost
2017-05-05T15:06:41.635+0200 dropping: studenti_2.student
2017-05-05T15:06:41.689+0200 imported 10 documents
User:~/ $ mongoimport --db studenti_2 --drop --collection grad --file ~/desktop/grad.json --jsonArray
2017-05-05T15:07:05.209+0200 connected to: localhost
2017-05-05T15:07:05.209+0200 dropping: studenti_2.grad
2017-05-05T15:07:05.266+0200 imported 8 documents
Zadatak 44 - Vratiti imena studenata i imena gradova odakle dolaze kombinujući kolekcije student i grad iz baze studenti_2
> use studenti_2
switched to db studenti_2
> db.student.aggregate([{$lookup: {from:"grad", localField:"grad_id", foreignField:"_id", as: "grad"}},{$project: {"ime":1, "grad.ime":1, "_id":0}}]).pretty()
{ "ime" : "Milos Nikolic", "grad" : [ { "ime" : "Beograd" } ] }
{ "ime" : "Rastko Cerge", "grad" : [ { "ime" : "Beograd" } ] }
{ "ime" : "Jovana Pavlovic", "grad" : [ { "ime" : "Kraljevo" } ] }
{ "ime" : "Daniel Kostic", "grad" : [ ] }
{ "ime" : "Petar Jelenkovic", "grad" : [ { "ime" : "Beograd" } ] }
{ "ime" : "Nikola Music", "grad" : [ { "ime" : "Sarajevo" } ] }
{ "ime" : "Marija Jankovic", "grad" : [ { "ime" : "Nis" } ] }
{ "ime" : "Nadja Jelicic", "grad" : [ { "ime" : "Brus" } ] }
{ "ime" : "Emilie Eric", "grad" : [ { "ime" : "Cacak" } ] }