3. blokk · Lekérdezések — SELECT · 7c. témakör
Egy webshop ereje a táblák kapcsolataiban van: ki rendelt mit, melyik
kategóriában, mennyiért? A JOIN több táblát kapcsol össze
egyetlen lekérdezésben. Ebben a részben halmaz-ábrákon (Venn-diagramokon) keresztül nézzük
meg, melyik JOIN mit ad vissza — és pontosan hogyan párosulnak a sorok.
A jól megtervezett adatbázisban az adatok külön táblákban élnek (a vásárló a
vasarlo-ban, a rendelése a rendeles-ben).
Ha egy kérdés több táblát is érint — pl. „melyik vásárló mikor rendelt?” —,
akkor ezeket össze kell kapcsolnunk. Erre való a
JOIN: a kapcsolódó táblák sorait egy közös érték
mentén párosítja, és egyetlen, széles eredménytáblát ad.
A táblák az idegen kulcsokkal kapcsolódnak (lásd a tervezési és EER
témaköröket): a rendeles.vasarlo_id a
vasarlo.id-ra mutat. A JOIN éppen ezt a kapcsolatot használja
fel a párosításhoz.
A JOIN-nak két dolgot kell megadnunk: melyik két táblát kapcsoljuk össze, és
milyen feltétellel párosítjuk a sorokat. Ez utóbbi az
ON feltétel — szinte mindig az idegen kulcs és a hivatkozott
elsődleges kulcs egyenlősége:
SELECT v.vezeteknev, r.id, r.rendeles_datuma FROM vasarlo v JOIN rendeles r ON r.vasarlo_id = v.id; -- └── a kapcsolási feltétel: a gyermek FK = a szülő PK
A párosítás logikája: a szerver minden rendeles
sorhoz megkeresi azt a vasarlo sort, amelyre az
ON feltétel igaz (r.vasarlo_id = v.id),
és a kettőt egyetlen sorrá fűzi össze. Az alábbi ábra ezt mutatja — a vonalak a párosított
sorokat kötik össze:
Az eredményt úgy képzeld el, mint egyetlen széles táblát, amelyben egymás mellett áll a két tábla összes oszlopa — minden sikeres párosítás egy-egy sor:
| v.id | v.vezeteknev | r.id | r.vasarlo_id | r.rendeles_datuma |
|---|---|---|---|---|
| 1 | Nagy | 10 | 1 | 2026-03-01 10:00:00 |
| 1 | Nagy | 11 | 1 | 2026-03-05 14:30:00 |
| 2 | Kiss | 12 | 2 | 2026-03-07 09:15:00 |
Figyeld meg: Nagy kétszer szerepel, mert két rendelése van — egy bal oldali sor annyiszor jelenik meg, ahány pár jut rá. Erre a széles táblára működik aztán minden, amit eddig tanultunk (WHERE, ORDER BY, GROUP BY…), a SELECT-listával pedig csak a kívánt oszlopokat mutatjuk meg belőle.
Ha a két táblában azonos nevű oszlop van (pl. mindkettőben
id), a nem egyértelmű oszlopok elé ki kell írni, melyik táblából
jönnek — különben „ambiguous” hibát kapnánk. Erre két lehetőség van: a teljes
táblanév vagy egy rövid alias — mindkettő helyes:
-- 1. Teljes táblanevekkel — alias nélkül is működik: SELECT vasarlo.vezeteknev, rendeles.id, rendeles.rendeles_datuma FROM vasarlo JOIN rendeles ON rendeles.vasarlo_id = vasarlo.id; -- 2. Rövid aliasokkal — ugyanaz, csak tömörebb (a gyakorlatban ezt szokás): SELECT v.vezeteknev, r.id, r.rendeles_datuma FROM vasarlo v JOIN rendeles r ON r.vasarlo_id = v.id;
Emlékezz a 7a-ból: ha aliast adtál, a tábla az eredeti nevén abban a lekérdezésben már nem érhető el. Egyetlen hely van, ahol az alias tényleg kötelező: az önkapcsolás (9. szakasz), mert ott ugyanaz a tábla kétszer szerepel.
Az INNER JOIN (vagy egyszerűen JOIN)
csak azokat a sorokat adja vissza, amelyekhez mindkét táblában van pár.
Halmazként ez a két tábla metszete.
A = {1, 2, 3} B = {2, 3, 4}
Csak a közös kulcsok (2 és 3) maradnak. Az 1 (csak A-ban) és a 4 (csak B-ben) kimarad.
Webshop: csak azok a vásárlók, akiknek van rendelése.
SELECT v.vezeteknev, r.id AS rendeles_id, r.rendeles_datuma FROM vasarlo v INNER JOIN rendeles r ON r.vasarlo_id = v.id;
Ha csak annyit írsz, hogy JOIN, az alapból
INNER JOIN-t jelent. A két forma teljesen egyenértékű.
A LEFT JOIN (más néven LEFT OUTER JOIN)
a bal tábla összes sorát visszaadja, és mellé a jobb tábla
egyező sorait. Ahol a bal sorhoz nincs pár a jobb táblában, ott a jobb oldali
oszlopok NULL-ok lesznek.
A = {1, 2, 3} B = {2, 3, 4}
A bal tábla minden sora megjelenik. Az 1-hez nincs B-beli pár → a B-oszlopok NULL-ok. A 4 (csak B-ben) kimarad.
Webshop: minden vásárló, a rendelés nélkülieknél NULL.
-- MINDEN vásárló, a rendeléseivel (vagy nélkülük): SELECT v.vezeteknev, r.id AS rendeles_id FROM vasarlo v LEFT JOIN rendeles r ON r.vasarlo_id = v.id;
| vezeteknev | rendeles_id |
|---|---|
| Nagy | 10 |
| Nagy | 11 |
| Kiss | 12 |
| Tóth | NULL |
| Szabó | NULL |
Tóth és Szabó is megjelenik — a hiányzó pár helyén NULL-lal. (A vásárló nélküli 13-as rendelés viszont itt nem szerepel: az a jobb tábla párja nélküli sora.)
A LEFT JOIN tipikus haszna: „kik nem rendeltek még?” Ehhez LEFT JOIN után
WHERE r.id IS NULL szűréssel pont a pár nélküli bal sorokat
kapod meg.
A RIGHT JOIN a LEFT tükörképe: a jobb tábla
összes sorát adja vissza, és mellé a bal tábla egyezőit; ahol nincs bal pár, ott a bal oldali
oszlopok NULL-ok.
A = {1, 2, 3} B = {2, 3, 4}
A jobb tábla minden sora megjelenik. A 4-hez nincs A-beli pár → az A-oszlopok NULL-ok. Az 1 (csak A-ban) kimarad.
Bármely RIGHT JOIN átírható LEFT JOIN-ná a táblák felcserélésével.
SELECT v.vezeteknev, r.id AS rendeles_id FROM vasarlo v RIGHT JOIN rendeles r ON r.vasarlo_id = v.id;
| vezeteknev | rendeles_id |
|---|---|
| Nagy | 10 |
| Nagy | 11 |
| Kiss | 12 |
| NULL | 13 |
Most minden rendelés megjelenik: a 13-as „vendégrendeléshez” nincs vásárló, ezért ott a bal oldal NULL. Tóth és Szabó viszont (rendelés híján) kimaradt — pont fordítva, mint a LEFT JOIN-nál.
A RIGHT JOIN ritkábban használt — a legtöbben inkább LEFT JOIN-t írnak, és a kívánt táblát teszik „bal” oldalra. A kettő közötti váltás csak nézőpont kérdése.
A FULL OUTER JOIN mindkét tábla összes sorát
visszaadja: ahol van pár, összefűzi; ahol nincs (akár bal, akár jobb oldalon), ott a hiányzó
oldal oszlopai NULL-ok. Halmazként ez a két tábla
uniója.
A = {1, 2, 3} B = {2, 3, 4}
Minden kulcs megjelenik: az 1-nél a B-oldal, a 4-nél az A-oldal NULL. A 2, 3 párba állítva.
A MySQL nem támogatja közvetlenül a
FULL OUTER JOIN-t! Helyette egy
LEFT JOIN és egy RIGHT JOIN
eredményét egyesítjük UNION-nal. (Az alábbi gyakorló
SQLite-motorja viszont ismeri a FULL JOIN-t, így ott közvetlenül is kipróbálható.)
A CROSS JOIN-nak nincs
ON feltétele: a bal tábla minden sorát
összepárosítja a jobb tábla minden sorával. Ez a halmazok
Descartes-szorzata. Az eredmény sorszáma a két tábla sorszámának
szorzata.
-- Minden vásárló minden kategóriával párosítva (4 × 3 = 12 sor): SELECT v.vezeteknev, k.nev AS kategoria FROM vasarlo v CROSS JOIN kategoria k ORDER BY v.vezeteknev, kategoria;
Mikor jó ez nekünk? Amikor tényleg minden kombinációra szükség van:
A CROSS JOIN könnyen hatalmas eredményt ad (a sorszámok szorzata). Ritkán használjuk
szándékosan — gyakran úgy keletkezik véletlenül, hogy elfelejtjük az
ON feltételt egy sima JOIN-nál!
Több JOIN is láncolható egymás után. A webshop „teljes” kérdéséhez — „ki, mit, mennyiért
rendelt?” — négy táblát kötünk össze a kapcsolataik mentén:
vasarlo → rendeles → rendeles_tetel → termek.
SELECT v.vezeteknev, r.id AS rendeles, t.nev AS termek, rt.mennyiseg, rt.egysegar FROM vasarlo v JOIN rendeles r ON r.vasarlo_id = v.id JOIN rendeles_tetel rt ON rt.rendeles_id = r.id JOIN termek t ON t.id = rt.termek_id;
Így oldjuk fel az N:M kapcsolatot is: a
rendeles_tetel kapcsolótáblán keresztül kötjük össze a
rendeléseket a termékekkel. Aggregálással pedig pl. vásárlónkénti összköltést kapunk:
SELECT v.vezeteknev, SUM(rt.mennyiseg * rt.egysegar) AS osszkoltes FROM vasarlo v JOIN rendeles r ON r.vasarlo_id = v.id JOIN rendeles_tetel rt ON rt.rendeles_id = r.id GROUP BY v.id ORDER BY osszkoltes DESC;
Egy tábla önmagával is összekapcsolható — ez az önkapcsolás.
Akkor kell, ha egy soron belül egy másik ugyanabban a táblában lévő sorra
hivatkozunk. Webshop-példa: a kategoria tábla
szulo_id oszlopa egy másik kategóriára mutat. A szülő nevének
megjelenítéséhez a táblát kétszer használjuk, két különböző aliasszal:
SELECT gy.nev AS alkategoria, sz.nev AS szulo_kategoria FROM kategoria gy LEFT JOIN kategoria sz ON gy.szulo_id = sz.id; -- gy = gyermek, sz = szülő — ugyanaz a tábla, két szerepben
A főkategóriáknak (pl. „Elektronika”) nincs szülőjük
(szulo_id IS NULL). A LEFT JOIN így
is megjeleníti őket — a szulo_kategoria oszlopukban NULL-lal.
Sima JOIN-nal kimaradnának.
Próbáld ki a JOIN-okat a webshopon! Figyeld meg, hogyan jelennek meg / tűnnek el a rendelés nélküli vásárlók (Tóth, Szabó) és a vásárló nélküli 13-as „vendégrendelés” a különböző JOIN-típusoknál.
A motor SQLite-alapú, amely az INNER,
LEFT, RIGHT,
FULL OUTER és CROSS JOIN-t is
ismeri. Fontos: a MySQL viszont a RIGHT-ot ismeri, de a FULL OUTER JOIN-t
nem — ott UNION-nal helyettesítjük. (Internetkapcsolat szükséges a motorhoz.)
⭐ alap · ⭐⭐ haladó · ⭐⭐⭐ kihívás — a kódírós feladatokat futtasd is le a fenti élő gyakorlóban!
Mivel adjuk meg a JOIN kapcsolási feltételét?
Miért használunk tábla-aliasokat a JOIN-okban? Adj legalább egy okot!
Hány táblát és milyen sorrendben kapcsolnál össze, hogy lásd, „ki, milyen terméket rendelt”?
Mit ad vissza ez a lekérdezés? (Tóth és Szabó nem rendelt.) Előbb gondold végig, aztán futtasd le!
SELECT v.vezeteknev, r.id FROM vasarlo v LEFT JOIN rendeles r ON r.vasarlo_id = v.id;
Miért lehet veszélyes ez a lekérdezés?
SELECT v.vezeteknev, t.nev FROM vasarlo v JOIN termek t;
Mit csinál a gy és sz alias ebben
a lekérdezésben?
SELECT gy.nev AS alkategoria, sz.nev AS szulo_kategoria FROM kategoria gy LEFT JOIN kategoria sz ON gy.szulo_id = sz.id;
Kódírás: írj lekérdezést, amely kiírja minden rendelés azonosítóját és a hozzá tartozó vásárló vezetéknevét (csak a tényleges rendeléseket)!
Kódírás: listázd azokat a vásárlókat (vezetéknév), akiknek nincs egyetlen rendelésük sem!
Kódírás: add meg vásárlónként az összes elköltött összeget (mennyiség × egységár), a legtöbbet költőtől kezdve!
Kódírás: írd ki minden termék nevét a kategóriája nevével együtt — azokat is, amelyeknek nincs kategóriája!
Minden kérdésnél egy válasz helyes. A végén pontszámot kapsz.
| Fogalom | Jelentés |
|---|---|
| JOIN · ON | táblák sorainak párosítása az ON feltétel (FK = PK) mentén |
| tábla-alias | rövid név a táblának (vasarlo v) — nem kötelező: a nem egyértelmű oszlop a teljes táblanévvel is minősíthető; önkapcsolásnál viszont muszáj |
| INNER JOIN | csak az egyező sorok — a két tábla metszete |
| LEFT JOIN | a bal tábla minden sora; pár híján a jobb oldal NULL |
| RIGHT JOIN | a jobb tábla minden sora — a LEFT tükörképe |
| FULL OUTER JOIN | mindkét tábla minden sora (unió) — MySQL-ben UNION-nal |
| CROSS JOIN | Descartes-szorzat ON nélkül — a sorszámok szorzata |
| „kik nem” minta | LEFT JOIN + WHERE jobb.id IS NULL — a pár nélküli bal sorok |
| láncolt JOIN | több tábla sorban összekapcsolva (N:M a kapcsolótáblán át) |
| önkapcsolás (self join) | a tábla önmagával, két aliasszal (pl. gyermek–szülő kategória) |
JOIN a táblák sorait egy ON
feltétel (jellemzően FK = PK) mentén párosítja.