När en rapport behöver visa kunder vars ordervärde överstiger genomsnittet kan en SQL-subquery lösa det utan att först skapa en separat tabell. Men rätt variant beror på om den inre frågan ska ge ett enda värde, en lista eller bara bekräfta att en matchning finns.

En nästlad fråga fungerar bäst när dess uppgift är tydlig
- Ett värde: använd en skalär subquery, till exempel för att jämföra med ett genomsnitt.
- En lista: använd ofta
INnär flera värden kan matcha. - En träffkontroll:
EXISTSpassar när du vill hitta rader med relaterade poster. - En fallgrop:
NOT INkan ge oväntat resultat om listan innehållerNULL.
Välj subquery efter vad den inre frågan ska lämna tillbaka
En nästlad SQL-fråga är mest lättläst när du först bestämmer dess jobb. Ska den leverera ett enda värde, flera möjliga värden eller bara ett ja eller nej? Det avgör vilken form som passar.
Anta att tabellen orders innehåller kolumnerna customer_id och amount. En skalär subquery kan jämföra varje order med genomsnittet:
SELECT customer_id, amount
FROM orders
WHERE amount > (
SELECT AVG(amount)
FROM orders
);Den inre frågan räknar fram ett värde, och den yttre väljer order över det värdet. Om subqueryn i stället returnerar flera rader fungerar en vanlig jämförelse som = inte som tänkt; använd exempelvis IN eller bygg om frågan.
IN och EXISTS svarar på två olika frågor
Två SQL-frågor kan se nästan likadana ut men uttrycka olika saker. IN frågar om ett värde finns i en lista; EXISTS frågar om den inre frågan hittar minst en rad.
Exempelvis kan du hitta kunder som har lagt minst en order så här:
SELECT c.customer_id
FROM customers AS c
WHERE EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.customer_id
);Vill du hellre matcha mot en lista med kund-ID:n kan IN vara mer direkt. Jag tycker att EXISTS ofta uttrycker avsikten tydligast när frågan gäller om en relaterad rad finns, särskilt eftersom du då inte behöver välja ut själva värdet från den inre frågan. Databasmotorn kan dessutom ofta sluta leta när den hittar en matchning, enligt PostgreSQL-dokumentationen om subquery-uttryck.
En NULL i NOT IN-listan kan slå ut alla träffar
Resultatet kan bli tomt utan att frågan ger ett tydligt fel. Om den inre listan innehåller NULL kan villkoret NOT IN bli okänt för rader som du väntade dig skulle passera.
Föreställ dig att du vill lista kunder utan order. Om ordertabellens customer_id innehåller en null-post kan det här villkoret ställa till det:
WHERE c.customer_id NOT IN (
SELECT o.customer_id
FROM orders AS o
)En tydlig alternativ formulering är NOT EXISTS, där kopplingen mellan kund och order står inne i subqueryn:
WHERE NOT EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.customer_id
)Praktisk tumregel: om värdena i den inre frågan kan vara NULL, välj NOT EXISTS eller filtrera bort nullvärden uttryckligen innan du använder NOT IN. Det här är en fråga om korrekt resultat, inte bara kodstil.
Korrelaterade subqueries behöver en tydlig koppling
I exemplet med kunder och order använder den inre frågan ett värde från den yttre frågan: c.customer_id. Det gör subqueryn korrelerad. Den beskriver alltså ett samband mellan raderna, snarare än en fristående lista.
En korrelerad fråga kan vara lätt att läsa när varje yttre rad ska testas mot relaterade poster. Men anta inte att databasen bokstavligen kör den inre frågan separat för varje rad. Optimeraren kan omforma planen, och utfallet beror på databasmotor, index och datamängd. Microsoft Learn om subqueries i SQL Server beskriver att subqueries och motsvarande joins ofta kan ge likvärdiga planer, men att vissa existenskontroller kan gynnas av en annan formulering.
Om frågan blir lång eller svår att följa kan en join eller en CTE, alltså en namngiven del-fråga med WITH, göra strukturen tydligare. Kontrollera då att omskrivningen inte ändrar antalet rader, exempelvis genom att en join skapar flera matchningar per kund.
Felsök resultatet innan du jagar prestanda
När svaret ser fel ut är det frestande att börja optimera. Börja hellre med att kontrollera vilket resultat den inre frågan faktiskt ger, eftersom både fel kardinalitet och nullvärden kan ändra den yttre frågans urval.
| Situation | Trolig orsak | Gör så här |
|---|---|---|
Jämförelsen med = ger fel eller avbryts | Subqueryn returnerar fler än ett värde | Kontrollera om villkoret ska använda IN, EXISTS eller en aggregering |
NOT IN ger oväntat få eller inga rader | Den inre listan kan innehålla NULL | Byt till NOT EXISTS eller filtrera bort nullvärden |
| Samma kund syns flera gånger efter omskrivning till join | Flera orderrader matchar samma kund | Använd EXISTS om du bara behöver testa förekomst |
| Frågan går långsamt på stora tabeller | Planen kan sakna effektiv åtkomst till kopplingskolumnen | Granska exekveringsplanen och kontrollera index på kolumnerna i villkoren |
En subquery är alltså inte automatiskt långsam, och en join är inte automatiskt snabbare. Jämför exekveringsplaner när prestandan spelar roll, men säkerställ först att båda versionerna ger samma resultat.
En bra subquery gör avsikten synlig
Om den inre frågan lämnar tillbaka ett enda beräknat värde passar en skalär subquery; om du vill testa om relaterade rader finns är EXISTS ofta ett tydligt val. Vid uteslutning bör möjliga NULL-värden få dig att granska NOT IN extra noga.
När en SQL-fråga känns som en låda i en låda, börja med att köra den inre delen separat och se vad den faktiskt returnerar. Då blir det lättare att avgöra om problemet ligger i värdet, listan eller kopplingen till den yttre frågan.