Internet Info, s.r.o. Lupa Měšec Podnikatel Root Zdroják DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

PHP okénko: Spojování tabulek

Dnešní PHP okénko ukazuje možnosti využití spojování databázových tabulek na příkladě několika jednoduchých úloh.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Důležitým obratem při práci s databází je spojování tabulek. Jak už název napovídá, spojování tabulek slouží ke spojení několika tabulek, které jsou v nějakém vztahu (např. master-detail), podle zadaného kritéria.

Dejme tomu, že máme tabulku s výrobky a tabulku se skupinami, do kterých tyto výrobky patří. A dejme tomu, že chceme vypsat přehled všech výrobků spolu se skupinou, do které patří (např. jako výsledek vyhledávání). Na vyřešení tohoto úkolu se ideálně hodí spojování tabulek.

<?php
// odstrašující řešení
$result = mysql_query("SELECT id, skupina, nazev FROM vyrobky WHERE podminka");
while ($row = mysql_fetch_assoc($result)) {
    $skupina = mysql_result(mysql_query("SELECT nazev FROM skupiny WHERE id = $row[skupina]"), 0);
    echo "<a href='?id=$row[id]'>$row[nazev]</a> ($skupina)<br />\n";
}
mysql_free_result($result);

// za určitých okolností přijatelné řešení
$skupiny = array(); // array(id => nazev)
$result = mysql_query("SELECT id, nazev FROM skupiny");
while ($row = mysql_fetch_assoc($result)) {
    $skupiny[$row["id"]] = $row["nazev"];
}
mysql_free_result($result);
$result = mysql_query("SELECT id, skupina, nazev FROM vyrobky WHERE podminka");
while ($row = mysql_fetch_assoc($result)) {
    echo "<a href='?id=$row[id]'>$row[nazev]</a> (" . $skupiny[$row["skupina"]] . ")<br />\n";
}
mysql_free_result($result);

// správné řešení
$result = mysql_query("
    SELECT vyrobky.id, vyrobky.nazev, skupiny.nazev AS skupina_nazev
    FROM vyrobky
    INNER JOIN skupiny ON vyrobky.skupina = skupiny.id
    WHERE podminka
");
while ($row = mysql_fetch_assoc($result)) {
    echo "<a href='?id=$row[id]'>$row[nazev]</a> ($row[skupina_nazev])<br />\n";
}
mysql_free_result($result);
?> 

První řešení je odstrašující proto, že v každém průchodu cyklem se znovu klade dotaz do databáze, což je drahá (pomalá) operace. Druhé řešení je ideální v případě, kdy se proměnná $skupiny využije i k jinému účelu (např. k výpisu všech skupin v navigaci).

Při spojování tabulek se vyplatí dodržovat několik zásad:

  1. Všechny sloupce by měly být pomocí tečkové notace uvozeny tabulkou, ke které se vztahují. Předejde se tím problémům při přidání stejnojmenného sloupce do druhé tabulky.
  2. Ze stejného důvodu je lepší se vyhnout zápisu SELECT *. Pokud chcete získat většinu sloupců z jedné tabulky, můžete použít  SELECT tabulka.*.
  3. Pro spojení tabulek se dá používat i zápis FROM vyrobky, skupiny WHERE vyrobky.skupina = skupiny.id. Tento zápis osobně nemám rád, protože dochází ke smíchání spojovacích a ostatních podmínek, což je nepřehledné obzvláště při větším množství spojovaných tabulek.
  4. Existuje i typ spojení NATURAL JOIN, který spojí tabulky podle shodných hodnot stejnojmenných sloupců, ten ale vyžaduje volit pojmenování sloupců tak, aby vzájemně nekolidovaly (tedy např. vyrobky(id_vyrobek, id_skupina, v_nazev), skupiny(id_skupina, s_nazev)), což názvy sloupců zbytečně komplikuje.

Kromě INNER JOIN existuje ještě typ spojení LEFT JOIN, které se liší tím, že pokud v pravé tabulce není nalezen žádný odpovídající řádek, dosadí se místo něj hodnoty NULL. Tento typ spojení se dá využít v situaci, kdy nemáme jistotu, že v pravé tabulce bude existovat alespoň jeden odpovídající záznam, tedy např. pokud výrobek nemusí mít nutně přiřazenou nějakou skupinu.

<?php
$result = mysql_query("
    SELECT vyrobky.id, vyrobky.nazev, skupiny.nazev AS skupina_nazev
    FROM vyrobky
    LEFT JOIN skupiny ON vyrobky.skupina = skupiny.id
    WHERE podminka
");
while ($row = mysql_fetch_assoc($result)) {
    echo "<a href='?id=$row[id]'>$row[nazev]</a> (" . (isset($row["skupina_nazev"]) ? $row["skupina_nazev"] : "bez skupiny") . ")<br />\n";
}
mysql_free_result($result);
?> 

Spojování tabulek se dá využít i ve složitějších případech. Řekněme, že chceme vypsat postupně všechny skupiny se všemi jejich výrobky.

TIB2012

       
<?php
// přehledné, ale neefektivní
$result = mysql_query("SELECT * FROM skupiny ORDER BY nazev");
while ($row = mysql_fetch_assoc($result)) {
    echo "<h3>$row[nazev]</h3>\n";
    $result1 = mysql_query("SELECT * FROM vyrobky WHERE skupina = '$row[id]'");
    while ($row1 = mysql_fetch_assoc($result1)) {
        echo "<a href='?id=$row1[id]'>$row1[nazev]</a><br />\n";
    }
    mysql_free_result($result1);
}
mysql_free_result($result);

// náročné na paměť
$vyrobky = array(); // array(skupina => array($row, ...), ...)
$result = mysql_query("SELECT id, skupina, nazev FROM vyrobky ORDER BY skupina");
while ($row = mysql_fetch_assoc($result)) {
    $vyrobky[$row["skupina"]][] = $row;
}
mysql_free_result($result);
$result = mysql_query("SELECT * FROM skupiny ORDER BY nazev");
while ($row = mysql_fetch_assoc($result)) {
    echo "<h3>$row[nazev]</h3>\n";
    if (isset($vyrobky[$row["id"]])) {
        foreach ($vyrobky[$row["id"]] as $row1) {
            echo "<a href='?id=$row1[id]'>$row1[nazev]</a><br />\n";
        }
    }
}
mysql_free_result($result);

// méně přehledné, ale efektivnější
$skupina_prev = 0;
$result = mysql_query("
    SELECT skupiny.*, vyrobky.id AS vyrobky_id, vyrobky.nazev AS vyrobky_nazev
    FROM skupiny
    LEFT JOIN vyrobky ON skupiny.id = vyrobky.skupina
    ORDER BY skupiny.nazev, skupiny.id
");
while ($row = mysql_fetch_assoc($result)) {
    if ($skupina_prev != $row["id"]) { // začíná nová skupina, vypíšeme nadpis
        echo "<h3>$row[nazev]</h3>\n";
        $skupina_prev = $row["id"];
    }
    if (isset($row["vyrobky_id"])) { // byl nalezen odpovídající výrobek
        echo "<a href='?id=$row[vyrobky_id]'>$row[vyrobky_nazev]</a><br />\n";
    }
}
mysql_free_result($result);
?> 

Vzhledem k tomu, že u třetího způsobu není na první pohled vůbec patrné, co kód dělá, zvolil bych ve většině případů spíše první nebo druhý způsob (první, pokud je málo skupin a hodně výrobků, druhý v opačném případě). Třetí případ bych tedy zvolil asi jen v situaci, kdy databázový server běží na jiném stroji než webový server a cena každého dotazu do databáze je opravdu vysoká.


Podobně laděné texty můžete najít i na autorově weblogu PHP triky.

Jakub Vrána

Jakub Vrána

Autor se živí programováním v PHP, podílí se na jeho oficiální dokumentaci, vyučuje ho na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.

Školení: Django framework: Struktura a základy vývoje (nejen) webových aplikací

Django je vyspělý webový framework napsaný v jazyce Python, který podporuje extrémně rychlý vývoj společně s dodržováním principů dobrého návrhu. Snaží se co nejvíce automatizovat a drží se principu DRY (z anglického Don't Repeat Yourself — neopakuj se).

  • Instalace potřebného softwaru
  • Programování v Pythonu: příkazy, funkce, datové typy, moduly, objekty, výjimky
  • Struktura aplikace v Djangu
  • Typické záležitosti webových aplikací: Napojení na databázi, zpracování vstupu od uživatele, přihlášení či generování dynamického obsahu.
  • Implementace principu MVC: modely, pohledy (views) a šablony
  • Seznámení s užitečnými komponenty frameworku Django
  • Šikovné praktiky

Podrobnější informace a přihláška

Ohodnoťte jako ve škole:
Průměrná známka 3,40

Přehled názorů

prehlednost
Michal Molhanec 11. 4. 2005 00:24
Nový
Smysl?
Jakub Hegenbart 11. 4. 2005 01:44
Nový
├ 
Re: Smysl?
Michal Kubeček 11. 4. 2005 02:16
Nový
│
└ 
Re: Smysl?
Jakub Vrána 11. 4. 2005 07:30
Nový
│
 
├ 
Re: Smysl?
anonymní uživatel 11. 4. 2005 08:59
Nový
│
 
│
└ 
Re: Smysl?
Petr 11. 4. 2005 09:57
Nový
│
 
│
 
└ 
Re: Smysl?
anonymní uživatel 11. 4. 2005 13:20
Nový
│
 
│
 
 
└ 
Re: Smysl?
Tom 12. 4. 2005 14:35
Nový
│
 
├ 
Re: Smysl?
petr andrs 11. 4. 2005 21:33
Nový
│
 
└ 
Re: Smysl?
Michal Kubeček 11. 4. 2005 22:21
Nový
└ 
Re: Smysl?
Michal Kára 11. 4. 2005 08:48
Nový
 
└ 
Re: Smysl?
Jakub Hegenbart 11. 4. 2005 09:03
Nový
 
 
├ 
Re: Smysl?
Michal Kára 11. 4. 2005 09:24
Nový
 
 
└ 
Re: Smysl?
Jakub Trávník 11. 4. 2005 11:26
Nový
 
 
 
└ 
Re: Smysl?
Jakub Hegenbart 11. 4. 2005 16:01
Nový
Jako příklad všechny tři varianty odstrašující
Petr Bravenec 11. 4. 2005 08:10
Nový
└ 
Re: Jako příklad všechny tři varianty odstrašující
Vladimir Kralik 11. 4. 2005 19:50
Nový
 
└ 
Re: Jako příklad všechny tři varianty odstrašující
Petr Bravenec 12. 4. 2005 06:10
Nový
LEFT JOIN
MokrOus 11. 4. 2005 08:48
Nový
├ 
Re: LEFT JOIN
Jakub Vrána 11. 4. 2005 08:55
Nový
│
└ 
Re: LEFT JOIN
Ján Sokoly 11. 4. 2005 13:36
Nový
│
 
├ 
Re: LEFT JOIN
dgx 11. 4. 2005 18:09
Nový
│
 
└ 
Re: LEFT JOIN
Michal Kubeček 11. 4. 2005 22:15
Nový
│
 
 
└ 
Re: LEFT JOIN
Ján Sokoly 11. 4. 2005 22:25
Nový
│
 
 
 
└ 
Re: LEFT JOIN
Michal Kubeček 12. 4. 2005 04:00
Nový
│
 
 
 
 
└ 
Re: LEFT JOIN
Ján Sokoly 12. 4. 2005 09:12
Nový
│
 
 
 
 
 
└ 
Re: LEFT JOIN
Michal Kubeček 12. 4. 2005 12:33
Nový
└ 
Re: LEFT JOIN
Petr Bravenec 11. 4. 2005 09:36
Nový
Nechci urazit autora
BigSam 11. 4. 2005 09:04
Nový
└ 
Re: Nechci urazit autora
dave 11. 4. 2005 11:20
Nový
OT - ukázky kódu a posuvníky
martin 11. 4. 2005 11:24
Nový
└ 
Re: OT - ukázky kódu a posuvníky
MMM 11. 4. 2005 14:27
Nový
...
AraxoN 11. 4. 2005 13:05
Nový
├ 
Re: ...
Jakub Vrána 11. 4. 2005 13:15
Nový
│
├ 
Re: ...
dave 11. 4. 2005 13:27
Nový
│
└ 
Re: ...
AraxoN 11. 4. 2005 16:03
Nový
└ 
Re: ...
Ivo 11. 4. 2005 14:41
Nový
 
└ 
Re: ...
anonymní uživatel 11. 4. 2005 15:26
Nový
 
 
└ 
Re: ...
HKMaly 12. 4. 2005 23:05
Nový
forum
gemda 11. 4. 2005 20:46
Nový
├ 
Re: forum
anonymní uživatel 12. 4. 2005 08:40
Nový
└ 
Re: forum
Jakub Vrána 12. 4. 2005 11:59
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem