Lær JOIN, GROUP BY, HAVING og subqueries for komplekse dataspørringer.
SQL – avanserte spørjingar og samankoplingar
Hittil har vi jobba med data frå éin tabell om gongen. Men den verkelege krafta i relasjonsdatabasar ligg i moglegheita til å kople saman data frå fleire tabellar. Tenk deg at du vil sjå elevnamn saman med fagnamn og karakterar – den informasjonen er fordelt over tre tabellar (elever, fag og karakterer). For å setje dette saman treng vi JOIN.
I tillegg skal vi lære å rekne ut statistikk med aggregeringsfunksjonar (telje, summere, finne gjennomsnitt), gruppere data med GROUP BY, filtrere grupper med HAVING, og bruke underspørjingar for å løyse komplekse dataspørsmål.
Desse teknikkane er det som gjer SQL til eit kraftig analyseverktøy. Med dei kan du svare på spørsmål som «Kva er gjennomsnittskarakteren per fag?», «Kva for elevar har karakter over gjennomsnittet?» og «Kva for klasse har flest elevar med toppkarakter?».
Ein JOIN koplar rader frå to tabellar basert på ein felles kolonne. Den vanlegaste forma er INNER JOIN, som returnerer berre rader der det finst ein match i begge tabellane.
Syntaks for INNER JOIN
SELECT kolonne1, kolonne2, ...
FROM tabell1
INNER JOIN tabell2 ON tabell1.kolonne = tabell2.kolonne;Eksempel: Elever med klassenavn
I staden for å sjå berre klasse_id (som er eit tal), vil vi sjå klassenamnet:
SELECT elever.fornavn, elever.etternavn, klasser.klassenavn
FROM elever
INNER JOIN klasser ON elever.klasse_id = klasser.klasse_id;Resultat:
| fornavn | etternavn | klassenavn |
|---|---|---|
| Emma | Hansen | 10A |
| Oliver | Johansen | 10A |
| Sofie | Berg | 10A |
| Nora | Olsen | 10B |
| Jakob | Larsen | 10B |
| Noah | Andreassen | 10B |
| Liam | Dahl | 10C |
| Ella | Nilsen | 10C |
Tabellaliasar
For å gjere spørjingane kortare kan vi gi tabellane aliasar:
SELECT e.fornavn, e.etternavn, k.klassenavn
FROM elever e
INNER JOIN klasser k ON e.klasse_id = k.klasse_id;Her er e eit alias for elever og k eit alias for klasser.La oss kople elever, karakterer og fag for å sjå ei komplett karakteroversikt med namn:
SELECT
e.fornavn,
e.etternavn,
f.fagnavn,
k.karakter,
k.termin
FROM karakterer k
INNER JOIN elever e ON k.elev_id = e.elev_id
INNER JOIN fag f ON k.fag_id = f.fag_id
ORDER BY e.etternavn, f.fagnavn;Resultat (utdrag):
| fornavn | etternavn | fagnavn | karakter | termin |
|---|---|---|---|---|
| Noah | Andreassen | Matematikk | 3 | H2024 |
| Noah | Andreassen | Norsk | 2 | H2024 |
| Sofie | Berg | Matematikk | 5 | H2024 |
| Sofie | Berg | Naturfag | 4 | H2024 |
| Sofie | Berg | Norsk | 5 | H2024 |
| ... | ... | ... | ... | ... |
Her koplar vi tre tabellar:
karakterer med elever (via elevid), og karakterer med fag (via fagid). Resultatet gir oss lesbare namn i staden for ID-nummer.Det finst fleire typar JOIN:
INNER JOIN – Returnerer berre rader der det finst match i begge tabellane. Rader utan match blir utelatne.
LEFT JOIN (LEFT OUTER JOIN) – Returnerer alle rader frå venstre tabell, og matchande rader frå høgre tabell. Dersom ingen match finst, blir høgre side fylt med NULL.
RIGHT JOIN (RIGHT OUTER JOIN) – Returnerer alle rader frå høgre tabell, og matchande rader frå venstre. Motsett av LEFT JOIN. (Merk: RIGHT JOIN blir ikkje støtta i SQLite, men kan simulerast ved å byte tabellrekkjefølgja med LEFT JOIN.)
FULL OUTER JOIN – Returnerer alle rader frå begge tabellane, med NULL der det ikkje er match. (Blir heller ikkje støtta direkte i SQLite.)
INNER JOIN viser berre elevar som har karakterar. Med LEFT JOIN får vi med alle elevar, også dei som manglar karakterar:
SELECT e.fornavn, e.etternavn, f.fagnavn, k.karakter
FROM elever e
LEFT JOIN karakterer k ON e.elev_id = k.elev_id
LEFT JOIN fag f ON k.fag_id = f.fag_id
ORDER BY e.etternavn;Dersom ein elev ikkje har nokon karakterar, blir NULL vist i kolonnane fagnavn og karakter:
| fornavn | etternavn | fagnavn | karakter |
|---|---|---|---|
| Emma | Hansen | Matematikk | 5 |
| Emma | Hansen | Norsk | 4 |
| Emma | Hansen | Naturfag | 5 |
| Maja | Strand | NULL | NULL |
Her ser vi at Maja Strand (som vi sette inn tidlegare) ikkje har nokon karakterar enno. Med INNER JOIN ville Maja vore usynleg i resultatet.
Tommelfingerregel: Bruk INNER JOIN når du berre vil ha rader som matchar i begge tabellar. Bruk LEFT JOIN når du vil behalde alle rader frå den «venstre» tabellen, uansett om dei har matchande rader i den andre.
Aggregeringsfunksjonar reknar ut éin verdi basert på mange rader. Dei vanlegaste er:
| Funksjon | Skildring | Eksempel |
|---|---|---|
COUNT() | Tel talet på rader | COUNT(*) eller COUNT(kolonne) |
SUM() | Summerer verdiar | SUM(karakter) |
AVG() | Reknar ut gjennomsnitt | AVG(karakter) |
MIN() | Finn lågaste verdi | MIN(karakter) |
MAX() | Finn høgaste verdi | MAX(karakter) |
Eksempel
-- Antall elever i databasen
SELECT COUNT(*) AS antall_elever FROM elever;Resultat: 8-- Gjennomsnittskarakter for alle
SELECT AVG(karakter) AS gjennomsnitt FROM karakterer;Resultat: 4.0 (ca.)-- Høyeste og laveste karakter
SELECT MAX(karakter) AS hoyeste, MIN(karakter) AS laveste FROM karakterer;Resultat: hoyeste = 6, laveste = 2
-- Antall karakterer registrert
SELECT COUNT(*) AS antall_karakterer FROM karakterer;Legg merke til AS – det gir kolonnen i resultatet eit forståeleg namn (alias).GROUP BY grupperer rader med like verdiar slik at aggregeringsfunksjonar blir rekna ut per gruppe.Eksempel: Antall elever per klasse
SELECT klasse_id, COUNT(*) AS antall_elever
FROM elever
GROUP BY klasse_id;Resultat:
| klasseid | antallelever |
|---|---|
| 1 | 3 |
| 2 | 3 |
| 3 | 2 |
Eksempel: Gjennomsnittskarakter per fag
SELECT f.fagnavn, AVG(k.karakter) AS snittkarakter
FROM karakterer k
INNER JOIN fag f ON k.fag_id = f.fag_id
GROUP BY f.fagnavn;Resultat:| fagnavn | snittkarakter |
|---|---|
| Matematikk | 4.0 |
| Naturfag | 4.67 |
| Norsk | 3.75 |
Eksempel: Antall fag per elev
SELECT e.fornavn, e.etternavn, COUNT(*) AS antall_fag
FROM karakterer k
INNER JOIN elever e ON k.elev_id = e.elev_id
GROUP BY e.elev_id, e.fornavn, e.etternavn;Resultat:
| fornavn | etternavn | antall_fag |
|---|---|---|
| Emma | Hansen | 3 |
| Oliver | Johansen | 3 |
| Nora | Olsen | 3 |
| Jakob | Larsen | 2 |
| ... | ... | ... |
HAVING filtrerer grupper etter at GROUP BY har gjort jobben sin. Det er til grupper det WHERE er til individuelle rader.Viktig forskjell:
- WHERE filtrerer rader før gruppering
- HAVING filtrerer grupper etter gruppering
Eksempel: Fag med gjennomsnittskarakter over 4
SELECT f.fagnavn, AVG(k.karakter) AS snittkarakter
FROM karakterer k
INNER JOIN fag f ON k.fag_id = f.fag_id
GROUP BY f.fagnavn
HAVING AVG(k.karakter) > 4;Resultat:
| fagnavn | snittkarakter |
|---|---|
| Naturfag | 4.67 |
Eksempel: Elever med mer enn 2 fag
SELECT e.fornavn, e.etternavn, COUNT(*) AS antall_fag
FROM karakterer k
INNER JOIN elever e ON k.elev_id = e.elev_id
GROUP BY e.elev_id, e.fornavn, e.etternavn
HAVING COUNT(*) > 2;Kombinere WHERE og HAVING
-- Gjennomsnittskarakter per fag for termin H2024,
-- men bare fag der snittet er over 3.5
SELECT f.fagnavn, AVG(k.karakter) AS snittkarakter, COUNT(*) AS antall
FROM karakterer k
INNER JOIN fag f ON k.fag_id = f.fag_id
WHERE k.termin = 'H2024'
GROUP BY f.fagnavn
HAVING AVG(k.karakter) > 3.5;Her filtrerer WHERE først ut berre H2024-karakterar, deretter grupperer GROUP BY etter fag, og til slutt filtrerer HAVING bort grupper med snitt under 3.5.
Ei fullstendig SELECT-setning har klausulane i denne rekkjefølgja:
SELECT kolonner -- 1. Velg kolonner
FROM tabell -- 2. Fra tabell(er)
JOIN tabell2 ON ... -- 3. Koble med andre tabeller
WHERE betingelse -- 4. Filtrer rader
GROUP BY kolonne -- 5. Grupper rader
HAVING betingelse -- 6. Filtrer grupper
ORDER BY kolonne -- 7. Sorter resultatet
LIMIT antall; -- 8. Begrens antall raderDenne rekkjefølgja må alltid følgjast i SQL-koden. Databasen utfører dei derimot i ei anna logisk rekkjefølgje: FROM/JOIN, deretter WHERE, GROUP BY, HAVING, SELECT, ORDER BY, og til slutt LIMIT.
Ein subquery (underspørjing) er ei SELECT-setning som er nesta inne i ei anna spørjing. Underspørjinga blir køyrd først, og resultatet blir brukt av den ytre spørjinga.
Subquery i WHERE
-- Finn alle elever med karakter høyere enn gjennomsnittet
SELECT e.fornavn, e.etternavn, k.karakter
FROM karakterer k
INNER JOIN elever e ON k.elev_id = e.elev_id
WHERE k.karakter > (SELECT AVG(karakter) FROM karakterer);Her reknar underspørjinga først ut gjennomsnittskarakteren, og den ytre spørjinga bruker dette resultatet for å filtrere.
Subquery med IN
-- Finn alle elever som har karakter 6 i et eller annet fag
SELECT fornavn, etternavn
FROM elever
WHERE elev_id IN (SELECT elev_id FROM karakterer WHERE karakter = 6);Underspørjinga finn alle elev_id-ar med karakter 6, og den ytre spørjinga hentar namna til desse elevane.
Subquery i FROM (avleidde tabellar)
-- Finn elevenes gjennomsnittskarakter og vis bare de med snitt over 4
SELECT fornavn, etternavn, snitt
FROM (
SELECT e.fornavn, e.etternavn, AVG(k.karakter) AS snitt
FROM karakterer k
INNER JOIN elever e ON k.elev_id = e.elev_id
GROUP BY e.elev_id, e.fornavn, e.etternavn
) AS elevsnitt
WHERE snitt > 4;Her lagar underspørjinga ein mellombels tabell (elevsnitt) med gjennomsnitt per elev, og den ytre spørjinga filtrerer denne.
La oss bruke alle teknikkane vi har lært for å lage ein omfattande analyse:
1. Karakteroversikt med klassenamn og fagnamn:
SELECT kl.klassenavn, e.fornavn, e.etternavn,
f.fagnavn, k.karakter
FROM karakterer k
INNER JOIN elever e ON k.elev_id = e.elev_id
INNER JOIN fag f ON k.fag_id = f.fag_id
INNER JOIN klasser kl ON e.klasse_id = kl.klasse_id
ORDER BY kl.klassenavn, e.etternavn, f.fagnavn;2. Beste elev per fag:
SELECT f.fagnavn, e.fornavn, e.etternavn, k.karakter
FROM karakterer k
INNER JOIN elever e ON k.elev_id = e.elev_id
INNER JOIN fag f ON k.fag_id = f.fag_id
WHERE k.karakter = (
SELECT MAX(k2.karakter)
FROM karakterer k2
WHERE k2.fag_id = k.fag_id
);3. Klassestatistikk:
SELECT kl.klassenavn,
COUNT(DISTINCT e.elev_id) AS antall_elever,
ROUND(AVG(k.karakter), 2) AS snittkarakter,
MIN(k.karakter) AS laveste,
MAX(k.karakter) AS hoyeste
FROM klasser kl
INNER JOIN elever e ON kl.klasse_id = e.klasse_id
INNER JOIN karakterer k ON e.elev_id = k.elev_id
GROUP BY kl.klassenavn
ORDER BY snittkarakter DESC;ROUND(AVG(k.karakter), 2) avrundar gjennomsnittet til 2 desimalar, og COUNT(DISTINCT e.elev_id) tel unike elevar (ikkje talet på karakterrader).
Når du koplar fleire tabellar, kan kolonnenamn bli tvetydige. Til dømes har både elever og karakterer ein kolonne elev_id. Bruk tabellaliasar for å unngå forvirring:
-- Uten alias (langt og uoversiktlig):
SELECT elever.fornavn, karakterer.karakter
FROM elever INNER JOIN karakterer ON elever.elev_id = karakterer.elev_id;
-- Med alias (kort og ryddig):
SELECT e.fornavn, k.karakter
FROM elever e
INNER JOIN karakterer k ON e.elev_id = k.elev_id;Vel korte, meiningsfulle aliasar: e for elever, k for karakterer, f for fag, kl for klasser osv.
Kva gjer ein INNER JOIN?
Kva for aggregeringsfunksjon reknar ut gjennomsnittet av verdiar i ein kolonne?
Kva er forskjellen mellom WHERE og HAVING?
Skriv SQL-spørjingar for følgjande oppgåver (bruk skuledatabasen):
a) Vis fornamn, etternamn og klassenamn for alle elevar (bruk JOIN)
b) Rekn ut gjennomsnittskarakteren per elev (vis fornamn og gjennomsnitt)
c) Tel talet på elevar per klasse (vis klassenamn og tal)
Kva returnerer denne spørjinga?
SELECT f.fagnavn, COUNT(*) AS antall
FROM karakterer k
INNER JOIN fag f ON k.fag_id = f.fag_id
GROUP BY f.fagnavn
HAVING COUNT(*) > 5;Skriv SQL-spørjingar for følgjande oppgåver:
a) Vis elevnamn, fagnamn og karakter for alle karakterar som er over gjennomsnittskarakteren (bruk subquery)
b) Finn alle elevar som har fått karakter 6 i minst eitt fag – vis namnet og faget dei fekk 6 i
c) Vis klassestatistikk: klassenamn, tal på elevar, gjennomsnittskarakter (avrunda til 1 desimal), høgaste og lågaste karakter – men berre for klassar med gjennomsnitt over 3.5
Kva er forskjellen mellom INNER JOIN og LEFT JOIN i denne situasjonen?
Du har 8 elevar i elevtabellen, men berre 6 av dei har karakterar i karakterer-tabellen.
-- Spørring A:
SELECT e.fornavn FROM elever e INNER JOIN karakterer k ON e.elev_id = k.elev_id;
-- Spørring B:
SELECT e.fornavn FROM elever e LEFT JOIN karakterer k ON e.elev_id = k.elev_id;Du har nettbutikk-databasen frå kapittel 6.2. Skriv SQL-spørjingar for:
a) Vis alle bestillingar med kundenamn (fornamn og etternamn), bestillingsdato og totalpris – sortert etter dato
b) Finn total omsetning (sum av alle totalpris) per kunde – vis berre kundar med total omsetning over 1000 kr
c) Finn det mest bestilte produktet (produktet som har flest bestillingslinjer). Vis produktnamn og kor mange gonger det er bestilt.
Oppsummering
I dette kapittelet har du lært:
- JOIN: koplar saman data frå fleire tabellar.
- Typar JOIN: INNER, LEFT, RIGHT og FULL.
- Aggregeringsfunksjonar: COUNT, SUM, AVG, MIN og MAX.
- GROUP BY og HAVING: grupperer og filtrerer grupper.
- Subqueries: underspørjingar for komplekse spørjingar.
Nøkkelomgrep
| Omgrep | Forklaring |
|---|---|
| JOIN | Koplar data frå fleire tabellar |
| GROUP BY | Grupperer rader for aggregering |
| Subquery | Spørjing inne i ei anna spørjing |
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.