Proč porozumět SELECT, WHERE a JOIN
SQL (Structured Query Language) je standardní jazyk pro práci s relačními databázemi. Základem prakticky každého dotazu jsou tři stavební kameny: SELECT pro výběr sloupců a výpočtů, WHERE pro filtrování řádků a JOIN pro spojování tabulek. Důkladná znalost jejich syntaxe, pořadí vyhodnocování a běžných úskalí je klíčová pro správné i výkonné dotazy.
Modelový datový model pro příklady
V příkladech budeme pracovat s tabulkami:
- customers(customer_id, name, city, created_at)
- orders(order_id, customer_id, order_date, status, total_amount)
- order_items(order_id, product_id, qty, unit_price)
- products(product_id, sku, product_name, category)
Základní kostra dotazu a pořadí vyhodnocování
Typická kostra dotazu:
SELECT [DISTINCT] sloupce/výrazy
FROM tabulka [JOIN ...]
WHERE podmínky
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT/OFFSET ...
Logické pořadí vyhodnocení (důležité pro pochopení chování): FROM → ON → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT. Například alias definovaný v SELECT nelze použít ve WHERE (s výjimkou některých dialektů), protože WHERE se vyhodnocuje dříve.
SELECT: výběr sloupců, aliasy a výrazy
- Výběr konkrétních sloupců:
SELECT customer_id, name FROM customers;MinimalizujteSELECT *kvůli výkonu a stabilitě schématu. - Alias sloupce:
SELECT name AS customer_name(zlepšuje srozumitelnost výstupu;ASje volitelné). - Výrazy a výpočty:
SELECT qty * unit_price AS line_total FROM order_items; - Agregační funkce:
SELECT COUNT(*) AS orders_cnt FROM orders;(vyžadujíGROUP BY, pokud vybíráte i neagregované sloupce). - Odstranění duplicit:
SELECT DISTINCT city FROM customers;
WHERE: filtrování řádků a práce s NULL
- Porovnání a logické operátory:
=, <>, <, <=, >, >=, AND, OR, NOT - Rozsahy a množiny:
BETWEEN,IN (...),LIKE(pozor na%a_),ILIKE(PostgreSQL) pro case-insensitive. - Datumy:
WHERE order_date >= DATE '2025-01-01'; vyhýbejte se funkci na sloupci v podmínce (např.WHERE DATE(order_date)=...) kvůli indexům. - NULL není hodnota: porovnává se pomocí
IS NULL/IS NOT NULL; výrazcol = NULLvrací neznámou (NULL) a neprojde filtrem. - Tříhodnotová logika: výsledek podmínky může být TRUE, FALSE nebo UNKNOWN (NULL).
WHEREpropustí pouze TRUE.
JOIN: spojování tabulek a volba správného typu
INNER JOIN vrací průnik, LEFT JOIN všechny řádky z levé tabulky (pravé chybějící doplní NULL), RIGHT JOIN analogicky, FULL OUTER JOIN sjednocení, CROSS JOIN kartézský součin.
| Typ JOIN | Popis | Typický příklad |
|---|---|---|
| INNER JOIN | Pouze páry s odpovídající shodou | Objednávky s existujícím zákazníkem |
| LEFT JOIN | Vše z levé + shody z pravé, jinak NULL | Zákazníci i bez objednávek |
| RIGHT JOIN | Vše z pravé + shody z levé | Méně běžné; použijte raději LEFT a prohoďte pořadí |
| FULL OUTER JOIN | Všechny řádky z obou stran | Sladění dvou seznamů bez ztráty nesouladů |
| CROSS JOIN | Kartézský součin (bez podmínky) | Generování kombinací (např. kalendář × kategorie) |
JOIN: ON vs. USING, kvalifikace sloupců a duplikace řádků
- ON umožňuje libovolnou podmínku:
... JOIN orders o ON o.customer_id = c.customer_id AND o.status = 'PAID'. - USING (sloupec) (ANSI SQL) zkracuje zápis při stejném jménu sloupce na obou stranách:
... JOIN orders USING (customer_id)a sloupec se ve výsledku objeví jednou. - Kvalifikace: vždy kvalifikujte sloupce aliasem tabulky v dotazech s JOINy (zamezí kolizím jmen a chybám).
- Násobení řádků: pokud vazba není 1:N/1:1, může JOIN kartézsky násobit řádky (např. více položek objednávky). Pro agregované výsledky používejte
GROUP BYneboDISTINCTs rozmyslem.
Příklady: SELECT + WHERE
- Zákazníci z konkrétního města registrovaní po datu
SELECT customer_id, name, city
FROM customers
WHERE city = 'Brno' AND created_at >= DATE '2025-01-01'; - Případy s NULL
SELECT order_id FROM orders WHERE status IS NULL; - Textové vyhledávání
SELECT product_id, product_name FROM products WHERE product_name LIKE '%kabel%';
Příklady: JOIN a filtrování
- Seznam zaplacených objednávek s jménem zákazníka
SELECT o.order_id, c.name, o.total_amount
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
WHERE o.status = 'PAID'; - Zákazníci bez jediné objednávky
SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL; - Objednávky s rozpisem položek a kategorií
SELECT o.order_id, p.category, oi.qty, oi.unit_price, (oi.qty*oi.unit_price) AS line_total
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id
WHERE o.order_date >= DATE '2025-09-01';
Agregace a GROUP BY ve spojení s JOIN
- Tržby podle kategorie
SELECT p.category, SUM(oi.qty*oi.unit_price) AS revenue
FROM order_items oi
JOIN products p ON p.product_id = oi.product_id
GROUP BY p.category
HAVING SUM(oi.qty*oi.unit_price) > 10000; - Počet objednávek na zákazníka za posledních 30 dní
SELECT c.customer_id, c.name, COUNT(o.order_id) AS orders_30d
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
AND o.order_date >= CURRENT_DATE - INTERVAL '30' DAY
GROUP BY c.customer_id, c.name;
Výkon: indexy, sargabilita a filtry
- Sargabilita: pište podmínky tak, aby šly využít indexy (vyhněte se funkcím na sloupcích v
WHEREaJOIN). Např. místoWHERE DATE(order_date)=...použijte interval:WHERE order_date >= '2025-10-01' AND order_date < '2025-11-01'. - Indexy na cizích klíčích:
orders.customer_id,order_items.order_id,order_items.product_id– zrychlují JOIN i referenční integritu. - Kombinované indexy: slaďte s typickými filtry a řazením (např.
(status, order_date)). Pořadí sloupců v indexu záleží na selektivitě. - SELECT pouze potřebné sloupce: zmenší se I/O; u analytických dotazů může pomoci sloupcové uložení (data warehouse).
- EXPLAIN/EXPLAIN ANALYZE: používejte k porozumění plánům (nested loop/hash join/merge join, cost, kardinalita).
Časté chyby a jak se jim vyhnout
- Nekvalifikované sloupce v JOIN: kolize jmen (
idv obou tabulkách) → používejte aliasy (c.id,o.id). - Filtr ve špatné části dotazu: podmínka v
WHEREpoLEFT JOINmůže „zrušit“ levé řádky. Filtry týkající se pravé tabulky dejte doON, pokud chcete zachovat nulové shody. - Porovnání s NULL: používejte
IS NULL/IS NOT NULL, nikoli= NULL. - Nechtěné násobení řádků: chybí jednoznačná vazba (např. vícenásobné shody) → zvažte deduplikaci (
ROW_NUMBER()) nebo agregaci před JOINem. - LIKE s předponou %: výraz
%textnevyužije běžný index; zvažte fulltext nebo trigramy.
Dialekty SQL: rozdíly, na které dát pozor
- Limity:
LIMIT/OFFSET(PostgreSQL, MySQL) vs.FETCH FIRST ... ROWS ONLY(ANSI/Oracle/DB2) vs.TOP(SQL Server). - Case-insensitive LIKE:
ILIKEje v PostgreSQL; v jiných dialektech použijteLOWER()a kolace. - DATUM/INTERVAL: syntaxe intervalů a funkcí se liší; preferujte standardní literály (
DATE 'YYYY-MM-DD'). - JOIN USING: ne všechny dialekty jej implementují shodně (SQL Server preferuje pouze
ON).
Okna (Window Functions) jako doplněk SELECT
I když nejsou součástí názvu tohoto článku, okna často doplňují SELECT bez nutnosti GROUP BY:
- Běžící součet:
SUM(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date) - Řazení v rámci skupiny:
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC)
Mikro-vzor: „bezobjednávkoví“ zákazníci (správné použití LEFT JOIN)
Cíl: vrátit zákazníky bez objednávek v období. Správné řešení filtruje období v ON, aby zachovalo i ty, kteří nemají shodu:
SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
AND o.order_date >= DATE '2025-10-01' AND o.order_date < DATE '2025-11-01'
WHERE o.order_id IS NULL;
Kontrolní checklist pro SELECT–WHERE–JOIN
- Jsou vybrány pouze potřebné sloupce a pojmenovány srozumitelnými aliasy?
- Je
WHEREsargabilní a využije dostupné indexy? - Jsou všechny sloupce v
JOINkvalifikované aliasy a vazby jednoznačné? - Je filtr na pravé tabulce u
LEFT JOINsprávně umístěn doON, pokud nechcete ztratit nulové shody? - Je ošetřena logika s
NULLa porovnání používajíIS NULL/IS NOT NULL? - Byl zkontrolován plán (
EXPLAIN) u náročných dotazů a doplněny indexy?
„Tahák“: syntaktické šablony
| Úloha | Šablona |
|---|---|
| Výběr s filtrem | SELECT col1, col2 FROM t WHERE cond AND col3 BETWEEN a AND b; |
| INNER JOIN | SELECT ... FROM a JOIN b ON a.key = b.key WHERE ...; |
| LEFT JOIN (zachovat levé) | SELECT ... FROM a LEFT JOIN b ON a.key=b.key AND b.cond WHERE b.key IS NULL; |
| Agregace | SELECT k, SUM(x) FROM t WHERE ... GROUP BY k HAVING SUM(x) > n; |
| Limit + řazení | SELECT ... FROM t WHERE ... ORDER BY col DESC FETCH FIRST 20 ROWS ONLY; |
Závěr
Stavebnice SELECT–WHERE–JOIN představuje jádro dotazování v SQL. Porozumění logickému pořadí vyhodnocování, práci s NULL, správnému umístění filtrů a volbě vhodného typu JOIN výrazně snižuje chybovost a zlepšuje výkon. Pečlivé indexování, kvalifikace sloupců a kontrola plánů dotazů jsou praktiky, které posunou vaše dotazy od „funguje“ k „funguje rychle a správně“.