3. blokk · Lekérdezések — SELECT · 7d. témakör
Néha egy lekérdezéshez előbb egy másik lekérdezés eredménye kell: „melyik termék drágább az átlagnál?”, „kik nem rendeltek?”. A beágyazott lekérdezés (subquery) egy lekérdezés egy másik lekérdezésen belül. Megnézzük, hol állhat, milyen formákban, és milyen kötöttségekkel.
A beágyazott lekérdezés (angolul subquery) egy zárójelbe tett
SELECT, amely egy nagyobb (külső) lekérdezés része. A szerver
előbb a belső lekérdezést futtatja le, és annak eredményét használja fel a
külső lekérdezésben.
-- "Mely termékek drágábbak az átlagnál?" SELECT nev, ar FROM termek WHERE ar > (SELECT AVG(ar) FROM termek); -- └── belső lekérdezés: kiszámolja az átlagot (pl. 87990)
A belső lekérdezés itt egyetlen számot ad (az átlagárat), a külső pedig ezzel hasonlít össze. Sokszor JOIN-nal is megoldható ugyanaz — a subquery viszont gyakran olvashatóbb, ha „egy érték kell egy másik lekérdezésből” a feladat.
Subquery több fő helyen állhat egy SELECT-ben. Az alábbi vázlaton a számok jelölik ezeket a helyeket:
SELECT oszlopok, () AS ertek FROM () AS sub WHERE oszlop = ();
👆 Kattints a számokra — megmutatjuk, mi állhat az adott helyen!
Mindegyik helynek megvan a maga tipikus formája és kötöttsége:
| Hely | Mire jó | Mit adhat vissza |
|---|---|---|
WHERE / HAVING | Szűrés egy másik lekérdezés eredménye alapján | Skalár (1 érték) összehasonlításhoz; vagy 1 oszlop több sora az IN-hez |
SELECT-lista | Egy számolt érték oszlopként minden sorhoz | Pontosan egy érték (skalár), soronként |
FROM | Egy „ideiglenes tábla”, amin tovább dolgozunk | Teljes táblát (több sor, több oszlop) — kötelező alias |
Mindig kérdezd meg: a subquery hány értéket ad vissza?
Egyet (skalár) → összehasonlításhoz (=,
>) vagy a SELECT-listába. Egy oszlop több
sorát → IN-hez. Egész táblát → a
FROM-ba. Ez dönti el, hová illeszthető.
A skalár subquery pontosan egy sort és egy oszlopot (tehát
egyetlen értéket) ad vissza. Ezzel összehasonlító operátort (=,
>, <…) használhatunk:
-- A legdrágább termék(ek): SELECT nev, ar FROM termek WHERE ar = (SELECT MAX(ar) FROM termek);
Ha skalár helyen (pl. ar = (...)) a subquery több
sort ad vissza, az hibát okoz („Subquery returns more than 1
row”). Ilyenkor vagy szűkítsd egy sorra (pl. MAX,
LIMIT 1), vagy használj IN-t a
= helyett.
Az IN egy oszlopnyi értékhalmazzal hasonlít: a
sor akkor felel meg, ha az értéke szerepel a subquery által visszaadott listában.
-- Azok a termékek, amelyek az "Elektronika" alkategóriáiban vannak: SELECT nev FROM termek WHERE kategoria_id IN (SELECT id FROM kategoria WHERE szulo_id = 1);
Ha a subquery akár egyetlen NULL-t is visszaad, a
NOT IN az egész feltételt „ismeretlenné” teszi, és
nulla sort ad — látszólag indok nélkül! Ezért a
NOT IN subquerynél biztosítsd, hogy ne legyen NULL az
eredményben (pl. WHERE oszlop IS NOT NULL), vagy használj
NOT EXISTS-t helyette.
Az EXISTS nem értéket hasonlít, hanem azt nézi, hogy a belső
lekérdezés ad-e legalább egy sort. Igaz/hamis eredményt ad. Tipikusan
korrelált (a külső sorra hivatkozik):
-- Azok a vásárlók, akiknek VAN legalább egy rendelése: SELECT v.vezeteknev FROM vasarlo v WHERE EXISTS (SELECT 1 FROM rendeles r WHERE r.vasarlo_id = v.id); -- Akiknek NINCS rendelése (a NOT IN biztonságos alternatívája): SELECT v.vezeteknev FROM vasarlo v WHERE NOT EXISTS (SELECT 1 FROM rendeles r WHERE r.vasarlo_id = v.id);
Az EXISTS-nél mindegy, mit ír a belső SELECT (gyakran
SELECT 1) — csak a sorok létezése számít, az értékek
nem. Az EXISTS sokszor gyorsabb is, mert leáll az első
találatnál.
| Független (nem korrelált) | Korrelált | |
|---|---|---|
| Hivatkozik a külső sorra? | Nem | Igen |
| Hányszor fut le? | Egyszer (az érték újrahasználható) | Külső soronként egyszer (logikailag) |
| Példa | ar > (SELECT AVG(ar) FROM termek) | EXISTS (SELECT 1 FROM rendeles r WHERE r.vasarlo_id = v.id) |
A független subquery önállóan is lefutna (nem ismeri a külső lekérdezést). A korrelált a külső lekérdezés aktuális sorára hivatkozik, ezért „minden külső sorhoz” kiértékelődik — ez erősebb, de nagy táblán lassabb lehet.
Egy skalár subquery oszlopként is állhat — minden külső sorhoz kiszámol egy értéket. Pl. vásárlónként a rendelések száma, JOIN nélkül:
SELECT v.vezeteknev, (SELECT COUNT(*) FROM rendeles r WHERE r.vasarlo_id = v.id) AS rendelesek FROM vasarlo v;
A FROM-ban álló subquery egy ideiglenes táblát (derived table) hoz létre, amin a külső lekérdezés tovább dolgozhat — pl. egy aggregált eredményt szűrünk tovább:
-- Csak azok a kategóriák, ahol az átlagár 10 000 Ft felett van: SELECT kat, atlagar FROM (SELECT kategoria_id AS kat, AVG(ar) AS atlagar FROM termek GROUP BY kategoria_id) AS sub WHERE atlagar > 10000;
A FROM-ban álló subquerynek mindig kell adni egy aliast (itt
sub), különben hibát kapsz. Ez a derived table neve, amire a
külső lekérdezésben hivatkozhatsz.
A MySQL ismeri az ANY és ALL
operátorokat is, amelyek egy összehasonlítást egy egész oszlopnyi értékre
alkalmaznak:
-- ANY: igaz, ha LEGALÁBB EGY értéknél teljesül (= IN rokona) SELECT nev FROM termek WHERE ar > ANY (SELECT ar FROM termek WHERE kategoria_id = 3); -- (drágább, mint a 3-as kategória LEGOLCSÓBB terméke) -- ALL: igaz, ha MINDEN értéknél teljesül SELECT nev FROM termek WHERE ar > ALL (SELECT ar FROM termek WHERE kategoria_id = 3); -- (drágább, mint a 3-as kategória LEGDRÁGÁBB terméke)
Ezek gyakran kiválthatók MAX/MIN-nel:
> ALL (...) = > (SELECT MAX(...)),
és > ANY (...) =
> (SELECT MIN(...)).
| Kontextus | A subquery mit adhat | Kötöttség / buktató |
|---|---|---|
Összehasonlítás (=, >) | Pontosan 1 érték (skalár) | Több sor → hiba |
IN / NOT IN | 1 oszlop, több sor | NOT IN + NULL → 0 sor (csapda) |
EXISTS | Bármit (csak létezést néz) | Általában korrelált; SELECT 1 elég |
| SELECT-lista | Pontosan 1 érték, soronként | Több érték → hiba |
FROM | Egész tábla | Kötelező alias |
ANY / ALL | 1 oszlop, több sor | a MySQL ismeri, az SQLite-gyakorló nem |
Próbáld ki a beágyazott lekérdezéseket a webshopon! A chipek a különböző formákat töltik be —
futtasd, majd alakítsd át (pl. cseréld az AVG-t
MAX-ra, vagy az EXISTS-t
NOT EXISTS-re).
A motor SQLite-alapú: az
IN/NOT IN,
EXISTS, korrelált és FROM-beli subquery-k azonosak a
MySQL-lel. Két eltérés: az
ANY/ALL-t az SQLite nem ismeri (a
gyakorló jelzi, és megmutatja a MAX/MIN-es helyettesítést); a több sort adó
skalár subquerynél pedig a MySQL hibát dob, az SQLite viszont csendben az
első sorral számol — ilyenkor a gyakorló sárga jelzéssel figyelmeztet.
(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 vizsgálod, hogy egy vásárlónak van-e legalább egy rendelése (létezés-vizsgálat)?
Mire való a SELECT 1 az EXISTS
belsejében?
Mivel helyettesíthető a MySQL ar > ALL (...) kifejezése?
Mit ad vissza ez a lekérdezés? Előbb gondold végig, aztán futtasd le!
SELECT nev, ar FROM termek WHERE ar > (SELECT AVG(ar) FROM termek);
Miért ad hibát ez a lekérdezés éles MySQL-en (a 3-as kategóriában több termék is van)? Futtasd le a gyakorlóban — figyeld meg a sárga jelzést —, majd javítsd ki kétféleképpen!
SELECT nev FROM termek WHERE ar = (SELECT ar FROM termek WHERE kategoria_id = 3);
Mit csinál ez a lekérdezés, és miért korrelált?
SELECT v.vezeteknev, (SELECT COUNT(*) FROM rendeles r WHERE r.vasarlo_id = v.id) AS db FROM vasarlo v;
Kódírás: írj lekérdezést a legdrágább termék(ek) nevére, skalár subqueryvel!
Kódírás: add meg azoknak a vásárlóknak a vezetéknevét, akiknek
nincs rendelésük — NOT EXISTS
használatával!
Kódírás: származtatott táblával (FROM-beli subquery) add meg azokat a kategória-azonosítókat, ahol az átlagár 10 000 Ft felett van!
Minden kérdésnél egy válasz helyes. A végén pontszámot kapsz.
| Fogalom | Jelentés |
|---|---|
| beágyazott lekérdezés (subquery) | zárójelbe tett SELECT egy nagyobb lekérdezésen belül |
| skalár subquery | pontosan egy értéket ad — összehasonlításhoz és a SELECT-listába |
| IN-lista subquery | egy oszlop több sora — a sor akkor felel meg, ha az érték szerepel benne |
| NOT IN NULL-csapda | egyetlen NULL a listában → 0 sor; helyette NOT EXISTS |
| EXISTS · SELECT 1 | létezés-vizsgálat — csak a sor léte számít, az érték nem |
| korrelált subquery | a külső sorra hivatkozik — soronként értékelődik ki |
| független subquery | önállóan is lefutna — egyszer értékelődik ki |
| származtatott tábla (derived table) | FROM-beli subquery ideiglenes táblaként — kötelező aliasszal |
| ANY / ALL | összehasonlítás egy oszlopnyi értékre — a MySQL ismeri (a gyakorló nem), MAX/MIN-nel kiváltható |
SELECT egy másik lekérdezésen
belül; előbb a belső fut le.WHERE/HAVING,
SELECT-lista, FROM (és
INSERT/UPDATE/DELETE is).IN-hez;
egész tábla: a FROM-ba.EXISTS a létezést nézi; a
NOT EXISTS a NOT IN biztonságos
alternatívája.NOT IN + NULL csapda; az
ANY/ALL-t a gyakorló nem ismeri
(a MySQL igen).