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 / TIME / TIMESTAMP | Dato, klokkeslett, eller dato + klokkeslett (evt. med tidssone). | TIMESTAMP '2026-04-26 09:00:30' |
| INTERVAL | Tidsperiode. Differanse mellom to dato/tid-verdier gir et interval, og intervall kan legges til dato/tid. | INTERVAL '1' DAY |
| BLOB | Binary large object: store binærdata (bilder, video, CAD). DBMS-en lagrer som rådata, applikasjonen tolker. | Profilbilde, PDF-vedlegg |
| CLOB | Character large object: store tekstdokumenter. Fungerer som VARCHAR for veldig lange verdier. | Bok-kapitler, lange notater |
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.
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
Egendefinerte domener og typer
SQL gir to mekanismer for å gjenbruke datatype-definisjoner i hele skjemaet.
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 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
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.
salary ≤ 100000 en lønnsøkning på 5 %, og resten 3 %, i én kjøring. Hvilken SQL er korrekt?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.