# SQL

## Vad det är

SQL är ett deklarativt språk för att definiera, ändra och läsa data som ligger i
relationstabeller: du skriver ned resultatet du vill ha, och databasen avgör hur
det ska tas fram. Bokstäverna brukar utläsas Structured Query Language och
uttalas antingen bokstav för bokstav eller som ”sequel”, medan den
internationella standarden som definierar språket, ISO/IEC 9075, helt enkelt
kallar det Database Language SQL. En enskild sats som läser data kallas ofta en
SQL-fråga.

Deklarativ är ordet som gör jobbet. Satsen nedan namnger en tabell, ett villkor,
en gruppering och en ordning. Den säger ingenting om index, loopar eller vilka
rader som ska läsas först:

```sql
SELECT country, count(*) AS signups
FROM accounts
WHERE created_at >= now() - interval '30 days'
GROUP BY country
ORDER BY signups DESC;
```

I PostgreSQL passerar den texten fyra steg. En parser kontrollerar syntaxen och
bygger ett träd, ett omskrivningssteg expanderar de vyer som satsen nämner, en
planerare jämför de möjliga sätten att köra den, t.ex. en sekventiell
genomläsning mot ett index, och en exekverare hämtar till sist raderna. Samma
mening kan ta en millisekund eller en minut beroende på vad planeraren hittar,
och därför hör det till att skriva SQL att kunna läsa en plan.

Språket har delar som brukar få egna namn. Datadefinition skapar och ändrar
tabeller, scheman och index. Datamanipulation infogar, uppdaterar, raderar och
slår ihop rader. Frågor läser dem med SELECT. Behörigheter och transaktioner
avgör vem som får göra vad och vilka ändringar som landar tillsammans. Allt
detta färdas som text över en enda anslutning, och därför är SQL den gemensamma
marken mellan en applikation, ett rapportverktyg och en människa vid en
terminal.

Standarden har reviderats många gånger, senast som SQL:2023, och varje
databasmotor implementerar sin egen delmängd av den. Bilagan om överensstämmelse
i PostgreSQL anger att motorn uppfyller minst 170 av de 177 obligatoriska
Core-funktionerna, och att inget databassystem när texten skrevs påstod sig
uppfylla hela Core. Varje motor lägger dessutom till en egen dialekt: Microsoft
SQL Server talar Transact-SQL, BigQuery talar GoogleSQL, och PostgreSQL lägger
till PL/pgSQL för procedurkod som körs inuti databasen.

## Varför det spelar roll

Det beslut som SQL ställer ett team inför är var arbetet med data sker: inuti
databasen, i det språk den byggdes för att köra, eller i applikationskod som
hämtar rader och bearbetar dem själv.

En mängdbaserad sats körs bredvid datan. Att filtrera en miljon rader ned till
fyrtio inuti databasen skickar fyrtio rader över nätverket; att filtrera i
applikationen skickar en miljon och kastar de flesta. Joins, aggregat och
borttagning av dubbletter följer samma aritmetik. Att flytta den sortens arbete
in i SQL är ofta den billigaste uppsnabbning som ett långsamt system har
tillgång till, eftersom den körs på den databas som redan finns.

Ägarskapet är motvikten. Logik som skrivs i SQL bor i migreringar och
funktioner, som behöver samma granskning, samma tester och samma
versionshantering som all annan kod, och färre utvecklare läser en lång
PL/pgSQL-funktion lika obehindrat som de läser Go eller Python. En fungerande
uppdelning skickar allt som måste se många rader till databasen och behåller
regler som ändras med verksamheten i applikationen, där de är lättare att testa
var för sig.

Portabiliteten är den andra avvägningen. Kärnan i språket, SELECT med joins,
GROUP BY och ORDER BY, flyttar mellan motorer med få ändringar. Kanterna flyttar
dåligt: datumaritmetik, upserts, paginering, JSON-funktioner och procedurkod
skiljer sig alla åt, och en migrering från en databas till en annan lägger det
mesta av sin möda där. Att välja motor är också att välja dialekt, och ju mer
logik som bor i den dialekten, desto mer kostar en senare flytt.

Rekryteringen är den tredje. SQL är en av få färdigheter som analytiker,
backendutvecklare, dataingenjörer och databasadministratörer delar, så ett
dataåtkomstlager skrivet i ren SQL kan läsas och granskas av vem som helst av
dem. En ORM döljer SQL tills en sida blir långsam, och då måste någon ändå läsa
vad den genererade.

## Så ser det ut i praktiken

På en plattform sätter PostgreSQL-funktioner ihop varje svar från API:et som ett
komplett JSON-dokument, och Go-handlern skickar det vidare som `json.RawMessage`
utan att avkoda eller koda om det.
[Arkitekturen som skickar JSON rakt igenom](/sv/portfolio/designed-a-json-passthrough-architecture-where-postgresql-functions-58/)
gör formningen av svaret till en fråga för SQL, utförd där datan redan finns. En
liten hjälpfunktion, QueryJSON, gjorde mönstret till det lättaste att följa, och
de DTO:er, mappningsfunktioner och svarsstructar som ett lagerindelat API brukar
samla på sig försvann. Handlerkoden blev mycket kortare, och en ändring av vad
en funktion returnerar når klienten utan att någon redigerar en handler.

Samma språk ser annorlunda ut i en datapipeline. I
[sex ETL/ELT-pipelines](/sv/portfolio/designed-implemented-and-administered-6-etl-elt-pipelines-8/)
som spände över BigQuery, MSSQL och PostgreSQL lästes råfiler in i scheman för
mellanlagring med bcp, psql och SSIS, och rensningen, normaliseringen och
berikningen skrevs som procedurer i PL/pgSQL och T-SQL. Tre motorer, var och en
med sin egen dialekt, i ett och samma jobb: frågan om portabilitet ovan, sedd
inifrån. Sidan anger resultatet som över tre timmars manuellt arbete som krympte
till under 20 minuter för hela flödet, data som uppdaterades dagligen i stället
för veckovis och Python-API:er som läste den och blev 30 % snabbare.

## Var det går fel

**Satser som limmas ihop av strängar.** En kund som heter O'Brien får en söksida
att gå sönder, eller en konstruerad inmatning läser någon annans data. Orsaken
är att användarens indata klistras in i SQL-texten, där databasen inte kan
skilja data från kommando. Skicka i stället värden som parametrar, separat från
satsen, vilket varje etablerad drivrutin tillåter. I klientbiblioteket som
PostgreSQL själv levererar accepterar ett parametriserat anrop dessutom bara ett
kommando per sträng, vilket ger en andra försvarslinje mot injektion.

**Ordning som bara var tur.** En lista som har kommit tillbaka sorterad i
månader dyker upp omkastad efter en datainläsning eller en ny plan. Utan ORDER
BY returnerar PostgreSQL rader i den ordning som går snabbast att ta fram, och
en LIMIT utan ORDER BY kan till och med returnera en annan delmängd varje gång.
Varje resultat vars ordning spelar roll säger det i frågan.

**NULL som behandlas som ett värde.** Ett filter skrivet `WHERE discount = NULL`
returnerar ingenting, och `NOT IN` mot en delfråga som innehåller en NULL
returnerar heller ingenting. Varje jämförelse med NULL ger okänt snarare än sant
eller falskt, och en WHERE-sats behåller bara de rader där villkoret är sant.
Skriv `IS NULL`, och föredra `NOT EXISTS` framför `NOT IN` när den inre kolumnen
kan innehålla en NULL.

**Dialekt som läcker in i testerna.** En testsvit går igenom mot SQLite och
samma kod fallerar mot PostgreSQL i produktion, eller så snubblar en flytt till
SQL Server på varje `LIMIT`, som den motorn stavar `TOP` eller `OFFSET ...
FETCH`. Varje motor accepterar saker som de andra avvisar. Testa mot den motor
som du driftsätter, och håll motorspecifik syntax på så få ställen som möjligt.

## Läs mer

- [Part II. The SQL Language](https://www.postgresql.org/docs/current/sql.html)
  — den fullständiga handledningen och referensen för språket från PostgreSQL,
  från syntax till tips om prestanda.
- [Appendix D. SQL Conformance](https://www.postgresql.org/docs/current/features.html)
  — delarna av ISO/IEC 9075 och vilka funktioner PostgreSQL implementerar.
- [Transact-SQL Reference](https://learn.microsoft.com/en-us/sql/t-sql/language-reference)
  — dialekten i SQL Server, dokumenterad av Microsoft.
- [Introduction to SQL in BigQuery](https://cloud.google.com/bigquery/docs/introduction-sql)
  — GoogleSQL och den äldre dialekt som den ersatte.
- [Query Language Understood by SQLite](https://www.sqlite.org/lang.html)
  — syntaxen i en inbäddad motor, sats för sats.

<https://engineer.company/sv/glossary/sql/>
