JOINs, subqueries, views og indekser.
Avansert SQL
Du kan allereie SELECT, WHERE, ORDER BY og grunnleggjande SQL. No skal vi ta steget vidare og lære teknikkar som gjer at du kan hente ut kompleks informasjon frå fleire tabellar samtidig, gruppere data, lage virtuelle tabellar og optimalisere ytinga.
I dette kapittelet lærer du:
- JOIN for å kombinere data frå fleire tabellar
- GROUP BY og HAVING for å aggregere og filtrere grupper
- Subqueries (underspørjingar) for komplekse spørjingar
- Views for å lage gjenbrukbare "virtuelle tabellar"
- Indeksar for å gjere spørjingar raskare
JOIN – å kombinere tabellar
I ein normalisert database er data spreidde over fleire tabellar. For å hente ut meiningsfull informasjon må vi ofte kombinere data frå fleire tabellar. Det gjer vi med JOIN.
INNER JOIN
Returnerer berre rader der det finst matchande verdiar i begge tabellane.
-- Hent alle bøker med forfatternavnet
SELECT Bok.tittel, Forfatter.navn
FROM Bok
INNER JOIN Forfatter ON Bok.forfatterID = Forfatter.forfatterID;LEFT JOIN (LEFT OUTER JOIN)
Returnerer alle rader frå venstre tabell, sjølv om det ikkje finst match i høgre tabell.
-- Hent alle forfattere, også de uten bøker
SELECT Forfatter.navn, Bok.tittel
FROM Forfatter
LEFT JOIN Bok ON Forfatter.forfatterID = Bok.forfatterID;Forfattarar utan bøker vil få NULL i bok-kolonnane.
RIGHT JOIN
Returnerer alle rader frå høgre tabell (fungerer motsett av LEFT JOIN). Ikkje støtta i SQLite, men kan løysast med LEFT JOIN ved å byte rekkjefølgje.
FULL OUTER JOIN
Returnerer alle rader frå begge tabellane. Heller ikkje støtta i SQLite.
Flerveis JOIN
Du kan kombinere fleire tabellar:
SELECT Kunde.navn, Ordre.ordreID, Produkt.produktnavn
FROM Kunde
INNER JOIN Ordre ON Kunde.kundeID = Ordre.kundeID
INNER JOIN OrdreLinjer ON Ordre.ordreID = OrdreLinjer.ordreID
INNER JOIN Produkt ON OrdreLinjer.produktID = Produkt.produktID;Eksempel: JOIN i praksis
Gitt desse tabellane:
-- Elev (elevID, navn, klasseID)
-- Klasse (klasseID, klassenavn)
-- Karakter (karakterID, elevID, fagnavn, karakter)Oppgåve: Hent alle elevar med klassenamn og karakterane deira.
SELECT
Elev.navn AS elevnavn,
Klasse.klassenavn,
Karakter.fagnavn,
Karakter.karakter
FROM Elev
INNER JOIN Klasse ON Elev.klasseID = Klasse.klasseID
LEFT JOIN Karakter ON Elev.elevID = Karakter.elevID
ORDER BY Elev.navn, Karakter.fagnavn;Vi brukar:
- INNER JOIN mellom Elev og Klasse (alle elevar må ha ein klasse)
- LEFT JOIN mellom Elev og Karakter (nokre elevar kan mangle karakterar)
GROUP BY og aggregatfunksjonar
GROUP BY lèt oss gruppere rader og bruke aggregatfunksjonar på kvar gruppe.
Vanlege aggregatfunksjonar:
- COUNT() – tel tal på rader
- SUM() – summerer verdiar
- AVG() – reknar ut gjennomsnitt
- MIN() – finn minste verdi
- MAX() – finn største verdi
Grunnleggjande GROUP BY:
-- Hvor mange elever i hver klasse?
SELECT klasseID, COUNT(*) AS antall_elever
FROM Elev
GROUP BY klasseID;-- Gjennomsnittskarakter per fag
SELECT fagnavn, AVG(karakter) AS snittkarakter
FROM Karakter
GROUP BY fagnavn;HAVING – filtrering av grupper
WHERE filtrerer rader før gruppering. HAVING filtrerer grupper etter gruppering.
-- Fag med snittkarakter over 4.0
SELECT fagnavn, AVG(karakter) AS snitt
FROM Karakter
GROUP BY fagnavn
HAVING AVG(karakter) > 4.0;Rekkjefølgje i SQL-spørjingar:
1. FROM (vel tabell)
2. WHERE (filtrer rader)
3. GROUP BY (grupper rader)
4. HAVING (filtrer grupper)
5. SELECT (vel kolonnar)
6. ORDER BY (sorter resultat)
Eksempel: GROUP BY med JOIN
La oss kombinere JOIN og GROUP BY:
-- Hvor mange bøker har hver forfatter skrevet?
SELECT
Forfatter.navn,
COUNT(Bok.ISBN) AS antall_bøker
FROM Forfatter
LEFT JOIN Bok ON Forfatter.forfatterID = Bok.forfatterID
GROUP BY Forfatter.forfatterID, Forfatter.navn
ORDER BY antall_bøker DESC;Tips:
- Bruk LEFT JOIN dersom du vil inkludere forfattarar utan bøker (dei får COUNT = 0)
- GROUP BY må inkludere alle kolonnar frå SELECT som ikkje er aggregatfunksjonar
- ORDER BY kjem alltid til slutt
Subqueries (underspørjingar)
Ein subquery er ei SQL-spørjing inne i ei anna SQL-spørjing. Dei blir ofte brukte når du treng resultatet av éi spørjing som input til ei anna.
Subquery i WHERE:
-- Finn elever som har bedre snittkarakter enn gjennomsnittet
SELECT navn, AVG(karakter) AS snitt
FROM Elev
INNER JOIN Karakter ON Elev.elevID = Karakter.elevID
GROUP BY Elev.elevID, Elev.navn
HAVING AVG(karakter) > (
SELECT AVG(karakter) FROM Karakter
);Subquery i FROM:
-- Bruk resultatet av en spørring som en "tabell"
SELECT klassenavn, gjennomsnitt
FROM (
SELECT
Klasse.klassenavn,
AVG(Karakter.karakter) AS gjennomsnitt
FROM Elev
JOIN Klasse ON Elev.klasseID = Klasse.klasseID
JOIN Karakter ON Elev.elevID = Karakter.elevID
GROUP BY Klasse.klasseID, Klasse.klassenavn
) AS klasseresultater
WHERE gjennomsnitt > 4.0;IN og NOT IN med subquery:
-- Finn elever som har fått 6 i minst ett fag
SELECT DISTINCT navn
FROM Elev
WHERE elevID IN (
SELECT elevID FROM Karakter WHERE karakter = 6
);Views – virtuelle tabellar
Ei view er ei lagra SQL-spørjing som oppfører seg som ein tabell. Ho lagrar ikkje data, men gir deg ei "ferdig" spørjing du kan gjenbruke.
Opprette ei view:
CREATE VIEW ElevOversikt AS
SELECT
Elev.navn AS elevnavn,
Klasse.klassenavn,
AVG(Karakter.karakter) AS snittkarakter
FROM Elev
INNER JOIN Klasse ON Elev.klasseID = Klasse.klasseID
LEFT JOIN Karakter ON Elev.elevID = Karakter.elevID
GROUP BY Elev.elevID, Elev.navn, Klasse.klassenavn;Bruke ei view:
-- Nå kan du bruke ElevOversikt som en vanlig tabell:
SELECT * FROM ElevOversikt WHERE snittkarakter > 4.5;Fordelar med views:
- Forenklar komplekse spørjingar
- Gjenbrukbar kode
- Skjuler kompleksitet for sluttbrukarar
- Kan brukast for tryggleik (gi tilgang til view i staden for rå-tabell)
Slette ei view:
DROP VIEW ElevOversikt;Indeksar – optimalisering
Ein indeks er ein datastruktur som gjer det raskare å søkje etter data i ein tabell. Han fungerer som stikkordregisteret bak i ei bok.
Når bruke indeksar?
- På kolonnar som ofte blir brukte i WHERE
- På kolonnar som ofte blir brukte i JOIN
- På primærnøklar (blir oppretta automatisk)
- På framandnøklar
Opprette ein indeks:
-- Indeks på kolonnen "navn" i Elev-tabellen
CREATE INDEX idx_elev_navn ON Elev(navn);Indeks på fleire kolonnar:
CREATE INDEX idx_karakter_elev_fag ON Karakter(elevID, fagnavn);Fordelar og ulemper:
Fordelar:
- Dramatisk raskare SELECT-spørjingar
- Raskare JOIN-operasjonar
Ulemper:
- Tek opp ekstra plass
- Gjer INSERT, UPDATE og DELETE litt tregare (indeksen må oppdaterast)
Tips: Ikkje lag indeks på alt. Berre på kolonnar som faktisk blir mykje brukte i spørjingar.
Slette ein indeks:
DROP INDEX idx_elev_navn;INNER JOIN: Returnerer berre rader med matchande verdiar i begge tabellane.
LEFT JOIN: Returnerer alle rader frå venstre tabell, også dei utan match.
GROUP BY: Grupperer rader med like verdiar i éin eller fleire kolonnar.
HAVING: Filtrerer grupper etter aggregering (blir brukt med GROUP BY).
Subquery: Ei SQL-spørjing inne i ei anna spørjing.
View: Ei lagra SQL-spørjing som kan brukast som ein virtuell tabell.
Indeks: Ein datastruktur som akselererer søk i ein tabell.
Kva er skilnaden mellom INNER JOIN og LEFT JOIN?
Kva er skilnaden mellom WHERE og HAVING?
Gitt desse tabellane:
Forfatter (forfatterID, navn, land)
Bok (ISBN, tittel, forfatterID, utgivelsesår)Skriv SQL-spørjingar for:
a) Hent alle bøker med forfattaren sitt namn og land
b) Finn tal på bøker per land
c) Finn land som har gitt ut meir enn 10 bøker
d) Finn forfattarar som ikkje har skrive nokon bøker
Gitt tabellen:
Ordre (ordreID, kundeID, produktID, antall, pris, dato)a) Finn totalt salsbeløp per kunde (sorter frå høgast til lågast)
b) Finn kundar som har handla for meir enn 10 000 kr totalt
c) Finn gjennomsnittleg ordrestorleik (antall × pris) per månad i 2024
a) Lag ei VIEW kalla "KundeStatistikk" som viser kundeID, tal på ordrar og totalt salsbeløp for kvar kunde.
b) Bruk denne viewen til å finne kundar som har handla meir enn 5 gonger og brukt over 5000 kr totalt.
Når bør du opprette ein indeks på ein kolonne?
// --- Samleoppgaver ---
Du har ein database for ei online-strøymeteneste:
Bruker (brukerID, navn, epost, registrertDato)
Film (filmID, tittel, sjanger, lengdeMinutter, utgivelsesår)
Visning (visningID, brukerID, filmID, dato, minutter_sett)Skriv SQL for:
a) Finn dei 5 mest populære filmane (flest visningar totalt)
b) Finn brukarar som har sett meir enn 100 timar totalt
c) Finn gjennomsnittleg visningstid per sjanger
d) Lag ei VIEW som viser favorittsjangeren til kvar brukar (sjangeren dei har sett mest)
e) Finn filmar som ingen har sett heilt til slutten (minutter_sett < lengdeMinutter for alle visningar)
f) Føreslå kva indeksar du ville oppretta på desse tabellane og forklar kvifor
Oppsummering
I dette kapittelet har du lært:
- JOIN: kombinerer tabellar (INNER, LEFT, RIGHT, FULL).
- GROUP BY og aggregatfunksjonar: COUNT, SUM, AVG, MIN, MAX.
- HAVING: filtrerer grupper.
- Subqueries: underspørjingar i WHERE og FROM.
- Views og indeksar: virtuelle tabellar og ytingsoptimalisering.
Nøkkelbegrep
| Begrep | Forklaring |
|---|---|
| JOIN | Kombinerer rader frå fleire tabellar |
| View | Virtuell tabell basert på ei spørjing |
| Indeks | Struktur som gjer søk raskare |
Oppgaver
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.