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.
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
| Type | Beskrivelse | Eksempel |
|---|---|---|
| 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 / SMALLINT | Heltall, maskinavhengig størrelse. | INT for antall |
| NUMERIC(p, d) | Eksakt desimaltall: p totale sifre, d etter desimaltegnet. | NUMERIC(12,2) for valuta |
| REAL / DOUBLE PRECISION | Flyttall — bruk når presisjon er mindre kritisk. | Sensordata |
| DATE / TIMESTAMP | Dato eller dato + klokkeslett (med eller uten tidssone). | DATE '2026-04-26' |
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 tomtPRIMARY KEY (a, b, …)— entydig identifikator, implisitt NOT NULLFOREIGN KEY (a) REFERENCES s— verdien må finnes i tabell s's primærnøkkelUNIQUE (a, b)— kombinasjonen må være entydig (men kan være NULL i noen DBMS)CHECK (uttrykk)— predikat på kolonne eller rad
Endre og slette skjema
ALTER TABLE instructor
ADD phone VARCHAR(15);
-- Alle eksisterende rader får
-- phone = NULL
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?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
SELECT name
FROM instructor
WHERE dept_name = 'Comp. Sci.'
AND salary > 70000;
| name |
|---|
| Katz |
| Brandt |
Aritmetikk og aliaser
SELECT kan inneholde uttrykk: + − * / og funksjonskall. Bruk AS for å gi et lesbart navn til resultat-kolonnen.
SELECT ID, name,
salary AS dagens,
salary * 1.1 AS etter_paaslag
FROM instructor;
| ID | name | dagens | etter_paaslag |
|---|---|---|---|
| 10101 | Srinivasan | 65000 | 71500.00 |
| 22222 | Einstein | 95000 | 104500.00 |
| 15151 | Mozart | 40000 | 44000.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.
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.
Filtrer rader — og unngå NULL-fellen
Operatorer du har
= <> < > <= >=x BETWEEN a AND b ⇔ x >= a AND x <= bdept_name IN ('CS','EE')% og _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.
| Uttrykk | Resultat | Hvorfor |
|---|---|---|
5 = NULL | UNKNOWN | Vi vet ikke om x er 5 |
NULL = NULL | UNKNOWN | To ukjente verdier kan være like — eller ikke |
x IS NULL | TRUE / FALSE | Spør om fravær, ikke om verdi-likhet |
NULL AND TRUE | UNKNOWN | Hvis vi visste, kunne vært TRUE eller FALSE |
NULL OR TRUE | TRUE | OR med TRUE er alltid TRUE — uansett |
NULL AND FALSE | FALSE | AND med FALSE er alltid FALSE |
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.
t(x) har radene {1, 2, NULL, 5}. Hvor mange rader returnerer SELECT x FROM t WHERE x <> 5;?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ønster | Matcher | Matcher 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"
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.
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:
SELECT dept_name
FROM instructor;
SELECT DISTINCT dept_name
FROM instructor;
Å 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
person(name) har 4 rader: Anne, Bjørn, Anne, Cato. Hvor mange rader returnerer SELECT DISTINCT name FROM person ORDER BY name;?Fra rader til oppsummeringer
Aggregatfunksjoner reduserer en kolonne med mange rader til én verdi. Standarden definerer fem stykker — alle ignorerer NULL bortsett fra COUNT(*).
| Funksjon | Beskrivelse | NULL? |
|---|---|---|
COUNT(*) | Antall rader | Teller alle rader |
COUNT(col) | Antall ikke-NULL verdier | Hopper over NULL |
COUNT(DISTINCT col) | Antall distinkte ikke-NULL verdier | Hopper over NULL |
SUM(col) | Sum (numerisk) | Hopper over NULL |
AVG(col) | Gjennomsnitt | Hopper over NULL |
MIN(col) / MAX(col) | Minste/største | Hopper 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.
SELECT dept_name,
COUNT(*) AS n_ansatte,
AVG(salary) AS snittlonn
FROM instructor
GROUP BY dept_name;
| dept_name | n_ansatte | snittlonn |
|---|---|---|
| Comp. Sci. | 3 | 77 333.33 |
| Physics | 2 | 91 000.00 |
| Finance | 2 | 85 000.00 |
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.
SELECT dept_name, COUNT(*) AS cnt
FROM instructor
WHERE dept_name <> 'BIO'
GROUP BY dept_name
HAVING COUNT(*) >= 3;
p(grade) har {5, 4, NULL, 3, NULL, 4}. Hva returnerer SELECT COUNT(*), COUNT(grade), AVG(grade) FROM p;?UNION · INTERSECT · EXCEPT
Kombiner to spørringer som om de var sett. Krav: begge spørringene må ha samme antall kolonner og kompatible typer.
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 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.
A UNION ALL B?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';
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.
Det du skal kunne
- Skrive
CREATE TABLEmed riktige typer, primær- og fremmednøkler, og forklare hvorfor hver constraint er der. - Lese en SELECT-spørring og gjenkjenne den logiske evalueringsrekkefølgen (FROM → WHERE → GROUP → HAVING → SELECT → ORDER → LIMIT).
- Forklare tre-verdi-logikken og hvorfor
= NULLaldri matcher. - Skille mellom
WHERE(filtrer rader) ogHAVING(filtrer grupper). - Bruke
UNION,INTERSECTogEXCEPTsamt deres ALL-varianter. - 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.