Základy jazyka SQL pro práci s relačními databázemi

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; Minimalizujte SELECT * kvůli výkonu a stabilitě schématu.
  • Alias sloupce: SELECT name AS customer_name (zlepšuje srozumitelnost výstupu; AS je 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 bezrozlišování velikosti písmen.
  • Datumy: WHERE order_date >= DATE '2025-01-01'; vyhýbejte se funkcím na sloupci v podmínce (např. WHERE DATE(order_date) = ...) kvůli využití indexů.
  • NULL není hodnota: porovnává se pomocí IS NULL / IS NOT NULL; výraz col = NULL vrací neznámou (NULL) a neprojde filtrem.
  • Tříhodnotová logika: výsledek podmínky může být TRUE, FALSE nebo UNKNOWN (NULL). WHERE propustí 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 JOIN a prohoďte pořadí tabulek
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 názvu sloupce na obou stranách: ... JOIN orders USING (customer_id) a sloupec se ve výsledku objeví jen jednou.
  • Kvalifikace: vždy používejte alias tabulky při odkazech na sloupce v dotazech s JOINy (zabrání kolizím jmen a chybám).
  • Násobení řádků: pokud vazba není 1:N nebo 1:1, může JOIN kartézsky násobit řádky (např. více položek objednávky na jednu objednávku). Pro agregované výsledky používejte GROUP BY nebo DISTINCT s 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 bylo možné využít indexy (vyhněte se funkcím na sloupcích v WHERE a JOIN). Například místo WHERE DATE(order_date) = ... použijte rozsah: 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 udržování referenční integrity.
  • Kombinované indexy: slaďte s typickými filtry a řazením (např. (status, order_date)). Pořadí sloupců v indexu závisí na selektivitě.
  • SELECT pouze potřebné sloupce: snižuje IO; u analytických dotazů může pomoci sloupcové uložení dat (data warehouse).
  • EXPLAIN/EXPLAIN ANALYZE: používejte k pochopení plánů dotazů (nested loop, hash join, merge join, cost, kardinalita).

Časté chyby a jak se jim vyhnout

  • Nekvalifikované sloupce v JOIN: kolize jmen (id ve více tabulkách) → používejte aliasy (c.id, o.id).
  • Filtr ve špatné části dotazu: podmínka v WHERE po LEFT JOIN může „zrušit“ levé řádky. Filtry týkající se pravé tabulky umístěte do ON, 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 pomocí ROW_NUMBER() nebo agregaci před JOINem.
  • LIKE s předponou %: výraz %text nevyužije běžný index; zvažte fulltextové vyhledávání 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: ILIKE je v PostgreSQL; v jiných dialektech použijte LOWER() 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í stejně (SQL Server preferuje pouze ON).

Okna (Window Functions) jako doplněk SELECT

I když nejsou vzhledem k názvu článku hlavním tématem, okna často doplňují SELECT bez nutnosti použití 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 daném období. Správné řešení zahrnuje filtrování období v ON, aby zachovalo i zákazníky bez shod v objednávkách:

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

  1. Jsou vybrány pouze potřebné sloupce a pojmenovány srozumitelnými aliasy?
  2. Je WHERE sargabilní a využívá dostupné indexy?
  3. Jsou všechny sloupce v JOIN kvalifikovány aliasy a vazby jednoznačné?
  4. Je filtr na pravé tabulce u LEFT JOIN správně umístěn do ON, pokud nechcete ztratit nulové shody?
  5. Je ošetřena logika s NULL a používají se porovnání IS NULL/IS NOT NULL?
  6. Byl zkontrolován plán dotazu (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;