Databasernas grunder

vår 2026

3. Frågor som omfattar flera tabeller

Tabellreferenser

En central idé inom databaser är att en rad i en tabell kan referera till en rad i en annan tabell. Således kan man skapa frågor som samlar data från flera tabeller utifrån dessa referenser. I praktiken består en sådan referens oftast av ID-numret för raden i den andra tabellen.

Exempel

Låt oss som exempel titta på en situation där databasen innehåller data om kurser och deras lärare. Vi antar att varje kurs har en lärare, och att samma lärare kan undervisa flera kurser.

Vi sparar information om lärarna i tabellen Teachers. Varje lärare har ett ID-nummer som vi kan referera till.

CREATE TABLE Teachers (
  id INTEGER PRIMARY KEY,
  name TEXT
);

Tabellen kan exempelvis se ut på följande sätt:

id  name      
--  ----------
1   Kaila     
2   Luukkainen
3   Kivinen   
4   Laaksonen 

Tabellen Courses innehåller data om kurserna. För varje kurs finns en referens till kursens lärare.

CREATE TABLE Courses (
  id INTEGER PRIMARY KEY,
  name TEXT,
  teacher_id INTEGER REFERENCES Teachers
);

Här refererar kolumnen teacher_id till ID-numret för en rad i tabellen Teachers, vilket anges med REFERENCES Teachers.

Tabellen kan exempelvis se ut på följande sätt:

id  name                    teacher_id
--  ----------------        ----------
1   Beräkningsmodeller      3         
2   Datanätverk             1         
3   Seminarium              1         
4   PHP-programmering       4         
5   Neuronnät               3         

Detta innebär att till exempel kursen Beräkningsmodeller undervisas av läraren med ID-numret 3. Från tabellen Teachers framgår att detta är lärare Kivinen.

Vi kan nu hämta kurserna med deras lärare med följande fråga som samtidigt hämtar information från tabellerna Courses och Teachers:

SELECT
  Courses.name, Teachers.name
FROM
  Courses, Teachers
WHERE
  Courses.teacher_id = Teachers.id;

Eftersom frågan involverar flera tabeller anger vi vilken tabell varje kolumn tillhör. Till exempel refererar Courses.name till kolumnen name i tabellen Courses.

Frågan ger följande svar:

name                    name     
----------------        ---------
Beräkningsmodeller      Kivinen  
Datanätverk             Kaila    
Seminarium              Kaila    
PHP-programmering       Laaksonen
Neuronnät               Kivinen  

Vad händer i exemplet ovan?

Frågan ovan omfattar flera tabeller (FROM Courses, Teachers). Vad innebär det egentligen?

Idén är att när en fråga omfattar flera tabeller är utgångspunkten för resultattabellen alla de olika möjliga sätten att kombinera rader från tabellerna. Därefter kan man med villkoren i WHERE-delen bestämma vilka radkombinationer som är av intresse.

Ett bra sätt för att förstå hur en fråga med flera tabeller fungerar är att först titta på en fråga som hämtar alla kolumner och som inte innehåller någon WHERE-del. I exemplet ovan är en sådan fråga följande:

SELECT * FROM Courses, Teachers;

Eftersom tabellen Courses har 5 rader och tabellen Teachers har 4 rader blir svaret en resultattabell på 5 * 4 = 20 rader. Resultattabellen innehåller alla möjliga sätt att först välja en rad från tabellen Courses och sedan en rad från tabellen Teachers:

id  name               teacher_id  id  name      
--  ----------------   ----------  --  ---------
1   Beräkningsmodeller  3          1   Kaila     
1   Beräkningsmodeller  3          2   Luukkainen
1   Beräkningsmodeller  3          3   Kivinen   
1   Beräkningsmodeller  3          4   Laaksonen 
2   Datanätverk         1          1   Kaila     
2   Datanätverk         1          2   Luukkainen
2   Datanätverk         1          3   Kivinen   
2   Datanätverk         1          4   Laaksonen 
3   Seminarium          1          1   Kaila     
3   Seminarium          1          2   Luukkainen
3   Seminarium          1          3   Kivinen   
3   Seminarium          1          4   Laaksonen 
4   PHP-programmering   4          1   Kaila     
4   PHP-programmering   4          2   Luukkainen
4   PHP-programmering   4          3   Kivinen   
4   PHP-programmering   4          4   Laaksonen 
5   Neuronnät           3          1   Kaila     
5   Neuronnät           3          2   Luukkainen
5   Neuronnät           3          3   Kivinen   
5   Neuronnät           3          4   Laaksonen 

De flesta resultatrader är dock inte av intresse eftersom deras information inte hör ihop sinsemellan. Till exempel berättar den första resultatraden bara att det finns en kurs som heter Beräkningsmodeller och att det finns en lärare som heter Kaila.

För att filtrera fram de rader som ger oss användbar information begränsar vi sökningen så att lärarens id-nummer ska vara samma i båda tabellerna:

SELECT
  *
FROM
  Courses, Teachers
WHERE
  Courses.teacher_id = Teachers.id;

Nu ger frågan ett mer intressant svar:

id  name                teacher_id  id  name     
--  ----------------    ----------  --  ---------
1   Beräkningsmodeller  3           3   Kivinen  
2   Datanätverk         1           1   Kaila    
3   Seminarium          1           1   Kaila    
4   PHP-programmering   4           4   Laaksonen
5   Neuronnät           3           3   Kivinen  

Vi kan ytterligare precisera svarstabellen genom att endast välja de kolumner som är av intresse:

SELECT
  Courses.name, Teachers.name
FROM
  Courses, Teachers
WHERE
  Courses.teacher_id = Teachers.id;

På så sätt kommer vi fram till samma svar som tidigare:

name                name     
----------------    ---------
Beräkningsmodeller  Kivinen  
Datanätverk         Kaila    
Seminarium          Kaila    
PHP-programmering   Laaksonen
Neuronnät           Kivinen  

Flera villkor

I frågor som involverar flera tabeller kopplar WHERE-delen ihop de rader i tabellerna som är av intresse. Vi kan också lägga till andra villkor i WHERE-delen på samma sätt som tidigare. Till exempel kan vi ställa följande fråga:

SELECT
  Courses.name, Teachers.name
FROM
  Courses, Teachers
WHERE
  Courses.teacher_id = Teachers.id AND Teachers.name = 'Kivinen';

Vi får då fram de kurser där Kivinen är lärare:

name                name   
----------------    -------
Beräkningsmodeller  Kivinen
Neuronnät           Kivinen

Förkortade tabellnamn

Vi kan göra frågor som involverar flera tabeller mer lättlästa genom att ge tabellerna alternativa kortare namn som vi sedan kan hänvisa till. Exempelvis frågan

SELECT
  Courses.name, Teachers.name
FROM
  Courses, Teachers
WHERE
  Courses.teacher_id = Teachers.id;

kan förkortas:

SELECT
  C.name, T.name
FROM
  Courses AS C, Teachers AS T
WHERE
  C.teacher_id = T.id;

Ordet AS är inte heller nödvändigt att använda. Vi kan alltså förkorta frågan ytterligare:

SELECT
  C.name, T.name
FROM
  Courses C, Teachers T
WHERE
  C.teacher_id = T.id;

Att upprepa samma tabell

I en fråga som involverar flera tabeller kan samma tabell också förekomma flera gånger så länge varje tabell som upprepas får ett annat namn. Till exempel hämtar följande fråga alla sätt att välja ett par av två lärare:

SELECT A.name, B.name FROM Teachers A, Teachers B;

Frågan ger följande svar:

name        name      
----------  ----------
Kaila       Kaila     
Kaila       Luukkainen
Kaila       Kivinen   
Kaila       Laaksonen 
Luukkainen  Kaila     
Luukkainen  Luukkainen
Luukkainen  Kivinen   
Luukkainen  Laaksonen 
Kivinen     Kaila     
Kivinen     Luukkainen
Kivinen     Kivinen   
Kivinen     Laaksonen 
Laaksonen   Kaila     
Laaksonen   Luukkainen
Laaksonen   Kivinen   
Laaksonen   Laaksonen 

Länkningstabeller

Mellan tabeller finns det i allmänhet två typer av relationer:

  1. En-till-många-relation: En rad i tabell A är kopplad till högst en rad i tabell B. En rad i tabell B kan vara kopplad till flera rader i tabell A.

  2. Många-till-många-relation: En rad i tabell A kan vara kopplad till flera rader i tabell B. En rad i tabell B kan vara kopplad till flera rader i tabell A.

I fall 1 kan vi lägga till en kolumn i tabell A som refererar till tabell B precis som vi gjorde i exemplet i föregående avsnitt. Fall 2 är däremot mer komplicerat eftersom en enskild referens i någon av tabellraderna inte räcker. Lösningen är att skapa en tredje kopplingstabell (join table) som innehåller datan om referenserna.

Exempel

Låt oss som exempel titta på en situation där en webbutik har produkter och kunder, och varje kund har valt vissa produkter till sin köpkorg. I en viss köpkorg kan det finnas flera produkter, och en viss produkt kan finnas i flera köpkorgar.

Vi skapar en databas som består av tre tabeller: Products, Customers och Purchases. Kopplingstabellen Purchases anger vilka produkter som finns i varje köpkorg. Varje rad i kopplingstabellen motsvarar ett par i formen “Kunden id har produkten id i sin köpkorg”.

Vi antar att tabellerna ser ut på följande sätt:

id name price
1 rädisa 7
2 morot 5
3 rova 4
4 kålrot 8
5 selleri 4
id name
1 Uolevi
2 Maija
3 Aapeli
customer_id product_id
1 2
1 5
2 1
2 4
2 5

Detta betyder att Uolevis köpkorg innehåller en morot och en selleri. Maijas köpkorg innehåller en rädisa, en kålrot och en selleri. Aapelis köpkorg är tom.

Vi kan nu hämta kunderna och produkterna på följande sätt:

SELECT
  Customers.name, Products.name
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND
  Products.id = Purchases.product_id;

Syftet med frågan är att hämta information från tabellerna Customers och Products som motsvarar raderna i tabellen Purchases. För att få fram relevanta svar kopplar vi ihop raderna med hjälp av två villkor. Frågan ger som svar följande resultattabell:

name    name    
------  --------
Uolevi  morot
Uolevi  selleri 
Maija   rädisa 
Maija   kålrot  
Maija   selleri 

Hur fungerar frågan?

Vi kan återigen undersöka hur frågan fungerar genom att hämta alla kolumner och ta bort villkoren:

SELECT * FROM Customers, Products, Purchases;

Resultattabellen från denna fråga innehåller alla möjliga kombinationer av kund, produkt och köp. Resultattabellen har 5 * 3 * 5 = 75 rader och börjar på följande sätt:

id  name    id  name      price  customer_id  product_id
--  ------  --  --------  -----  -----------  ----------
1   Uolevi  1   rädisa    7      1            2         
1   Uolevi  1   rädisa    7      1            5         
1   Uolevi  1   rädisa    7      2            1         
1   Uolevi  1   rädisa    7      2            4         
1   Uolevi  1   rädisa    7      2            5         
1   Uolevi  2   morot     5      1            2         
1   Uolevi  2   morot     5      1            5         
1   Uolevi  2   morot     5      2            1         
1   Uolevi  2   morot     5      2            4         
1   Uolevi  2   morot     5      2            5         
1   Uolevi  3   rova      4      1            2         
1   Uolevi  3   rova      4      1            5         
1   Uolevi  3   rova      4      2            1         
1   Uolevi  3   rova      4      2            4         
1   Uolevi  3   rova      4      2            5         
...

När vi sedan lägger till villkoren i frågan kan vi begränsa oss till de rader som vi är intresserade av:

SELECT
  *
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND
  Products.id = Purchases.product_id;
id  name    id  name      price  customer_id  product_id
--  ------  --  --------  -----  -----------  ----------
1   Uolevi  2   morot     5      1            2         
1   Uolevi  5   selleri   4      1            5         
2   Maija   1   rädisa    7      2            1         
2   Maija   4   kålrot    8      2            4         
2   Maija   5   selleri   4      2            5         

När vi därtill specificerar de önskade kolumnerna får vi den slutgiltiga frågan:

SELECT
  Customers.name, Products.name
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND
  Products.id = Purchases.product_id;
name    name    
------  --------
Uolevi  morot
Uolevi  selleri 
Maija   rädisa 
Maija   kålrot  
Maija   selleri 

Flera villkor i frågan

Vi kan lägga till flera villkor i frågan om vi vill få reda på mer om köpkorgarna. Till exempel hämtar följande fråga de produkter som finns i Maijas köpkorg:

SELECT
  Products.name
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND
  Products.id = Purchases.product_id AND
  Customers.name = 'Maija';
name
----------
rädisa   
kålrot    
selleri   

Följande fråga visar vilka kunder som har selleri i sin köpkorg:

SELECT
  Customers.name
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND
  Products.id = Purchases.product_id AND
  Products.name = 'selleri';
name
----------
Uolevi    
Maija    

Sammanfattning av tabellerna

Vi kan också använda aggregeringsfunktioner och gruppering i frågor som omfattar flera tabeller. De behandlar resultattabellen på samma sätt som i frågor som använder endast en tabell.

Vi betraktar fortsättningsvis databasen som innehåller produkter, kunder och köp:

id name price
1 rädisa 7
2 morot 5
3 rova 4
4 kålrot 8
5 selleri 4
id name
1 Uolevi
2 Maija
3 Aapeli
customer_id product_id
1 2
1 5
2 1
2 4
2 5

Följande fråga sammanfattar varje kunds totala antal produkter i köpkorgen och det totala priset för produkterna.

SELECT
  Customers.name, COUNT(Products.id), SUM(Products.price)
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND Products.id = Purchases.product_id
GROUP BY
  Customers.id;

Frågan ger följande svar:

name    COUNT(Products.id)  SUM(Products.price)
------  ------------------  -------------------
Uolevi  2                   9                  
Maija   3                   19                 

Uolevis köpkorg innehåller 2 produkter med ett totalt pris på 9. Maijas köpkorg innehåller 3 produkter med ett totalt pris på 19.

Hur fungerar frågan?

Frågan utgår från följande:

SELECT
  *
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND Products.id = Purchases.product_id;
id  name    id  name      price  customer_id  product_id
--  ------  --  --------  -----  -----------  ----------
1   Uolevi  2   morot     5      1            2         
1   Uolevi  5   selleri   4      1            5         
2   Maija   1   rädisa    7      2            1         
2   Maija   4   kålrot    8      2            4         
2   Maija   5   selleri   4      2            5         

Om vi i frågan lägger till grupperingen GROUP BY Customers.id delas raderna upp i två grupper baserat på kolumnen Customers.id:

Grupp 1:

id  name    id  name      price  customer_id  product_id
--  ------  --  --------  -----  -----------  ----------
1   Uolevi  2   morot     5      1            2         
1   Uolevi  5   selleri   4      1            5         

Grupp 2:

id  name   id  name     price  customer_id  product_id
--  -----  --  -------  -----  -----------  ----------
2   Maija  1   rädisa   7      2            1         
2   Maija  4   kålrot   8      2            4         
2   Maija  5   selleri  4      2            5         

För dessa grupper beräknas sedan antalet produkter med COUNT(Products.id) samt det totala priset för köpen med SUM(Products.price).

Observera att grupperingen sker enligt kolumnen Customers.id men att frågan hämtar kolumnen Customers.name. Detta är logiskt eftersom det är kolumnen Customers.id som definierar Customers.name. Medan denna fråga fungerar i exempelvis SQLite, kräver vissa andra databaser att alla kolumner som hämtas direkt också finns med i grup­peringen. I sådana fall ska grup­peringen skrivas som GROUP BY Customers.id, Customers.name.

Problem med en rad som saknas

Frågan ovan fungerar i och för sig bra, men något saknas:

name    COUNT(Products.id)  SUM(Products.price)
------  ------------------  -------------------
Uolevi  2                   9                  
Maija   3                   19                 

Ett problem med frågan ovan är att svaret inte innehåller den tredje kunden i databasen, alltså Aapeli. Eftersom Aapelis köpkorg är tom kopplas hans rad inte ihop med någon annan rad och kommer därför inte med i resultattabellen.

Som tur är så har vi snart en lösning på detta problem.

JOIN-syntaxen

Hittills har vi hämtat information från tabellerna genom att lista dem i frågans FROM-del, vilket i regel fungerar bra. Ibland är det dock nödvändigt att använda JOIN-syntaxen, vilket är användbart när det ser ut som om viss information saknas i frågans svar.

Sätt att ställa frågan på

Nedan visas två sätt att ställa samma fråga på. Först ställs frågan på det redan bekanta sättet och sedan med hjälp av JOIN-syntaxen.

SELECT
  Courses.name, Teachers.name
FROM
  Courses, Teachers
WHERE
  Courses.teacher_id = Teachers.id;
SELECT
  Courses.name, Teachers.name
FROM
  Courses JOIN Teachers ON Courses.teacher_id = Teachers.id;

I JOIN-syntaxen står ordet JOIN mellan tabellnamnen. Villkoret som kopplar ihop tabellraderna anges i en separat ON-del.

I exemplet ovan tillför JOIN-syntaxen inget nytt, utan är bara ett alternativt sätt att ställa frågan på. Vi ska i nästa exempel se hur vi kan utvidga syntaxen så att den ger oss nya möjligheter i frågorna.

Exempel

Låt oss som exempel titta på en situation där databasen innehåller de bekanta tabellerna Courses och Teachers men där en kurs i tabellen Courses saknar en lärare.

id  name                teacher_id
--  ----------------    ----------
1   Beräkningsmodeller  3         
2   Datanätverk         1         
3   Seminarium          1         
4   PHP-programmering   NULL      
5   Neuronnät           3         

I rad 4 har kolumnen teacher_id värdet NULL. Om vi kör någon av de tidigare frågorna uppstår problemet att rad 4 inte matchar någon rad i tabellen Teachers. Detta leder till att resultattabellen inte innehåller någon rad för kursen PHP-programmering:

name                name   
----------------    -------
Beräkningsmodeller  Kivinen
Datanätverk         Kaila  
Seminarium          Kaila  
Neuronnät           Kivinen

Lösningen på problemet är att använda LEFT JOIN-syntaxen, vilket innebär att om en rad i den vänstra tabellen inte kopplas till någon rad i den högra tabellen, skapas en rad i resultattabellen där varje kolumn som hör till den tabellen då får värdet NULL.

Vi kan här ställa frågan på följande sätt:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers ON Courses.teacher_id = Teachers.id;

Nu visas även kursen PHP-programmering utan lärare i resultattabellen.

name                name   
----------------    -------
Beräkningsmodeller  Kivinen
Datanätverk         Kaila  
Seminarium          Kaila  
PHP-programmering   NULL   
Neuronnät           Kivinen

Hur fungerar frågan?

Återigen är ett bra sätt att förstå hur frågan fungerar att förenkla den:

SELECT
  *
FROM
  Courses LEFT JOIN Teachers ON Courses.teacher_id = Teachers.id;
id  name                teacher_id  id    name   
--  ----------------    ----------  ----  -------
1   Beräkningsmodeller  3           3     Kivinen
2   Datanätverk         1           1     Kaila  
3   Seminarium          1           1     Kaila  
4   PHP-programmering   NULL        NULL  NULL   
5   Neuronnät           3           3     Kivinen

Här ser man att eftersom rad 4 i den vänstra tabellen inte matchar någon rad i den högra tabellen, blir det en rad i resultattabellen för den vänstra raden där varje kolumn som hör till den högra tabellen har värdet NULL.

Mera om JOIN

Det finns fyra olika varianter av JOIN-frågan:

En begränsning i SQLite var länge att endast JOIN och LEFT JOIN var möjliga, men från och med version 3.39.0 är även RIGHT JOIN och FULL JOIN tillgängliga. I materialet för denna kurs använder vi LEFT JOIN-syntax när vi vill att resultatet även ska innehålla rader som inte kopplas ihop med rader i den andra tabellen.

ON vs. WHERE

Ordet ON är centralt i en LEFT JOIN-fråga eftersom det anger villkoret som gör att även de extra raderna i den vänstra tabellen tas med:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers ON Courses.teacher_id = Teachers.id;
name                name   
----------------    -------
Beräkningsmodeller  Kivinen
Datanätverk         Kaila  
Seminarium          Kaila  
PHP-programmering   NULL   
Neuronät            Kivinen

Om vi i stället använder ordet WHERE faller de extra raderna i den vänstra tabellen bort:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers
WHERE
  Courses.teacher_id = Teachers.id;
name                name   
----------------    -------
Beräkningsmodeller  Kivinen
Datanätverk         Kaila  
Seminarium          Kaila  
Neuronnät           Kivinen

Både ON och WHERE kan förekomma i samma fråga:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers ON Courses.teacher_id = Teachers.id
WHERE
  Courses.name <> 'Datanätverk';

I sådant fall sköter ON-delen sammanslagningen av tabellerna medan WHERE-delen begränsar resultaten ytterligare:

name                name   
----------------    -------
Beräkningsmodeller  Kivinen
Seminarium          Kaila  
PHP-Programmering   NULL   
Neuronnät           Kivinen

I det här fallet gör WHERE-delen att resultattabellen inte innehåller någon rad för kursen Datanätverk.

Om båda villkoren istället finns i ON-delen förändras resultatet:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers
          ON Courses.teacher_id = Teachers.id AND
             Courses.name <> 'Datanätverk';
name                name   
----------------    -------
Beräkningsmodeller  Kivinen
Datanätverk         NULL   
Seminarium          Kaila  
PHP-programmering   NULL   
Neuronät            Kivinen

I det här fallet blir raderna för kurserna Datanätverk och PHP-programmering kvar som extra rader i den vänstra tabellen eftersom de inte kan kopplas till någon rad i den högra tabellen enligt ON-villkoret.

Lösning för sammanfattningen

Vi återgår till det tidigare problemet där frågan som sammanfattar saknade data. I vår databas finns fortfarande följande tabeller:

id name price
1 rädisa 7
2 morot 5
3 rova 4
4 kålrot 8
5 selleri 4
id name
1 Uolevi
2 Maija
3 Aapeli
customer_id product_id
1 2
1 5
2 1
2 4
2 5

Vi sammanfattade köpkorgarna med följande fråga:

SELECT
  Customers.name, COUNT(Products.id), SUM(Products.price)
FROM
  Customers, Products, Purchases
WHERE
  Customers.id = Purchases.customer_id AND Products.id = Purchases.product_id
GROUP BY
  Customers.id;

Problemet var att Aapeli inte fanns med i sammanfattningen:

name    COUNT(Products.id)  SUM(Products.price)
------  ------------------  -------------------
Uolevi  2                   9                  
Maija   3                   19                 

Orsaken till problemet är att Aapelis köpkorg är tom. När frågan väljer kombinationer av rader från tabellerna finns det ingen rad som innehåller Aapeli. Lösningen på problemet är att använda LEFT JOIN-syntaxen på följande sätt:

SELECT
  Customers.name, COUNT(Products.id), SUM(Products.price)
FROM
  Customers LEFT JOIN Purchases ON Customers.id = Purchases.customer_id
            LEFT JOIN Products ON Products.id = Purchases.product_id
GROUP BY
  Customers.id;

Nu finns också Aapeli med i sammanfattningen:

name    COUNT(Products.id)  SUM(Products.price)
------  ------------------  -------------------
Uolevi  2                   9                  
Maija   3                   19                 
Aapeli  0                   NULL               

Eftersom Aapelis köpkorg inte innehåller några produkter blir summan av produkternas priser NULL. Vi kan förbättra frågan ytterligare genom att använda IFNULL.

SELECT
  Customers.name, COUNT(Products.id), IFNULL(SUM(Products.price), 0)
FROM
  Customers LEFT JOIN Purchases ON Customers.id = Purchases.customer_id
            LEFT JOIN Products ON Products.id = Purchases.product_id
GROUP BY
  Customers.id;

Då blir NULL istället 0:

name    COUNT(Products.id)  IFNULL(SUM(Products.price), 0)
------  ------------------  ------------------------------
Uolevi  2                   9                             
Maija   3                   19                            
Aapeli  0                   0                             

Hur fungerar frågan?

När frågan innehåller flera LEFT JOIN-delar tolkas det som att tabellerna slås ihop från vänster till höger. I frågan ovan kan vi tänka oss att det första steget slår ihop tabellerna Customers och Purchases:

SELECT
  *
FROM
  Customers LEFT JOIN Purchases ON Customers.id = Purchases.customer_id;
id  name    customer_id  product_id
--  ------  -----------  ----------
1   Uolevi  1            2         
1   Uolevi  1            5         
2   Maija   2            1         
2   Maija   2            4         
2   Maija   2            5         
3   Aapeli  NULL         NULL      

I det andra steget slås resultattabellen från det första steget ihop med tabellen Products:

SELECT
  *
FROM
  Customers LEFT JOIN Purchases ON Customers.id = Purchases.customer_id
            LEFT JOIN Products ON Products.id = Purchases.product_id;
id  name    customer_id  product_id  id    name      price
--  ------  -----------  ----------  ----  --------  -----
1   Uolevi  1            2           2     morot     5    
1   Uolevi  1            5           5     selleri   4    
2   Maija   2            1           1     rädisa    7    
2   Maija   2            4           4     kålrot    8    
2   Maija   2            5           5     selleri   4    
3   Aapeli  NULL         NULL        NULL  NULL      NULL 

I båda stegen inkluderas Aapeli i resultattabellen eftersom hans rad inte matchar någon rad i den högra tabellen.