Tietokantojen perusteet

syksy 2025

3. Monen taulun kyselyt

Taulujen viittaukset

Keskeinen idea tietokannoissa on, että taulun rivi voi viitata toisen taulun riviin. Tällöin voidaan muodostaa kyselyjä, jotka keräävät tietoa useista tauluista viittausten perusteella. Käytännössä viittauksena on yleensä toisessa taulussa olevan rivin id-numero.

Esimerkki

Tarkastellaan esimerkkinä tilannetta, jossa tietokannassa on tietoa kursseista ja niiden opettajista. Oletamme, että jokaisella kurssilla on yksi opettaja ja sama opettaja voi opettaa monta kurssia.

Tallennamme tauluun Teachers tietoa opettajista. Jokaisella opettajalla on id-numero, jolla voimme viitata siihen.

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

Taulun sisältö voisi olla esimerkiksi seuraava:

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

Taulussa Courses on puolestaan tietoa kursseista ja jokaisen kurssin kohdalla viittaus kurssin opettajaan.

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

Tässä sarake teacher_id viittaa taulussa Teachers olevan rivin id-numeroon, minkä tuo esille merkintä REFERENCES Teachers.

Taulun sisältö voisi olla esimerkiksi seuraava:

id  name              teacher_id
--  ----------------  ----------
1   Laskennan mallit  3         
2   Tietoverkot       1         
3   Graduseminaari    1         
4   PHP-ohjelmointi   4         
5   Neuroverkot       3         

Tämä tarkoittaa, että esimerkiksi kurssia Laskennan mallit luennoi opettaja, jonka id-numero on 3. Taulusta Teachers selviää, että kyseinen opettaja on Kivinen.

Voimme nyt hakea kurssit opettajineen seuraavalla kyselyllä, joka hakee tietoa samaan aikaan tauluista Courses ja Teachers:

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

Koska kyselyssä on monta taulua, ilmoitamme sarakkeiden taulut. Esimerkiksi Courses.name viittaa taulun Courses sarakkeeseen name.

Kysely antaa seuraavan tuloksen:

name              name     
----------------  ---------
Laskennan mallit  Kivinen  
Tietoverkot       Kaila    
Graduseminaari    Kaila    
PHP-ohjelmointi   Laaksonen
Neuroverkot       Kivinen  

Mitä tässä tapahtui?

Yllä olevassa kyselyssä uutena asiana on, että kysely koskee useaa taulua (FROM Courses, Teachers), mutta mitä tämä tarkoittaa?

Ideana on, että kun kyselyssä on monta taulua, tulostaulun lähtökohtana ovat kaikki tavat valita rivien yhdistelmiä tauluista. Tämän jälkeen WHERE-osan ehdoilla voi määrittää, mitkä rivien yhdistelmät ovat kiinnostuksen kohteena.

Hyvä tapa saada ymmärrystä monen taulun kyselyn toiminnasta on tarkastella ensin kyselyä, joka hakee kaikki sarakkeet ja jossa ei ole WHERE-osaa. Yllä olevassa esimerkkitilanteessa tällainen kysely on seuraava:

SELECT * FROM Courses, Teachers;

Koska taulussa Courses on 5 riviä ja taulussa Teachers on 4 riviä, kyselyn tulostaulussa on 5 * 4 = 20 riviä. Tulostaulu sisältää kaikki mahdolliset tavat valita ensin jokin rivi taulusta Courses ja sitten jokin rivi taulusta Teachers:

id  name              teacher_id  id  name      
--  ----------------  ----------  --  ----------
1   Laskennan mallit  3           1   Kaila     
1   Laskennan mallit  3           2   Luukkainen
1   Laskennan mallit  3           3   Kivinen   
1   Laskennan mallit  3           4   Laaksonen 
2   Tietoverkot       1           1   Kaila     
2   Tietoverkot       1           2   Luukkainen
2   Tietoverkot       1           3   Kivinen   
2   Tietoverkot       1           4   Laaksonen 
3   Graduseminaari    1           1   Kaila     
3   Graduseminaari    1           2   Luukkainen
3   Graduseminaari    1           3   Kivinen   
3   Graduseminaari    1           4   Laaksonen 
4   PHP-ohjelmointi   4           1   Kaila     
4   PHP-ohjelmointi   4           2   Luukkainen
4   PHP-ohjelmointi   4           3   Kivinen   
4   PHP-ohjelmointi   4           4   Laaksonen 
5   Neuroverkot       3           1   Kaila     
5   Neuroverkot       3           2   Luukkainen
5   Neuroverkot       3           3   Kivinen   
5   Neuroverkot       3           4   Laaksonen 

Suurin osa tulosriveistä ei ole kuitenkaan kiinnostavia, koska niillä olevat tiedot eivät liity toisiinsa. Esimerkiksi ensimmäinen tulosrivi kertoo vain, että on olemassa kurssi Laskennan mallit ja on olemassa opettaja Kaila.

Jotta kysely antaisi mielekkäitä tuloksia, rajaamme hakua niin, että opettajan id-numeron tulee olla sama kummankin taulun riveissä:

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

Tämän seurauksena kysely alkaa antaa mielekkäitä tuloksia:

id  name              teacher_id  id  name     
--  ----------------  ----------  --  ---------
1   Laskennan mallit  3           3   Kivinen  
2   Tietoverkot       1           1   Kaila    
3   Graduseminaari    1           1   Kaila    
4   PHP-ohjelmointi   4           4   Laaksonen
5   Neuroverkot       3           3   Kivinen  

Tämän jälkeen voimme vielä siistiä kyselyn tuloksia valitsemalla meitä kiinnostavat sarakkeet:

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

Näin päädymme samaan tulokseen kuin aiemmin:

name              name     
----------------  ---------
Laskennan mallit  Kivinen  
Tietoverkot       Kaila    
Graduseminaari    Kaila    
PHP-ohjelmointi   Laaksonen
Neuroverkot       Kivinen  

Lisää ehtoja kyselyssä

Monen taulun kyselyissä WHERE-osa kytkee toisiinsa meitä kiinnostavat taulujen rivit, mutta lisäksi voimme laittaa WHERE-osaan muita ehtoja samaan tapaan kuin ennenkin. Esimerkiksi voimme suorittaa seuraavan kyselyn:

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

Näin saamme haettua kurssit, joiden opettajana on Kivinen:

name              name   
----------------  -------
Laskennan mallit  Kivinen
Neuroverkot       Kivinen

Taulujen lyhyet nimet

Voimme tiivistää monen taulun kyselyä antamalla tauluille vaihtoehtoiset lyhyet nimet, joiden avulla voimme viitata niihin kyselyssä. Esimerkiksi kysely

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

voidaan esittää lyhemmin näin:

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

Sana AS ei ole pakollinen, eli voimme lyhentää kyselyä lisää:

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

Saman taulun toistaminen

Monen taulun kyselyssä voi esiintyä myös monta kertaa sama taulu, kunhan toistuvalle taululle annetaan eri nimet. Esimerkiksi seuraava kysely hakee kaikki tavat valita kahden opettajan pari:

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

Kyselyn tulos on seuraava:

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 

Liitostaulut

Taulujen välillä esiintyy yleensä kahdenlaisia suhteita:

  1. Yksi moneen -suhde: Taulun A rivi liittyy enintään yhteen taulun B riviin. Taulun B rivi voi liittyä useaan taulun A riviin.

  2. Monta moneen -suhde: Taulun A rivi voi liittyä useaan taulun B riviin. Taulun B rivi voi liittyä useaan taulun A riviin.

Tapauksessa 1 voimme lisätä tauluun A sarakkeen, joka viittaa tauluun B, kuten teimme edellisen osion esimerkissä. Tapauksessa 2 tilanne on kuitenkin hankalampi, koska yksittäinen viittaus kummankaan taulun rivissä ei riittäisi. Ratkaisuna on luoda kolmas liitostaulu (join table), joka sisältää tiedot viittauksista.

Esimerkki

Tarkastellaan esimerkkinä tilannetta, jossa verkkokaupassa on tuotteita ja asiakkaita ja jokainen asiakas on valinnut tiettyjä tuotteita ostoskoriin. Tietyn asiakkaan korissa voi olla useita tuotteita, ja toisaalta tietty tuote voi olla usean asiakkaan korissa.

Rakennamme tietokannan, jossa on kolme taulua: Products, Customers ja Purchases. Liitostaulu Purchases ilmaisee, mitä tuotteita on kunkin asiakkaan ostoskorissa. Sen jokainen rivi esittää yhden parin muotoa “asiakkaan id korissa on tuote id”.

Oletetaan, että taulujen sisällöt ovat seuraavat:

id name price
1 retiisi 7
2 porkkana 5
3 nauris 4
4 lanttu 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

Tämä tarkoittaa, että Uolevin korissa on porkkana ja selleri sekä Maijan korissa on retiisi, lanttu ja selleri. Aapelin korissa ei ole mitään tuotetta.

Nyt voimme hakea asiakkaat ja tuotteet seuraavasti:

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

Kyselyn ideana on hakea tauluista Customers ja Products taulun Purchases rivejä vastaavat tiedot. Jotta saamme mielekkäitä tuloksia, kytkemme rivit yhteen kahden ehdon avulla. Kysely tuottaa seuraavan tulostaulun:

name    name    
------  --------
Uolevi  porkkana
Uolevi  selleri 
Maija   retiisi 
Maija   lanttu  
Maija   selleri 

Miten kysely toimii?

Voimme taas tutkia kyselyn toimintaa hakemalla kaikki sarakkeet ja poistamalla ehdot:

SELECT * FROM Customers, Products, Purchases;

Tämän kyselyn tulostaulussa on kaikki tavat valita jokin asiakas, tuote ja ostokset. Tulostaulussa on 5 * 3 * 5 = 75 riviä ja se alkaa näin:

id  name    id  name      price  customer_id  product_id
--  ------  --  --------  -----  -----------  ----------
1   Uolevi  1   retiisi   7      1            2         
1   Uolevi  1   retiisi   7      1            5         
1   Uolevi  1   retiisi   7      2            1         
1   Uolevi  1   retiisi   7      2            4         
1   Uolevi  1   retiisi   7      2            5         
1   Uolevi  2   porkkana  5      1            2         
1   Uolevi  2   porkkana  5      1            5         
1   Uolevi  2   porkkana  5      2            1         
1   Uolevi  2   porkkana  5      2            4         
1   Uolevi  2   porkkana  5      2            5         
1   Uolevi  3   nauris    4      1            2         
1   Uolevi  3   nauris    4      1            5         
1   Uolevi  3   nauris    4      2            1         
1   Uolevi  3   nauris    4      2            4         
1   Uolevi  3   nauris    4      2            5         
...

Sitten kun lisäämme kyselyyn ehdot, saamme rajattua kiinnostavat rivit:

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   porkkana  5      1            2         
1   Uolevi  5   selleri   4      1            5         
2   Maija   1   retiisi   7      2            1         
2   Maija   4   lanttu    8      2            4         
2   Maija   5   selleri   4      2            5         

Kun vielä määritämme halutut sarakkeet, tuloksena on lopullinen kysely:

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

Lisää ehtoja kyselyyn

Voimme lisätä kyselyyn lisää ehtoja, jos haluamme saada selville muuta ostoskoreista. Esimerkiksi seuraava kysely hakee Maijan korissa olevat tuotteet:

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

Seuraava kysely puolestaan kertoo, keiden korissa on selleri:

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    

Yhteenveto tauluista

Voimme käyttää koostefunktioita ja ryhmittelyä myös usean taulun kyselyissä. Ne käsittelevät tulostaulua samalla periaatteella kuin yhden taulun kyselyissä.

Tarkastellaan edelleen tietokantaa, jossa on tuotteita, asiakkaita ja ostoksia:

id name price
1 retiisi 7
2 porkkana 5
3 nauris 4
4 lanttu 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

Seuraava kysely luo yhteenvedon, joka näyttää jokaisesta asiakkaasta, montako tuotetta hänen ostoskorissaan on ja mikä on tuotteiden yhteishinta.

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;

Kyselyn tulos on seuraava:

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

Uolevin korissa on siis 2 tavaraa, joiden yhteishinta on 9, ja Maijan korissa on 3 tavaraa, joiden yhteishinta on 19.

Miten kysely toimii?

Kyselyn perusta on tässä:

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   porkkana  5      1            2         
1   Uolevi  5   selleri   4      1            5         
2   Maija   1   retiisi   7      2            1         
2   Maija   4   lanttu    8      2            4         
2   Maija   5   selleri   4      2            5         

Kun kyselyyn lisätään ryhmittely GROUP BY Customers.id, rivit jakautuvat kahteen ryhmään sarakkeen Customers.id mukaan:

Ryhmä 1:

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

Ryhmä 2:

id  name   id  name     price  customer_id  product_id
--  -----  --  -------  -----  -----------  ----------
2   Maija  1   retiisi  7      2            1         
2   Maija  4   lanttu   8      2            4         
2   Maija  5   selleri  4      2            5         

Näille ryhmille lasketaan sitten tuotteiden määrä COUNT(Products.id) sekä ostosten yhteishinta SUM(Products.price).

Huomaa, että kyselyssä ryhmittely tapahtuu sarakkeen Customers.id mukaan, mutta kyselyssä haetaan sarake Customers.name. Tämä on sinänsä järkevää, koska sarake Customers.id määrää sarakkeen Customers.name. Tämä kysely toimii esimerkiksi SQLitessä, mutta jotkin tietokannat vaativat tässäkin tapauksessa, että sellaisenaan haettavan sarakkeen tulee esiintyä myös ryhmittelyssä. Tällöin ryhmittelyn tulisi olla GROUP BY Customers.id, Customers.name.

Puuttuvan rivin ongelma

Äskeinen kysely toimii sinänsä hyvin, mutta jotain puuttuu:

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

Kyselyn puutteena on vielä, että tuloksissa ei ole lainkaan kolmatta tietokannassa olevaa asiakasta eli Aapelia. Koska Aapelin korissa ei ole mitään, Aapelin rivi ei yhdisty minkään muun rivin kanssa eikä pääse osaksi tulostaulua.

Olemme törmänneet ongelmaan, mutta onneksi löydämme siihen ratkaisun pian.

JOIN-syntaksi

Tähän mennessä olemme hakeneet tietoa tauluista listaamalla taulut kyselyn FROM-osassa, mikä toimii yleensä hyvin. Kuitenkin joskus on tarpeen JOIN-syntaksi, josta on hyötyä silloin, kun kyselyn tuloksesta näyttää “puuttuvan” tietoa.

Kyselytavat

Seuraavassa on kaksi tapaa toteuttaa sama kysely, ensin käyttäen ennestään tuttua tapaa ja sitten käyttäen JOIN-syntaksia.

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;

JOIN-syntaksissa taulujen nimien välissä esiintyy sana JOIN ja lisäksi taulujen rivit toisiinsa kytkevä ehto annetaan erillisessä ON-osassa.

Tässä tapauksessa JOIN-syntaksi on vain vaihtoehtoinen tapa toteuttaa kysely eikä se tuo mitään uutta. Kuitenkin näemme seuraavaksi, miten voimme laajentaa syntaksia niin, että se antaa meille uusia mahdollisuuksia kyselyissä.

Esimerkki

Tarkastellaan esimerkkinä tilannetta, jossa tietokannassa on tutut taulut Courses ja Teachers, mutta taulussa Courses yhdeltä kurssilta puuttuu opettaja:

id  name              teacher_id
--  ----------------  ----------
1   Laskennan mallit  3         
2   Tietoverkot       1         
3   Graduseminaari    1         
4   PHP-ohjelmointi   NULL      
5   Neuroverkot       3         

Rivin 4 sarakkeessa teacher_id on arvo NULL, joten jos suoritamme jommankumman äskeisen kyselyn, ongelmaksi tulee, että rivi 4 ei täsmää mihinkään taulun Teachers riviin. Tämän seurauksena tulostauluun ei tule riviä kurssista PHP-ohjelmointi:

name              name   
----------------  -------
Laskennan mallit  Kivinen
Tietoverkot       Kaila  
Graduseminaari    Kaila  
Neuroverkot       Kivinen

Ratkaisu ongelmaan on käyttää LEFT JOIN -syntaksia, joka tarkoittaa, että mikäli jokin vasemman taulun rivi ei yhdisty mihinkään oikean taulun riviin, kyseinen vasemman taulun rivi pääsee silti mukaan yhdeksi riviksi tulostauluun. Kyseisellä rivillä jokaisen oikeaan tauluun perustuvan sarakkeen arvona on NULL.

Tässä tapauksessa voimme toteuttaa kyselyn näin:

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

Nyt tulostauluun ilmestyy myös kurssi Tietokantojen perusteet ilman opettajaa:

name              name   
----------------  -------
Laskennan mallit  Kivinen
Tietoverkot       Kaila  
Graduseminaari    Kaila  
PHP-ohjelmointi   NULL   
Neuroverkot       Kivinen

Miten kysely toimii?

Jälleen hyvä tapa saada selkoa kyselystä on yksinkertaistaa sitä:

SELECT
  *
FROM
  Courses LEFT JOIN Teachers ON Courses.teacher_id = Teachers.id;
id  name              teacher_id  id    name   
--  ----------------  ----------  ----  -------
1   Laskennan mallit  3           3     Kivinen
2   Tietoverkot       1           1     Kaila  
3   Graduseminaari    1           1     Kaila  
4   PHP-ohjelmointi   NULL        NULL  NULL   
5   Neuroverkot       3           3     Kivinen

Tästä näkee, että koska vasemman taulun rivi 4 ei täsmää mihinkään oikean taulun riviin, niin kyseisestä rivistä tulee tulostauluun yksi rivi, jossa jokainen sarake oikean taulun osuudessa on NULL.

JOIN-kyselyperhe

Itse asiassa JOIN-kyselystä on olemassa peräti neljä eri muunnelmaa:

SQLiten rajoituksena on, että vain kaksi ensimmäistä kyselytapaa ovat mahdollisia. Onneksi LEFT JOIN on yleensä se, mitä haluamme.

ON vs. WHERE

Sana ON on oleellinen LEFT JOIN -kyselyssä, koska se asettaa ehdon niin, että mukaan otetaan myös vasemman taulun ylimääräiset rivit:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers ON Courses.teacher_id = Teachers.id;
name              name   
----------------  -------
Laskennan mallit  Kivinen
Tietoverkot       Kaila  
Graduseminaari    Kaila  
PHP-ohjelmointi   NULL   
Neuroverkot       Kivinen

Jos käytämme sen sijasta sanaa WHERE, vasemman taulun ylimääräiset rivit jäävät pois:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers
WHERE
  Courses.teacher_id = Teachers.id;
name              name   
----------------  -------
Laskennan mallit  Kivinen
Tietoverkot       Kaila  
Graduseminaari    Kaila  
Neuroverkot       Kivinen

Sinänsä kyselyssä voi esiintyä sekä ON että WHERE:

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

Tällöin ON-osa hoitaa taulujen yhdistämisen ja WHERE-osa rajaa tuloksia lisää:

name              name   
----------------  -------
Laskennan mallit  Kivinen
Graduseminaari    Kaila  
PHP-ohjelmointi   NULL   
Neuroverkot       Kivinen

Tässä tapauksessa WHERE-osan vaikutuksena on, että tulostaulussa ei ole riviä, jossa kurssina on Tietoverkot.

Jos molemmat ehdot ovatkin ON-osassa, kyselyn tulos muuttuu taas:

SELECT
  Courses.name, Teachers.name
FROM
  Courses LEFT JOIN Teachers
          ON Courses.teacher_id = Teachers.id AND
             Courses.name <> 'Tietoverkot';
name              name   
----------------  -------
Laskennan mallit  Kivinen
Tietoverkot       NULL   
Graduseminaari    Kaila  
PHP-ohjelmointi   NULL   
Neuroverkot       Kivinen

Tässä tapauksessa kursseja Tietoverkot ja PHP-ohjelmointi vastaavat rivit jäävät ylimääräisiksi vasemmassa taulussa, koska niitä ei voida liittää mihinkään oikean taulun riviin ON-ehdossa.

Yhteenveto toimivaksi

Nyt voimme pureutua aiempaan ongelmaan, jossa yhteenvetokyselystä puuttui tietoa. Tietokannassamme on edelleen seuraavat taulut:

id name price
1 retiisi 7
2 porkkana 5
3 nauris 4
4 lanttu 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

Muodostimme yhteenvedon ostoskoreista seuraavalla kyselyllä:

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;

Kuitenkin ongelmaksi tuli, että Aapeli puuttuu yhteenvedosta:

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

Ongelman syynä on, että Aapelin ostoskori on tyhjä eli kun kysely valitsee yhdistelmiä taulujen riveistä, ei ole mitään sellaista riviä, jolla esiintyisi Aapeli. Ratkaisu ongelmaan on käyttää LEFT JOIN -syntaksia näin:

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;

Nyt myös Aapeli ilmestyy mukaan yhteenvetoon:

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

Koska Aapelin ostoskorissa ei ole tuotteita, hintojen summaksi tulee NULL. Voimme vielä parantaa kyselyä IFNULL-funktion avulla:

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;

Tämän seurauksena mahdollinen NULL muuttuu arvoksi 0:

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

Miten kysely toimii?

Kun kyselyssä on useita LEFT JOIN -osia, tulkintana on, että ne yhdistävät tauluja vasemmalta oikealle. Yllä olevassa kyselyssä voimme ajatella, että ensimmäinen vaihe yhdistää taulut Customers ja 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      

Toinen vaihe puolestaan yhdistää yllä olevan tulostaulun ja taulun 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     porkkana  5    
1   Uolevi  1            5           5     selleri   4    
2   Maija   2            1           1     retiisi   7    
2   Maija   2            4           4     lanttu    8    
2   Maija   2            5           5     selleri   4    
3   Aapeli  NULL         NULL        NULL  NULL      NULL 

Molemmissa vaiheissa Aapeli pääsee osaksi tulostaulua, koska kyseinen rivi ei täsmää minkään oikean taulun rivin kanssa.