Hopp til innhald
Fagartikkel

Meir om spørjingar og organisering av data i Cloud Firestore

Tanken bak Cloud Firestore er at det skal gå så raskt som mogleg å hente ut data, same kor stor databasen er. Derfor er databasen organisert på ein annan måte enn det tradisjonelle SQL-databasar er, og du må derfor tenke annleis når du skal forme ut spørjingar mot databasen.

Frå datamodellering med relasjonsdatabasar (SQL) er du kanskje vand til at data blir organisert over fleire tabellar som har relasjonar til kvarandre. Om vi til dømes har ein database over hotell og vil registrere hotellromma på kvart enkelt hotell, ville vi i SQL ha laga ein tabell for hotell og ein tabell for hotellrom. Så hadde vi kopla desse saman med ein ein-til-mange-relasjon.

Samlingar og undersamlingar

I dokumentbaserte databasar (NoSQL) blir data organiserte på ein annan måte. Sidan kvart enkelt databaseobjekt er lagra som eit frittståande dokument, med i utgangspunktet ingen reglar for kva data som skal inn i kvart enkelt dokument, kan vi ikkje basere oss på koplingar med nøklar og framandnøklar, slik som i SQL. I dokumentbaserte databasar er dokument i staden organiserte i samlingar (collections) og undersamlingar (subcollections). Hotelldatabasen i dømet vårt vil då vere organisert med alle hotella i ei samling, og romma vil vere undersamlingar til kvart hotell.

Dette kan verke ulogisk om du er vand til relasjonsdatabasar, der det blir lagt mykje vekt på struktur, og der det er eit viktig prinsipp å unngå dobbeltlagring, men begge modellane følger det same prinsippet: Eitt hotell har fleire rom, og eitt rom høyrer til eitt hotell.

NoSQL gir deg derimot meir fleksibilitet. Det er ingenting i vegen for at fleire hotell har like romnummer, som dei ofte har, fordi romma blir lagra under kvart hotell, og ID-ane treng då berre å vere unike for kvart hotell. Det er heller ikkje noko problem om eit hotell ikkje har romnummer, men unike namn på romma, eller kombinasjonar av bokstavar og tal. Det kan òg variere kva slags informasjon som er relevant å ha med i databasen ut frå profilen til hotellet. Nokre hotell har kanskje fokus på velvære og vil registrere om rommet har boblebad, steamdusj eller massasjestolar, mens andre vil berre registrere om rommet har bad eller ikkje. Det er ikkje noko problem i ein NoSQL-database.

Mange-til-mange-relasjonar

Kva med mange-til-mange-relasjonar? Sjå for deg ein database for å registrere elevar, kva fag dei har teke, og kva karakter dei har fått i dei ulike faga. Ein datamodell for denne relasjonsdatabasen kan sjå slik ut:

I Firebase kan dette gjerast på nesten same måte ved å lage to parallelle samlingar for elevar og fag og ei eiga samling for å halde orden på kva elevar som har teke kva fag, og kva karakterar dei har fått.

Spørjingar mot fleire samlingar

Forskjellen på dette og ein mange-til-mange-relasjon i SQL er korleis spørjingar fungerer. I SQL kunne vi ha brukt ei spørjing med JOIN for å hente data frå fleire tabellar samtidig. NoSQL har ikkje støtte for framandnøklar og JOIN-operasjonar, så for å hente ut data frå fleire tabellar, må vi køyre fleire separate spørjingar. I NoSQL kan vi heller ikkje definere på førehand kva felt vi vil hente ut, slik som vi kan gjere i SQL med SELECT.

For å hente ut namn og karakter på alle elevar i faget utvikling kunne SQL-spørjinga ha sett slik ut:

SQL-spørjing
1SELECT Elev.fornamn, Elev.etternamn, Elev_har_Fag.Karakter
2FROM Fag 
3  JOIN Elev_har_Fag 
4  JOIN Elev
5ON Elev.ElevID = Elev_har_Fag.Elev_ElevID 
6  AND Fag.FagID = Elev_har_Fag.Fag_FagID
7WHERE Fag.Fagnamn = "Utvikling";

I Cloud Firestore må vi lage fleire åtskilde spørjingar, slik:

Firestore-spørjing
1// import { getDoc, getDocs, doc, collection, query, where } from "https://www.gstatic.com/firebasejs/9.6.3/firebase-firestore.js";
2
3// Hentar alle dokument frå samlinga Fag der fagnamn er Utvikling,
4// og lagrar dei i arrayen fagSporjing
5const fagSporjing = await getDocs(
6  query(
7    collection(db, "Fag"), 
8    where("Fagnamn", "==", "Utvikling")
9  )
10);
11// (I praksis vil spørjinga returnere berre eitt dokument, 
12// men det blir likevel lagra som ein array med eitt element.)
13
14// Hentar alle dokument frå samlinga Elev_har_Fag 
15// (dvs. ID, ElevID, FagID, år og karakter) der FagID 
16// er lik resultatet frå fagSporjing, og lagrer dei 
17// i arrayen elevHarFagSporjing. 
18const elevHarFagSporjing = await getDocs(
19  query(
20    collection(db, "Elev_har_Fag"), 
21    where("FagID", "==", fagSporjing[0].id)
22  )
23);
24// Sidan getDocs returnerer ein array, må vi angi 
25// at vi skal ha data frå element 0 i arrayen 
26// (arrayen har i dette tilfellet berre eitt element).
27
28// Opprettar ein array for å lagre all data om elevar 
29const elevArr = [];
30// Opprettar ein (parallell) array for å lagre karakterar
31const karakterArr = [];
32
33// Køyrer ei lykkje som går gjennom alle dokument 
34// frå spørjinga elevHarFagSporjing
35elevHarFagSporjing.forEach((dokument) => {
36  // Hentar inn kvart enkelt elevdokument og lagrar i arrayen
37  // elevArr. getDoc hentar eit dokument med ein bestemd ID. 
38  elevArr.push(
39    await getDoc(
40      doc(db, "Elev", dokument.data().ElevID)
41    )
42  );
43  
44  //Hentar karakteren til kvar elev og lagrar i arrayen karakterArr 
45  karakterArr.push(
46    dokument.data().karakter
47  );
48  // Denne arrayen får då parallelle verdiar med ElevArr, 
49  // altså vil karakterane komme i rekkefølge med riktig elev.
50});
51
52// Skriv ut fornamn og etternamn (frå elevArr) og 
53// karakter (frå karakterArr) til alle elevar
54for(i = 0; i < elevArr.length; i++){
55  console.log(
56    elevArr[i].data().fornamn, 
57    elevArr[i].data().etternamn, 
58    karakterArr[i]
59  );
60}

Avgrensingar på spørjingar i Cloud Firestore

Dersom du har brukt SQL før, veit du at du i SQL kan kombinere vilkår, sorterting, gruppering og meir for å få akkurat det resultatet du ønsker frå databasen. På grunn av måten data er organisert i Cloud Firestore på, er det ein del avgrensingar på korleis du kan forme ut spørjingar. Dersom du har eit vilkår (where()), kan du ikkje sortere (orderBy()) etter noko anna enn den parameteren du brukte i vilkåret.

Eksempel
1// import { collection, query, where, orderBy } from "https://www.gstatic.com/firebasejs/9.6.3/firebase-firestore.js";
2
3query(
4  collection(db, "elevar"),
5  where("alder", "<", 18),
6  orderBy("etternamn")
7);

I dette dømet vil du få ei feilmelding fordi det er brukt ulike parametrar i where() (alder) og orderBy() (etternamn). Dette er fordi ei slik spørjing vil ta lengre tid å gjennomføre, og det vil skape meir trafikk til og frå databasen. Det er likevel mogleg å gjennomføre ei slik spørjing ved å lage ein samansett indeks (composite index) i Firebase-konsollen. Dette er det mogleg å gjere direkte frå feilmeldinga du får dersom du prøver å køyre spørjinga i dømet. Feilmeldinga vil innehalde ei lenke til Firebase-konsollen som lar deg opprette akkurat den samansette indeksen du treng, men du bør vere varsam med å lage for mange, då du berre har moglegheit til å lage 50 samansette indeksar til kvar database. Det er meir formålstenleg å sortere resultatet du får tilbake (som ein array) enn at Firestore sorterer dette for deg.

På same måte vil du i utgangspunktet ikkje kunne køyre spørjingar mot fleire undersamlingar samtidig. I hotelldømet over vil du til dømes ikkje kunne søke etter ledige dobbeltrom frå fleire hotell samtidig. Dette kan òg løysast ved å lage ein samansett indeks i Firebase-konsollen, eller du kan køyre fleire separate spørjingar mot kvart hotell og samle resultata i ei liste (array), som du kan sortere og søke i via ditt eige skript.

Meir informasjon

Firebase tilbyr informasjon om korleis Firestore-databasen er bygd opp, og korleis han blir brukt i videospelelista "Get to know Cloud Firestore" på YouTube.

Her er lenke til to videoar som forklarer prinsippa bak det du har lese om i denne artikkelen:

Relatert innhald

Fagstoff
Datamodellering

Ved hjelp av datamodellering planlegg ein korleis ein database skal byggjast opp ved å setje opp tabellar og angi kva forhold desse har til kvarandre.

Fagstoff
Lese data frå Cloud Firestore

Når vi skal hente data frå Cloud Firestore, kan vi hente ut eitt enkelt dokument, alle dokumenta i ei samling, eller vi kan hente data med spørjingar.