Kirjautuminen

Haku

Tehtävät

Oppaat: MySQL ja PHP: Osa 9 - Monta taulua

  1. Osa 1 - Johdanto
  2. Osa 2 - PHP ja PDO
  3. Osa 3 - Hakukyselyt I
  4. Osa 4 - Hakukyselyt II
  5. Osa 5 - Hakukyselyt III
  6. Osa 6 - Lisäys, muutos ja poisto
  7. Osa 7 - Tietotyypit
  8. Osa 8 - Tietokannan suunnittelu
  9. Osa 9 - Monta taulua
  10. Osa 10 - Lisätietoa
  11. Liite 1 - MySQL:n komentorivityökalu
  12. Liite 2 - phpMyAdmin-sovellus

Kirjoittaja: Antti Laaksonen (2009).

Tässä oppaassa tutustumme aiempaa monimutkaisempiin tietokantahakuihin, joissa tietoa haetaan samaan aikaan monesta taulusta. Tällöin SELECT-kyselyyn liittyy useita tauluja, joiden rivit yhdistetään toisiinsa sopivalla tavalla. Käytettävät esimerkit pohjautuvat edellisen oppaan tilanteisiin, joissa joka viikko tarjouksessa on jokin tuote ja tuotteet muodostavat erikoishintaisia tuotepaketteja.

Esimerkkitaulut

Tässä oppaassa käytössä on aiemmista osista tuttu taulu tuotteet:

+----+----------+-------+
| id | nimi     | hinta |
+----+----------+-------+
|  1 | kaali    |     3 |
|  2 | porkkana |     2 |
|  3 | peruna   |     3 |
|  4 | retiisi  |     4 |
|  5 | lanttu   |     1 |
|  6 | selleri  |     3 |
|  7 | nauris   |     4 |
|  8 | purjo    |     2 |
+----+----------+-------+

Lisäksi luodaan taulu tarjoukset, jossa kerrotaan eri viikkojen tarjoustuotteet. Kenttä viikko sisältää viikon numeron, ja kenttä tuote_id on viittaus tauluun tuotteet, eli siinä on jonkin tuotteen tunnusnumero.

CREATE TABLE tarjoukset (
    id INT PRIMARY KEY AUTO_INCREMENT,
    viikko INT,
    tuote_id INT
);

Lisätään viikkojen 26–30 tarjoustuotteiksi selleri, kaali, peruna, selleri ja nauris:

INSERT INTO tarjoukset (viikko, tuote_id) VALUES (26, 6);
INSERT INTO tarjoukset (viikko, tuote_id) VALUES (27, 1);
INSERT INTO tarjoukset (viikko, tuote_id) VALUES (28, 3);
INSERT INTO tarjoukset (viikko, tuote_id) VALUES (29, 6);
INSERT INTO tarjoukset (viikko, tuote_id) VALUES (30, 7);

Taulun sisältö on nyt seuraava:

+----+--------+----------+
| id | viikko | tuote_id |
+----+--------+----------+
|  1 |     26 |        6 |
|  2 |     27 |        1 |
|  3 |     28 |        3 |
|  4 |     29 |        6 |
|  5 |     30 |        7 |
+----+--------+----------+

Lisäksi luodaan taulu paketit, jossa on tuotepaketteja, sekä taulu sisallot, jossa kerrotaan, mitä tuotteita eri paketeissa on. Taulussa sisallot on viittaus sekä tauluun tuotteet että tauluun paketit. Taulun avain muodostuu kahdesta kentästä, minkä vuoksi se täytyy merkitä erikseen.

CREATE TABLE paketit (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nimi TEXT,
    hinta INT
);
CREATE TABLE sisallot (
    tuote_id INT,
    paketti_id INT,
    PRIMARY KEY (tuote_id, paketti_id)
);

Lisätään kaksi tuotepakettia, "Isoäidin herkut" ja "Voimapari":

INSERT INTO paketit (nimi, hinta) VALUES ('Isoäidin herkut', 8);
INSERT INTO paketit (nimi, hinta) VALUES ('Voimapari', 6);

Paketti "Isoäidin herkut" sisältää nauriksen, porkkanan ja retiisin:

INSERT INTO sisallot (tuote_id, paketti_id) VALUES (7, 1);
INSERT INTO sisallot (tuote_id, paketti_id) VALUES (2, 1);
INSERT INTO sisallot (tuote_id, paketti_id) VALUES (4, 1);

Paketti "Voimapari" sisältää retiisin ja sellerin:

INSERT INTO sisallot (tuote_id, paketti_id) VALUES (4, 2);
INSERT INTO sisallot (tuote_id, paketti_id) VALUES (6, 2);

Taulut paketit ja sisallot näyttävät nyt tältä:

+----+-----------------+-------+
| id | nimi            | hinta |
+----+-----------------+-------+
|  1 | Isoäidin herkut |     8 |
|  2 | Voimapari       |     6 |
+----+-----------------+-------+
+----------+------------+
| tuote_id | paketti_id |
+----------+------------+
|        2 |          1 |
|        4 |          1 |
|        4 |          2 |
|        6 |          2 |
|        7 |          1 |
+----------+------------+

Kysely monesta taulusta

Kun SELECT-kyselyssä on monta taulua, tulostaulun lähtökohtana ovat kaikki mahdolliset tavat valita yksi rivi kustakin taulusta. Esimerkiksi seuraava kysely hakee kaikki taulujen tuotteet ja tarjoukset rivien yhdistelmät. Tulostaulussa on 40 riviä, koska taulusta tuotteet rivin voi valita 8 tavalla, taulusta tarjoukset rivin voi valita 5 tavalla ja 8 * 5 = 40.

SELECT * FROM tuotteet, tarjoukset
+----+----------+-------+----+--------+----------+
| id | nimi     | hinta | id | viikko | tuote_id |
+----+----------+-------+----+--------+----------+
|  1 | kaali    |     3 |  1 |     26 |        6 |
|  2 | porkkana |     2 |  1 |     26 |        6 |
|  3 | peruna   |     3 |  1 |     26 |        6 |
|  4 | retiisi  |     4 |  1 |     26 |        6 |
|  5 | lanttu   |     1 |  1 |     26 |        6 |
|  6 | selleri  |     3 |  1 |     26 |        6 |
|  7 | nauris   |     4 |  1 |     26 |        6 |
|  8 | purjo    |     2 |  1 |     26 |        6 |
|  1 | kaali    |     3 |  2 |     27 |        1 |
|  2 | porkkana |     2 |  2 |     27 |        1 |
|  3 | peruna   |     3 |  2 |     27 |        1 |
|  4 | retiisi  |     4 |  2 |     27 |        1 |
|  5 | lanttu   |     1 |  2 |     27 |        1 |
|  6 | selleri  |     3 |  2 |     27 |        1 |
|  7 | nauris   |     4 |  2 |     27 |        1 |
|  8 | purjo    |     2 |  2 |     27 |        1 |
|  1 | kaali    |     3 |  3 |     28 |        3 |
|  2 | porkkana |     2 |  3 |     28 |        3 |
|  3 | peruna   |     3 |  3 |     28 |        3 |
|  4 | retiisi  |     4 |  3 |     28 |        3 |
|  5 | lanttu   |     1 |  3 |     28 |        3 |
|  6 | selleri  |     3 |  3 |     28 |        3 |
|  7 | nauris   |     4 |  3 |     28 |        3 |
|  8 | purjo    |     2 |  3 |     28 |        3 |
|  1 | kaali    |     3 |  4 |     29 |        6 |
|  2 | porkkana |     2 |  4 |     29 |        6 |
|  3 | peruna   |     3 |  4 |     29 |        6 |
|  4 | retiisi  |     4 |  4 |     29 |        6 |
|  5 | lanttu   |     1 |  4 |     29 |        6 |
|  6 | selleri  |     3 |  4 |     29 |        6 |
|  7 | nauris   |     4 |  4 |     29 |        6 |
|  8 | purjo    |     2 |  4 |     29 |        6 |
|  1 | kaali    |     3 |  5 |     30 |        7 |
|  2 | porkkana |     2 |  5 |     30 |        7 |
|  3 | peruna   |     3 |  5 |     30 |        7 |
|  4 | retiisi  |     4 |  5 |     30 |        7 |
|  5 | lanttu   |     1 |  5 |     30 |        7 |
|  6 | selleri  |     3 |  5 |     30 |        7 |
|  7 | nauris   |     4 |  5 |     30 |        7 |
|  8 | purjo    |     2 |  5 |     30 |        7 |
+----+----------+-------+----+--------+----------+

Taulujen kytkentä

Suurin osa edellisen kyselyn tuottamista riveistä ei ole kiinnostavia, koska eri tauluista valituilla riveillä ei ole mitään tekemistä toistensa kanssa. Vain ne rivit ovat kiinnostavia, joissa taulun tuotteet kenttä id ja taulun tarjoukset kenttä tuote_id ovat samat, koska niistä riveistä näkee, mitkä tuotteet ovat tarjouksessa eri viikkoina.

Tämän kiinnostavan tiedon saamiseksi esille taulut täytyy kytkeä lisäämällä kyselyyn ehto taulun tuotteet kentän id ja taulun tarjoukset kentän tuote_id vastaavuudesta. Kun kyselyssä on monta taulua, ehdoissa mainitaan myös taulun nimi.

SELECT * FROM tuotteet, tarjoukset WHERE tuotteet.id = tarjoukset.tuote_id;
+----+---------+-------+----+--------+----------+
| id | nimi    | hinta | id | viikko | tuote_id |
+----+---------+-------+----+--------+----------+
|  6 | selleri |     3 |  1 |     26 |        6 |
|  1 | kaali   |     3 |  2 |     27 |        1 |
|  3 | peruna  |     3 |  3 |     28 |        3 |
|  6 | selleri |     3 |  4 |     29 |        6 |
|  7 | nauris  |     4 |  5 |     30 |        7 |
+----+---------+-------+----+--------+----------+

Tulostaulussa on vielä paljon turhaa tietoa, minkä vuoksi valitaan tarkemmin haettavat kentät.

SELECT tarjoukset.viikko, tuotteet.nimi
FROM tuotteet, tarjoukset
WHERE tuotteet.id = tarjoukset.tuote_id;
+--------+---------+
| viikko | nimi    |
+--------+---------+
|     26 | selleri |
|     27 | kaali   |
|     28 | peruna  |
|     29 | selleri |
|     30 | nauris  |
+--------+---------+

Esimerkkejä

Seuraavassa on muutamia esimerkkiä tuotepakettien tarkastelusta. Nyt kyselyissä on mukana kolme taulua, jotka täytyy kytkeä toisiinsa kahden ehdon avulla.

Seuraava kysely hakee paketin "Isoäidin herkut" tuotteiden nimet:

SELECT tuotteet.nimi
FROM tuotteet, paketit, sisallot
WHERE paketit.nimi = 'Isoäidin herkut' AND
      sisallot.paketti_id = paketit.id AND
      sisallot.tuote_id = tuotteet.id;
+----------+
| nimi     |
+----------+
| retiisi  |
| porkkana |
| nauris   |
+----------+

Seuraava kysely hakee kaikkien pakettien tiedot:

SELECT paketit.nimi, tuotteet.nimi
FROM tuotteet, paketit, sisallot
WHERE sisallot.paketti_id = paketit.id AND
      sisallot.tuote_id = tuotteet.id;
+-----------------+----------+
| nimi            | nimi     |
+-----------------+----------+
| Isoäidin herkut | retiisi  |
| Isoäidin herkut | porkkana |
| Isoäidin herkut | nauris   |
| Voimapari       | retiisi  |
| Voimapari       | selleri  |
+-----------------+----------+

Seuraava kysely laskee, kuinka monta tuotetta eri paketeissa on:

SELECT paketit.nimi, COUNT(*) AS maara
FROM tuotteet, paketit, sisallot
WHERE sisallot.paketti_id = paketit.id AND
      sisallot.tuote_id = tuotteet.id
GROUP BY paketit.id;
+-----------------+-------+
| nimi            | maara |
+-----------------+-------+
| Isoäidin herkut |     3 |
| Voimapari       |     2 |
+-----------------+-------+

Seuraava kysely näyttää, miten paljon paketin ostamalla säästää:

SELECT paketit.nimi,
       SUM(tuotteet.hinta) AS erikseen,
       paketit.hinta AS paketissa
FROM tuotteet, paketit, sisallot
WHERE sisallot.paketti_id = paketit.id AND
      sisallot.tuote_id = tuotteet.id
GROUP BY paketit.id;
+-----------------+----------+-----------+
| nimi            | erikseen | paketissa |
+-----------------+----------+-----------+
| Isoäidin herkut |       10 |         8 |
| Voimapari       |        7 |         6 |
+-----------------+----------+-----------+

Monen taulun kyselyissä taulujen nimiä voi joutua kirjoittamaan lukuisia kertoja. Joskus on kätevää antaa tauluille väliaikaiset lyhyemmät nimet. Edellisen kyselyn voisi kirjoittaa myös näin:

SELECT p.nimi,
       SUM(t.hinta) AS erikseen,
       p.hinta AS paketissa
FROM tuotteet AS t, paketit AS p, sisallot AS s
WHERE s.paketti_id = p.id AND
      s.tuote_id = t.id
GROUP BY p.id;

Kaikki rivit mukaan

Tarkastellaan lopuksi tilannetta, jossa halutaan laskea jokaisesta tuotteesta, kuinka monta kertaa se on ollut viikon tuote.

Luonnollinen ratkaisuajatus on muodostaa kysely seuraavasti:

SELECT tuotteet.nimi, COUNT(*)
FROM tuotteet, tarjoukset
WHERE tuotteet.id = tarjoukset.tuote_id
GROUP BY tuotteet.id;
+---------+----------+
| nimi    | COUNT(*) |
+---------+----------+
| kaali   |        1 |
| peruna  |        1 |
| selleri |        2 |
| nauris  |        1 |
+---------+----------+

Tästä listasta puuttuvat kuitenkin tuotteet, jotka eivät ole olleet koskaan viikon tuotteina. Ongelmana on, että jos tuote ei esiinny lainkaan taulussa tarjoukset, sitä ei valita koskaan mukaan tauluista tuotteet ja tarjoukset muodostettaviin rivipareihin.

Ratkaisu on käyttää merkintää JOIN, joka on vaihtoehtoinen tapa yhdistää tietoa eri tauluista. Siinä voi käyttää lisämerkintöjä LEFT ja RIGHT, jotka tarkoittavat, että vasemmasta tai oikeasta taulusta otetaan mukaan myös rivit, joilla ei ole vastinetta toisessa taulussa.

Seuraava kysely laskee kaikkien tuotteiden esiintymiskerrat:

SELECT tuotteet.nimi, COUNT(tarjoukset.id)
FROM tuotteet
LEFT JOIN tarjoukset ON tuotteet.id = tarjoukset.tuote_id
GROUP BY tuotteet.id;
+----------+----------------------+
| nimi     | COUNT(tarjoukset.id) |
+----------+----------------------+
| kaali    |                    1 |
| porkkana |                    0 |
| peruna   |                    1 |
| retiisi  |                    0 |
| lanttu   |                    0 |
| selleri  |                    2 |
| nauris   |                    1 |
| purjo    |                    0 |
+----------+----------------------+

Kyselyn toiminta on helpompi ymmärtää, jos mukaan otetaan kaikki kentät:

SELECT * FROM tuotteet
LEFT JOIN tarjoukset ON tuotteet.id = tarjoukset.tuote_id
GROUP BY tuotteet.id;
+----+----------+-------+------+--------+----------+
| id | nimi     | hinta | id   | viikko | tuote_id |
+----+----------+-------+------+--------+----------+
|  1 | kaali    |     3 |    2 |     27 |        1 |
|  2 | porkkana |     2 | NULL |   NULL |     NULL |
|  3 | peruna   |     3 |    3 |     28 |        3 |
|  4 | retiisi  |     4 | NULL |   NULL |     NULL |
|  5 | lanttu   |     1 | NULL |   NULL |     NULL |
|  6 | selleri  |     3 |    1 |     26 |        6 |
|  7 | nauris   |     4 |    5 |     30 |        7 |
|  8 | purjo    |     2 | NULL |   NULL |     NULL |
+----+----------+-------+------+--------+----------+

Neljälle tuotteelle (porkkana, retiisi, lanttu ja purjo) ei ole vastinetta taulussa tarjoukset, minkä vuoksi jokainen niistä saa tulostauluun yhden rivin, jossa kaikki taulun tarjoukset kentät ovat tyhjiä. Lisäksi COUNT(tarjoukset.id) ottaa huomioon vain ne rivit, joilla tarjoukset.id ei ole tyhjä.


Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta