Programování

SQL JOIN prakticky: INNER, LEFT, RIGHT a FULL na příkladech

Pochopte SQL JOINy na jednoduchých tabulkách zákazníků a objednávek. INNER, LEFT, FULL OUTER, self join a nejčastější chyby s duplicitami.

12. 10. 2026 · 5 min čtení

JOIN je operace, která z relační databáze dělá relační databázi. Data máte rozdělená do více tabulek a JOIN je při dotazu zase spojí. Ukážeme si všechny běžné typy na jednom malém příkladu, který si můžete rovnou spustit v PostgreSQL, MySQL/MariaDB i SQLite.

Ukázková data

CREATE TABLE customers (
  id   INTEGER PRIMARY KEY,
  name VARCHAR(100) NOT NULL
);

CREATE TABLE orders (
  id          INTEGER PRIMARY KEY,
  customer_id INTEGER,
  total       DECIMAL(10,2) NOT NULL
);

INSERT INTO customers (id, name) VALUES
  (1, 'Alena'), (2, 'Bohdan'), (3, 'Cyril');

INSERT INTO orders (id, customer_id, total) VALUES
  (10, 1, 450.00), (11, 1, 120.00), (12, 2, 990.00), (13, NULL, 75.00);

Všimněte si dvou „okrajových“ případů: Cyril nemá žádnou objednávku a objednávka 13 nemá zákazníka. Právě na nich se typy JOINů liší.

INNER JOIN – jen shody

SELECT c.name, o.id AS order_id, o.total
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;
name order_id total
Alena 10 450.00
Alena 11 120.00
Bohdan 12 990.00

Vrátí jen řádky, které mají protějšek v obou tabulkách. Cyril ani objednávka 13 ve výsledku nejsou. Slovo INNER je nepovinné – samotné JOIN znamená totéž.

LEFT JOIN – vše z levé tabulky

SELECT c.name, o.id AS order_id, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;

Výsledek obsahuje navíc řádek Cyril | NULL | NULL. LEFT JOIN použijete, když chcete „všechny zákazníky a k nim objednávky, pokud nějaké mají“.

Hledání řádků bez protějšku

Klasický trik: LEFT JOIN a filtrování na NULL v pravé tabulce.

SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;

Vrátí zákazníky bez objednávky (Cyril). Alternativou je NOT EXISTS, který bývá stejně rychlý a často čitelnější:

SELECT c.name
FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);

Pozor na NOT IN (SELECT customer_id FROM orders) – protože sloupec obsahuje NULL, vrátí tento dotaz prázdný výsledek. Je to jedna z nejzáludnějších chyb v SQL.

RIGHT JOIN a FULL OUTER JOIN

RIGHT JOIN je zrcadlo LEFT JOINu – zachová všechny řádky z pravé tabulky. V praxi se moc nepoužívá, protože stačí prohodit pořadí tabulek a napsat LEFT JOIN.

FULL OUTER JOIN zachová řádky z obou stran:

SELECT c.name, o.id AS order_id
FROM customers c
FULL OUTER JOIN orders o ON o.customer_id = c.id;

Ve výsledku bude Cyril bez objednávky i objednávka 13 bez zákazníka. PostgreSQL a SQLite (od verze 3.39) ho podporují, MySQL a MariaDB ne. Tam ho nahradíte spojením LEFT a RIGHT JOINu přes UNION:

SELECT c.name, o.id FROM customers c LEFT JOIN orders o ON o.customer_id = c.id
UNION
SELECT c.name, o.id FROM customers c RIGHT JOIN orders o ON o.customer_id = c.id;

Agregace přes JOIN

Typický úkol: součet objednávek za zákazníka, včetně těch s nulou.

SELECT c.name,
       COUNT(o.id)              AS orders_count,
       COALESCE(SUM(o.total), 0) AS revenue
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY revenue DESC;

Dvě důležité drobnosti:

  • COUNT(o.id) počítá jen ne-NULL hodnoty, takže Cyril má 0. COUNT(*) by mu dal 1.
  • COALESCE převede NULL součet na 0.

Pozor na podmínku v ON vs. WHERE

U LEFT JOINu záleží, kam podmínku napíšete:

-- Všichni zákazníci, objednávky jen nad 200 Kč
SELECT c.name, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.total > 200;

-- Tohle z LEFT JOINu potichu udělá INNER JOIN
SELECT c.name, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.total > 200;

Ve druhém dotazu WHERE vyřadí řádky, kde je o.total NULL, a Cyril zmizí.

Self join

Tabulku můžete spojit samu se sebou, například zaměstnance s nadřízeným:

SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id;

Aliasy (e, m) jsou tady povinné, jinak databáze nerozliší, o kterou „kopii“ tabulky jde.

Nejčastější chyby

  • Násobení řádků. Když spojíte zákazníka s objednávkami a zároveň s adresami, každá objednávka se zopakuje pro každou adresu. Součty pak vycházejí špatně. Řešení: agregovat v poddotazu nebo CTE a spojovat až výsledky.
  • Chybějící podmínka ON. CROSS JOIN (kartézský součin) vznikne i omylem, třeba ve staré syntaxi FROM a, b bez WHERE. Z 10 000 × 10 000 řádků je rázem 100 milionů.
  • JOIN přes neindexovaný sloupec. Cizí klíče jako orders.customer_id by měly mít index. PostgreSQL ho pro cizí klíč automaticky nevytváří, InnoDB v MySQL ano.

Jak si ověřit, že dotaz dělá, co má

  1. Spočítejte řádky před JOINem a po něm (SELECT COUNT(*) ...). Nečekaný nárůst ukazuje na násobení.
  2. Ručně si projděte okrajové případy – záznamy bez protějšku a NULL hodnoty.
  3. U pomalých dotazů použijte EXPLAIN, viz článek Indexy a pomalé dotazy.

Pokud si nejste jistí, proč dotaz vrací víc řádků, než má, vložte schéma i dotaz do ColdHost AI v režimu Výuka – vysvětlí krok po kroku, kde se řádky násobí.