Kapittel 3 · 3A · Forelesning 3 · Lærebok 3.1–3.7, 3.9

SQL — DDL og spørringer

Tabeller, SELECT, aggregat — hovedmotet i SQL. Vi går fra «tom database» til en spørring som svarer på et faktisk spørsmål, og lærer fellene underveis.

01 · DDL

CREATE TABLE og typer

Et relasjonsskjema er en kontrakt: hvilke kolonner finnes, hvilke typer har de, og hvilke rad-kombinasjoner er ulovlige. SQL DDL-en lar deg spesifisere alt dette i én setning. Eksemplet under er hentet fra universitetsdatabasen i lærebokens figur 3.1.

CREATE TABLE department (
  dept_name  VARCHAR(20),
  building   VARCHAR(15),
  budget     NUMERIC(12, 2),
  PRIMARY KEY (dept_name)
);

CREATE TABLE course (
  course_id  VARCHAR(7),
  title      VARCHAR(50),
  dept_name  VARCHAR(20),
  credits    NUMERIC(2, 0),
  PRIMARY KEY (course_id),
  FOREIGN KEY (dept_name) REFERENCES department
);

Datatyper du må kjenne

TypeBeskrivelseEksempel
CHAR(n)Fast lengde — padder med blanke. Vær varsom: 'Avi' blir 'Avi       ' i CHAR(10).CHAR(2) for landskode
VARCHAR(n)Variabel lengde opp til n. Foretrukket for tekst.VARCHAR(50) for navn
INT / SMALLINTHeltall, maskinavhengig størrelse.INT for antall
NUMERIC(p, d)Eksakt desimaltall: p totale sifre, d etter desimaltegnet.NUMERIC(12,2) for valuta
REAL / DOUBLE PRECISIONFlyttall — bruk når presisjon er mindre kritisk.Sensordata
DATE / TIME / TIMESTAMPDato, klokkeslett, eller dato + klokkeslett (evt. med tidssone).TIMESTAMP '2026-04-26 09:00:30'
INTERVALTidsperiode. Differanse mellom to dato/tid-verdier gir et interval, og intervall kan legges til dato/tid.INTERVAL '1' DAY
BLOBBinary large object: store binærdata (bilder, video, CAD). DBMS-en lagrer som rådata, applikasjonen tolker.Profilbilde, PDF-vedlegg
CLOBCharacter large object: store tekst­dokumenter. Fungerer som VARCHAR for veldig lange verdier.Bok-kapitler, lange notater
Store objekter

Når en spørring returnerer et BLOB eller CLOB, gir DBMS-en typisk en peker til verdien — ikke selve dataen. Dette unngår at en SELECT av flere rader trekker megabyte ut hver gang.

Praksisregel

Bruk VARCHAR over CHAR. Sammenligning av CHAR og VARCHAR oppfører seg ulikt på tvers av DBMS — det fører til subtile bugger der A = B overraskende returnerer false fordi A er padded.

Integritetskrav på rad-nivå

  • NOT NULL — feltet kan ikke være tomt
  • PRIMARY KEY (a, b, …) — entydig identifikator, implisitt NOT NULL
  • FOREIGN KEY (a) REFERENCES s — verdien må finnes i tabell s's primærnøkkel
  • UNIQUE (a, b) — kombinasjonen må være entydig (men kan være NULL i noen DBMS)
  • CHECK (uttrykk) — predikat på kolonne eller rad

Egendefinerte domener og typer

SQL gir to mekanismer for å gjenbruke datatype-definisjoner i hele skjemaet.

CREATE DOMAIN — type + constraints
CREATE DOMAIN degree_level VARCHAR(10)
  CONSTRAINT dl_check
  CHECK (VALUE IN
    ('Bachelors', 'Masters', 'Doctorate'));

CREATE TABLE grad (
  id CHAR(5),
  level degree_level
);
CREATE TYPE — egendefinert datatype
CREATE TYPE Dollars AS
  NUMERIC(12,2) FINAL;

CREATE TABLE department (
  dept_name VARCHAR(20),
  building VARCHAR(15),
  budget Dollars
);

Forskjell: Begge gjenbruker en navngitt definisjon. DOMAIN kan ha constraints (f.eks. NOT NULL eller CHECK) — TYPE definerer en strengt typisert «egen datatype» som ikke kan blandes med andre uten eksplisitt konvertering. I praksis bruker mange DBMS-er disse litt forskjellig; PostgreSQL har f.eks. både CREATE DOMAIN og CREATE TYPE.

Endre og slette skjema

Legge til kolonne
ALTER TABLE instructor
  ADD phone VARCHAR(15);
Effekt
-- Alle eksisterende rader får
-- phone = NULL
DROP TABLE r
Sletter både skjema og data — irreversibelt.
DELETE FROM r
Tømmer rader, beholder skjemaet.
TRUNCATE r
Som DELETE, men raskere og uten transaksjonsspor (DBMS-spesifikt).
ALTER TABLE
Legg til/fjern kolonner, endre constraints, omdøp tabellen.
Sjekkpunkt · Lett
Du oppretter CREATE TABLE x (id INT PRIMARY KEY, parent_id INT, FOREIGN KEY (parent_id) REFERENCES x);. Deretter prøver du å sette inn INSERT INTO x VALUES (1, 1);. Hva skjer?
Selvreferansen er klassisk for hierarki (ansatt → leder, kategori → forelder). Klassen som ofte feiler i quiz er at første rot-rad må sette parent_id til seg selv eller kreves at FK tillater NULL.
02 · SELECT-grunnform

SELECT — projeksjon, kilde, filter

Tre obligatoriske komponenter: SELECT (hvilke kolonner), FROM (fra hvilke relasjoner), WHERE (hvilke rader). Tenk på dem som π, × og σ i relasjonsalgebra-termer.

Eksempel: enkel én-tabell-spørring

SQL
SELECT name
FROM instructor
WHERE dept_name = 'Comp. Sci.'
  AND salary > 70000;
Resultat
name
Katz
Brandt

Aritmetikk og aliaser

SELECT kan inneholde uttrykk: + − * / og funksjonskall. Bruk AS for å gi et lesbart navn til resultat-kolonnen.

SQL
SELECT ID, name,
       salary AS dagens,
       salary * 1.1 AS etter_paaslag
FROM instructor;
Resultat (utdrag)
IDnamedagensetter_paaslag
10101Srinivasan6500071500.00
22222Einstein95000104500.00
15151Mozart4000044000.00

Flere tabeller — kartesisk produkt + filter

Når FROM har flere relasjoner, bygger SQL først det kartesiske produktet og deretter filtrerer med WHERE. Dette er den mentale modellen — ikke nødvendigvis hva motoren faktisk gjør.

INSTRUCTOR ID name 10101 Srinivasan 22222 Einstein 15151 Mozart TEACHES ID course_id 10101 CS-101 22222 PHY-101 10101 CS-315 FROM × FROM (3·3=9 RADER) i.ID name t.ID course 10101Srinivasan10101CS-101 10101Srinivasan22222PHY-101 10101Srinivasan10101CS-315 22222Einstein10101CS-101 22222Einstein22222PHY-101 22222Einstein10101CS-315 15151Mozart 10101CS-101 15151Mozart 22222PHY-101 15151Mozart 10101CS-315 3 av 9 rader oppfyller WHERE i.ID = t.ID Steg 1: To kildetabeller
Klikk gjennom de tre stegene. Husk: dette er den logiske evalueringen; en moderne optimaliserer vil aldri faktisk lage hele det kartesiske produktet hvis den ikke må.
Steg 1 / 3
Med tall

Med 200 ansatte som hver underviser i 3 emner får du 200·600 = 120 000 rader i mellomresultatet. Bare riktig WHERE redder ytelsen — eller en optimalisator som er smart nok til å unngå hele produktet.

03 · WHERE og NULL

Filtrer rader — og unngå NULL-fellen

Operatorer du har

Sammenligning
= <> < > <= >=
BETWEEN
x BETWEEN a AND bx >= a AND x <= b
IN / NOT IN
dept_name IN ('CS','EE')
LIKE
Strengmønster med % og _
IS NULL
Eneste lovlige test for fravær
AND / OR / NOT
Logiske koblinger — gir tre-verdi-logikk

Tre-verdi-logikk: TRUE, FALSE, UNKNOWN

I SQL er NULL ikke en verdi du sammenligner mot — det er en markør for «ukjent eller fraværende». Sammenligning med NULL gir alltid UNKNOWN, og WHERE beholder kun rader der predikatet er TRUE.

UttrykkResultatHvorfor
5 = NULLUNKNOWNVi vet ikke om x er 5
NULL = NULLUNKNOWNTo ukjente verdier kan være like — eller ikke
x IS NULLTRUE / FALSESpør om fravær, ikke om verdi-likhet
NULL AND TRUEUNKNOWNHvis vi visste, kunne vært TRUE eller FALSE
NULL OR TRUETRUEOR med TRUE er alltid TRUE — uansett
NULL AND FALSEFALSEAND med FALSE er alltid FALSE
Klassisk feller

WHERE x <> 5 returnerer ikke rader hvor x er NULL. Hvis du vil ha «alt unntatt 5, inkludert ukjente», skriv WHERE x <> 5 OR x IS NULL.

Sjekkpunkt · Middels
Tabellen t(x) har radene {1, 2, NULL, 5}. Hvor mange rader returnerer SELECT x FROM t WHERE x <> 5;?
NULL <> 5 er UNKNOWN, ikke TRUE. WHERE filtrerer den bort. Bare 1 og 2 består.
04 · Strenger og LIKE

Tekstmønstre med LIKE

Strenger skrives i enkelt-fnutter: 'Computer'. Et fnutt-tegn inni en streng dobles: 'It''s right'.

Mønster-jokere

  • % matcher vilkårlig tekst (inkludert tom).
  • _ matcher nøyaktig ett tegn.
MønsterMatcherMatcher ikke
'Intro%''Intro to SQL''An Intro'
'%Comp%''Intro to Computer Science', 'Computational Bio''Calculus'
'___''CS1''CS101'
'__%'Alt med ≥2 tegn'A'

Escape-tegn

Vil du matche selve % eller _? Bruk ESCAPE:

SELECT *
FROM products
WHERE code LIKE 'ab\%cd%' ESCAPE '\';
-- Matcher alle koder som starter med "ab%cd"
Case-sensitivitet

Standard SQL er case-sensitiv for likhet. 'comp. sci.' = 'Comp. Sci.' evaluerer til false. MySQL og SQL Server er ofte case-insensitiv som default — enda en grunn til å lese DBMS-dokumentasjonen.

05 · ORDER BY · DISTINCT

Sortering, dedup og top-N

Sortering

SELECT *
FROM instructor
ORDER BY salary DESC, name ASC;

Default er stigende (ASC). Du kan sortere på flere kolonner — første sorteringsnøkkel har høyest prioritet, så bruker SQL den neste til å bryte uavgjorte verdier.

DISTINCT — fjerne duplikater

SQL er multiset som default: spørringer beholder duplikater. Be eksplisitt om mengde-semantikk:

Bag (default)
SELECT dept_name
FROM instructor;
Set
SELECT DISTINCT dept_name
FROM instructor;
Hvorfor multiset?

Å fjerne duplikater krever en sortering eller hash-tabell — kostbart. Standarden lar deg velge når kostnaden er verdt det.

Top-N: LIMIT / FETCH FIRST

SELECT name, salary
FROM instructor
ORDER BY salary DESC
LIMIT 5;
-- I standard SQL: FETCH FIRST 5 ROWS ONLY
Sjekkpunkt · Lett
En tabell person(name) har 4 rader: Anne, Bjørn, Anne, Cato. Hvor mange rader returnerer SELECT DISTINCT name FROM person ORDER BY name;?
DISTINCT fjerner duplikatet av Anne. Resultat: Anne, Bjørn, Cato.
06 · Aggregat og GROUP BY

Fra rader til oppsummeringer

Aggregatfunksjoner reduserer en kolonne med mange rader til én verdi. Standarden definerer fem stykker — alle ignorerer NULL bortsett fra COUNT(*).

FunksjonBeskrivelseNULL?
COUNT(*)Antall raderTeller alle rader
COUNT(col)Antall ikke-NULL verdierHopper over NULL
COUNT(DISTINCT col)Antall distinkte ikke-NULL verdierHopper over NULL
SUM(col)Sum (numerisk)Hopper over NULL
AVG(col)GjennomsnittHopper over NULL
MIN(col) / MAX(col)Minste/størsteHopper over NULL

GROUP BY: ett aggregat per gruppe

GROUP BY deler tabellen i grupper basert på like verdier i én eller flere kolonner. Hver gruppe gir én rad i resultatet.

SQL
SELECT dept_name,
       COUNT(*) AS n_ansatte,
       AVG(salary) AS snittlonn
FROM instructor
GROUP BY dept_name;
Resultat
dept_namen_ansattesnittlonn
Comp. Sci.377 333.33
Physics291 000.00
Finance285 000.00
Regel om SELECT-listen

Hver kolonne i SELECT må enten være i GROUP BY, eller pakkes i en aggregatfunksjon. Ellers er resultatet udefinert: hvilken «name» skal man velge når raden representerer 12 ulike ansatte?

HAVING — filter etter aggregering

WHERE filtrerer rader før gruppering. HAVING filtrerer grupper etter aggregering.

RAW namedept ACS BCS CCS DEE EEE FPHY GPHY HBIO WHERE dept <> 'BIO' EFTER WHERE namedept ACS BCS CCS DEE EEE FPHY GPHY GROUP BY dept · COUNT(*) PER GRUPPE deptcnt CS3 EE2 PHY2 HAVING cnt >= 3 SLUTT deptcnt CS3
Pipeline-en: råtabell → WHERE filtrer rader → GROUP BY bunter → HAVING filtrerer grupper. Klikk gjennom stegene under.
Steg 1 / 4
SELECT dept_name, COUNT(*) AS cnt
FROM instructor
WHERE dept_name <> 'BIO'
GROUP BY dept_name
HAVING COUNT(*) >= 3;
Sjekkpunkt · Vanskelig
Tabellen p(grade) har {5, 4, NULL, 3, NULL, 4}. Hva returnerer SELECT COUNT(*), COUNT(grade), AVG(grade) FROM p;?
COUNT(*) ser alle 6 radene. COUNT(grade) hopper over de 2 NULL-ene → 4. AVG ignorerer også NULL: (5+4+3+4)/4 = 4.
07 · Mengdeoperatorer

UNION · INTERSECT · EXCEPT

Kombiner to spørringer som om de var sett. Krav: begge spørringene må ha samme antall kolonner og kompatible typer.

A UNION B
begge sett, dedup
A INTERSECT B
overlapp
A EXCEPT B
i A men ikke i B

Eksempel: emner som ble undervist i begge semestrene

(SELECT course_id FROM section
 WHERE semester = 'Fall'   AND year = 2017)
INTERSECT
(SELECT course_id FROM section
 WHERE semester = 'Spring' AND year = 2018);
UNION vs UNION ALL

UNION fjerner duplikater (set-semantikk). UNION ALL beholder dem (multiset-semantikk). Hvis du vet at kildene ikke har duplikater, er UNION ALL raskere — den slipper sortering.

Sjekkpunkt · Middels
A = {1, 2, 2, 3} og B = {2, 3, 4} (begge multiset). Hva returnerer A UNION ALL B?
UNION ALL slår sammen alle rader — duplikater og alt. UNION (uten ALL) ville gitt {1, 2, 3, 4}.
08 · INSERT · UPDATE · DELETE

Endre data

INSERT

-- Eksplisitt verdier
INSERT INTO instructor (ID, name, dept_name, salary)
VALUES ('77777', 'Lovelace', 'CS', 85000);

-- Sett inn fra spørring
INSERT INTO hi_paid (ID, name)
SELECT ID, name
FROM instructor
WHERE salary > 100000;

UPDATE

UPDATE instructor
SET salary = salary * 1.05
WHERE dept_name = 'CS';
Glem aldri WHERE

UPDATE instructor SET salary = 0; oppdaterer alle radene. Dette er den klassiske produksjons-katastrofen som kan stoppes ved å alltid teste UPDATE-er først som en SELECT med samme WHERE.

DELETE

DELETE FROM instructor
WHERE dept_name = 'BIO'
  AND salary < 50000;

CASE-uttrykk i SET

UPDATE instructor
SET salary =
  CASE
    WHEN salary < 60000 THEN salary * 1.10
    ELSE                       salary * 1.03
  END;

CASE er det nærmeste SQL kommer en if/else, og fungerer både i SELECT, WHERE og SET.

Sjekkpunkt · Middels
Du skal gi alle ansatte med salary ≤ 100000 en lønnsøkning på 5 %, og resten 3 %, i én kjøring. Hvilken SQL er korrekt?
CASE WHEN ... THEN ... ELSE ... END er SQL-standardens betingede uttrykk og kan brukes hvor som helst et uttrykk er lov — også inne i SET. To-UPDATE-løsningen virker, men er ikke nødvendig: hvis du kjører dem i feil rekkefølge, får ansatte i grenseland to økninger. IF() finnes i MySQL, men er ikke standard.
Oppsummering

Det du skal kunne

  1. Skrive CREATE TABLE med riktige typer, primær- og fremmednøkler, og forklare hvorfor hver constraint er der.
  2. Lese en SELECT-spørring og gjenkjenne den logiske evalueringsrekkefølgen (FROM → WHERE → GROUP → HAVING → SELECT → ORDER → LIMIT).
  3. Forklare tre-verdi-logikken og hvorfor = NULL aldri matcher.
  4. Skille mellom WHERE (filtrer rader) og HAVING (filtrer grupper).
  5. Bruke UNION, INTERSECT og EXCEPT samt deres ALL-varianter.
  6. Vite at SELECT-listen ved GROUP BY enten må være i GROUP-listen eller pakket i et aggregat.

Klar for joins og subqueries? Gå til 3B → Joins og subqueries.