Tilbake
6.4
SQL – avanserte spørringer og JOIN

6.4 SQL – avanserte spørringer og JOIN

Lær JOIN, GROUP BY, HAVING og subqueries for komplekse dataspørringer.

70 min
8 oppgaver
JOININNER JOINLEFT JOINGROUP BY
Du leser den tradisjonelle versjonen
Din fremgang i kapitlet
0 / 8 oppgaver

SQL – avanserte spørringer og sammenkoblinger

Hittil har vi jobbet med data fra én tabell om gangen. Men den virkelige kraften i relasjonsdatabaser ligger i muligheten til å koble sammen data fra flere tabeller. Tenk deg at du vil se elevnavn sammen med fagnanvn og karakterer – den informasjonen er fordelt over tre tabeller (elever, fag og karakterer). For å sette dette sammen trenger vi JOIN.

I tillegg skal vi lære å beregne statistikk med aggregeringsfunksjoner (telle, summere, finne gjennomsnitt), gruppere data med GROUP BY, filtrere grupper med HAVING, og bruke underspørringer for å løse komplekse dataspørsmål.

Disse teknikkene er det som gjør SQL til et kraftig analyseverktøy. Med dem kan du svare på spørsmål som «Hva er gjennomsnittskarakteren per fag?», «Hvilke elever har karakter over gjennomsnittet?» og «Hvilken klasse har flest elever med toppkarakter?».

En JOIN kobler rader fra to tabeller basert på en felles kolonne. Den vanligste formen er INNER JOIN, som returnerer bare rader der det finnes en match i begge tabellene.

Syntaks for INNER JOIN

SELECT kolonne1, kolonne2, ...
FROM tabell1
INNER JOIN tabell2 ON tabell1.kolonne = tabell2.kolonne;

Eksempel: Elever med klassenavn

I stedet for å se bare klasse_id (som er et tall), vil vi se klassenavnet:

SELECT elever.fornavn, elever.etternavn, klasser.klassenavn
FROM elever
INNER JOIN klasser ON elever.klasse_id = klasser.klasse_id;

Resultat:

fornavnetternavnklassenavn
EmmaHansen10A
OliverJohansen10A
SofieBerg10A
NoraOlsen10B
JakobLarsen10B
NoahAndreassen10B
LiamDahl10C
EllaNilsen10C

Tabellaliaser


For å gjøre spørringene kortere kan vi gi tabellene aliaser:
SELECT e.fornavn, e.etternavn, k.klassenavn
FROM elever e
INNER JOIN klasser k ON e.klasse_id = k.klasse_id;
Her er e et alias for elever og k et alias for klasser.

✏️Eksempel: JOIN over tre tabeller

La oss koble elever, karakterer og fag for å se en komplett karakteroversikt med navn:

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):

fornavnetternavnfagnavnkaraktertermin
NoahAndreassenMatematikk3H2024
NoahAndreassenNorsk2H2024
SofieBergMatematikk5H2024
SofieBergNaturfag4H2024
SofieBergNorsk5H2024
...............

Her kobler vi tre tabeller: karakterer med elever (via elevid), og karakterer med fag (via fagid). Resultatet gir oss lesbare navn i stedet for ID-nummer.

Typer JOIN

Det finnes flere typer JOIN:

INNER JOIN – Returnerer bare rader der det finnes match i begge tabellene. Rader uten match utelates.

LEFT JOIN (LEFT OUTER JOIN) – Returnerer alle rader fra venstre tabell, og matchende rader fra høyre tabell. Hvis ingen match finnes, fylles høyre side med NULL.

RIGHT JOIN (RIGHT OUTER JOIN) – Returnerer alle rader fra høyre tabell, og matchende rader fra venstre. Motsatt av LEFT JOIN. (Merk: RIGHT JOIN støttes ikke i SQLite, men kan simuleres ved å bytte tabellrekkefølgen med LEFT JOIN.)

FULL OUTER JOIN – Returnerer alle rader fra begge tabellene, med NULL der det ikke er match. (Støttes heller ikke direkte i SQLite.)

✏️Eksempel: LEFT JOIN – alle elever, også uten karakterer

INNER JOIN viser bare elever som har karakterer. Med LEFT JOIN får vi med alle elever, selv de som mangler karakterer:

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;

Hvis en elev ikke har noen karakterer, vises NULL i kolonnene fagnavn og karakter:

fornavnetternavnfagnavnkarakter
EmmaHansenMatematikk5
EmmaHansenNorsk4
EmmaHansenNaturfag5
MajaStrandNULLNULL

Her ser vi at Maja Strand (som vi satte inn tidligere) ikke har noen karakterer ennå. Med INNER JOIN ville Maja vært usynlig i resultatet.
Tommelfingerregel: Bruk INNER JOIN når du bare vil ha rader som matcher i begge tabeller. Bruk LEFT JOIN når du vil beholde alle rader fra den «venstre» tabellen, uansett om de har matchende rader i den andre.

Aggregeringsfunksjoner beregner én verdi basert på mange rader. De vanligste er:

FunksjonBeskrivelseEksempel
COUNT()Teller antall raderCOUNT(*) eller COUNT(kolonne)
SUM()Summerer verdierSUM(karakter)
AVG()Beregner gjennomsnittAVG(karakter)
MIN()Finner laveste verdiMIN(karakter)
MAX()Finner høyeste verdiMAX(karakter)

Eksempler


-- 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 et forståelig navn (alias).

GROUP BY grupperer rader med like verdier slik at aggregeringsfunksjoner beregnes per gruppe.

Eksempel: Antall elever per klasse

SELECT klasse_id, COUNT(*) AS antall_elever
FROM elever
GROUP BY klasse_id;

Resultat:

klasseidantallelever
13
23
32

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:
fagnavnsnittkarakter
Matematikk4.0
Naturfag4.67
Norsk3.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:

fornavnetternavnantall_fag
EmmaHansen3
OliverJohansen3
NoraOlsen3
JakobLarsen2
.........
HAVING filtrerer grupper etter at GROUP BY har gjort sin jobb. 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:

fagnavnsnittkarakter
Naturfag4.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 bare H2024-karakterer, deretter grupperer GROUP BY etter fag, og til slutt filtrerer HAVING bort grupper med snitt under 3.5.

En subquery (underspørring) er en SELECT-setning som er nestet inne i en annen spørring. Underspørringen kjøres først, og resultatet brukes av den ytre spørringen.

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 beregner underspørringen først gjennomsnittskarakteren, og den ytre spørringen 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ørringen finner alle elev_id-er med karakter 6, og den ytre spørringen henter navnene til disse elevene.

Subquery i FROM (avledede tabeller)

-- 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 lager underspørringen en midlertidig tabell (elevsnitt) med gjennomsnitt per elev, og den ytre spørringen filtrerer denne.

✏️Eksempel: Komplett analyse av skoledatabasen

La oss bruke alle teknikkene vi har lært for å lage en omfattende analyse:

1. Karakteroversikt med klassenavn og fagnavn:

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) avrunder gjennomsnittet til 2 desimaler, og COUNT(DISTINCT e.elev_id) teller unike elever (ikke antall karakterrader).

📝Oppgave 6.4.1

Hva gjør en INNER JOIN?

📝Oppgave 6.4.2

Hvilken aggregeringsfunksjon beregner gjennomsnittet av verdier i en kolonne?

📝Oppgave 6.4.3

Hva er forskjellen mellom WHERE og HAVING?

📝Oppgave 6.4.4

Skriv SQL-spørringer for følgende oppgaver (bruk skoledatabasen):

a) Vis fornavn, etternavn og klassenavn for alle elever (bruk JOIN)

b) Beregn gjennomsnittskarakteren per elev (vis fornavn og gjennomsnitt)

c) Tell antall elever per klasse (vis klassenavn og antall)

📝Oppgave 6.4.5

Hva returnerer denne spørringen?

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;

📝Oppgave 6.4.6

Skriv SQL-spørringer for følgende oppgaver:

a) Vis elevnavn, fagnavn og karakter for alle karakterer som er over gjennomsnittskarakteren (bruk subquery)

b) Finn alle elever som har fått karakter 6 i minst ett fag – vis navnet og faget de fikk 6 i

c) Vis klassestatistikk: klassenavn, antall elever, gjennomsnittskarakter (avrundet til 1 desimal), høyeste og laveste karakter – men bare for klasser med gjennomsnitt over 3.5

📝Oppgave 6.4.7

Hva er forskjellen mellom INNER JOIN og LEFT JOIN i denne situasjonen?

Du har 8 elever i elevtabellen, men bare 6 av dem har karakterer 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;

📝Oppgave 6.4.8

Du har nettbutikk-databasen fra kapittel 6.2. Skriv SQL-spørringer for:

a) Vis alle bestillinger med kundenavn (fornavn og etternavn), bestillingsdato og totalpris – sortert etter dato

b) Finn total omsetning (sum av alle totalpris) per kunde – vis bare kunder med total omsetning over 1000 kr

c) Finn den mest bestilte produktet (produktet som har flest bestillingslinjer). Vis produktnavn og antall ganger det er bestilt.

Oppsummering

I dette kapittelet har du lært:

- JOIN: kobler sammen data fra flere tabeller.
- Typer JOIN: INNER, LEFT, RIGHT og FULL.
- Aggregeringsfunksjoner: COUNT, SUM, AVG, MIN og MAX.
- GROUP BY og HAVING: grupperer og filtrerer grupper.
- Subqueries: underspoerringer for komplekse spørringer.

Noekkelbegreper


BegrepForklaring
JOINKobler data fra flere tabeller
GROUP BYGrupperer rader for aggregering
SubquerySpørring inne i en annen spørring

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.