Hoppa till innehållet
AI-grafen
D· AI-utvecklareprogrammering· ca 45 min· grundläggande — ändras sällan· verifierad 2026-09-20

SQL — grunderna

Kunna skriva SELECT med filter, join och gruppering mot en relationsdatabas.

Förkunskaper

Intuition

SQL är ett språk där du beskriver vad du vill ha, inte hur det ska hämtas. Databasen räknar ut hur.

SELECT klass, avg(poang) AS medel
FROM   resultat
WHERE  poang IS NOT NULL
GROUP BY klass
HAVING count(*) >= 5
ORDER BY medel DESC
LIMIT 10;

Läs uppifrån och ner, men tänk i den ordning databasen kör:

OrdningNyckelordGör
1FROM / JOINvilka tabeller
2WHEREfiltrera rader
3GROUP BYslå ihop till grupper
4HAVINGfiltrera grupper
5SELECTvälj kolumner
6ORDER BYsortera
7LIMITbegränsa

Den ordningen förklarar den vanligaste nybörjarfrågan: varför kan jag inte använda WHERE count(*) > 5? Därför att WHERE körs innan grupperingen finns. Grupper filtreras med HAVING.

Formellt

JOIN-typerna:

TypBehåller
INNER JOINbara rader som matchar i båda
LEFT JOINalla från vänstra, NULL där högra saknas
RIGHT JOINtvärtom
FULL OUTER JOINalla från båda

Använd LEFT JOIN när du vill behålla alla i vänstertabellen — till exempel alla elever, även de som inte gjort något prov. Ett INNER JOIN skulle tyst kasta bort dem, och det är lätt att inte märka.

NULL beter sig annorlunda än du tror. NULL betyder «okänt», inte «tomt»:

UttryckResultat
NULL = NULLNULL (inte sant!)
NULL <> 5NULL
x IS NULLsant/falskt — rätt sätt
count(*)räknar alla rader
count(kolumn)räknar rader där kolumnen inte är NULL
avg(kolumn)hoppar över NULL

Skillnaden mellan count(*) och count(kolumn) är en klassisk källa till fel siffror i rapporter.

SQL-injektion. Bygg aldrig frågor med stränginterpolation:

cur.execute(f"SELECT * FROM elev WHERE namn = '{namn}'")   # ALDRIG
cur.execute("SELECT * FROM elev WHERE namn = %s", (namn,))  # rätt

Med parametrar skickas värdet separat från frågan, och databasen kan aldrig tolka det som kod. Det är inte en optimering utan den enda korrekta metoden — och det gäller även när värdet «kommer inifrån systemet», eftersom det sällan förblir så.

När SQL och när Pandas? Filtrera och aggregera i databasen — den är byggd för det och du slipper flytta data. Hämta hem det som blir kvar och gör det sista i Pandas. Att hämta en miljon rader för att räkna ett medelvärde är nästan alltid fel väg.

Kod

-- Tabeller
CREATE TABLE elev (id int primary key, namn text, klass text);
CREATE TABLE prov (id int primary key, elev_id int references elev(id),
                   amne text, poang int, datum date);

-- Alla elever med sitt snitt — även de utan prov (LEFT JOIN)
SELECT e.namn,
       e.klass,
       count(p.id)        AS antal_prov,
       round(avg(p.poang), 1) AS snitt
FROM   elev e
LEFT JOIN prov p ON p.elev_id = e.id
GROUP BY e.id, e.namn, e.klass
ORDER BY snitt DESC NULLS LAST;

-- Klasser med minst 5 prov och snitt över 70
SELECT e.klass, count(*) AS n, round(avg(p.poang), 1) AS snitt
FROM   prov p JOIN elev e ON e.id = p.elev_id
WHERE  p.poang IS NOT NULL          -- filtrerar RADER
GROUP BY e.klass
HAVING count(*) >= 5 AND avg(p.poang) > 70   -- filtrerar GRUPPER
ORDER BY snitt DESC;

-- count(*) mot count(kolumn): olika svar när det finns NULL
SELECT count(*) AS alla_rader, count(poang) AS med_poang FROM prov;
# Från Python — alltid med parametrar
import psycopg

with psycopg.connect(dsn) as conn, conn.cursor() as cur:
    cur.execute(
        """SELECT e.klass, round(avg(p.poang), 1)
           FROM prov p JOIN elev e ON e.id = p.elev_id
           WHERE p.datum >= %s
           GROUP BY e.klass
           HAVING count(*) >= %s""",
        ("2026-01-01", 5),
    )
    for klass, snitt in cur.fetchall():
        print(klass, snitt)

Behärskning innebär

  • Skriver SELECT med WHERE och ORDER BY
  • Använder JOIN mellan tabeller
  • Grupperar med GROUP BY och HAVING

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser