Logo Light
Published on

Self Join

Když potřebuju porovnat řádky uvnitř jedné tabulky, spojím tabulku samu se sebou. Jedná se o standardní join, ale na obou stranách spojení mám tu samou tabulku. Abych byl schopný rozlišit, která je která, přiřadím jí na obou stranách alias:

SELECT t1.data, t2.data
FROM tabulka t1
JOIN tabulka t2
ON t1.sloupec = t2.sloupec;
  • t1 a t2 jsou aliasy naší tabulky

Kdy použít Self Join

1. Hierarchické struktury

vypiš všechny zaměstnance a jejich šéfy

SELECT podrizeny.jmeno, nadrizeny.jmeno
FROM zamestnanci podrizeny
LEFT JOIN zamestnanci nadrizeny
ON podrizeny.sef_id = nadrizeny.id;

(Pozn. použil jsem LEFT JOIN, aby ve výpisu byli i zaměstnanci, kteří nemají šéfa)

rodokmeny (dítě-rodič)

SELECT dite.jmeno, rodic.jmeno
FROM clenove_rodiny AS dite
LEFT JOIN clenove_rodiny AS rodic
ON dite.rodic_id = rodic.id;

2. Hledání podobných dat nebo duplicit

zákazníci se stejným emailem

SELECT a.jmeno, a.email
FROM zakaznici a
JOIN zakaznici b
ON a.email = b.email
WHERE a.id <> b.id;      -- vyradim radky, kde se mi spojil zakaznik zam se sebou

duplicitní objednávky

Pro tenhle složitější příklad uvedu i strukturu tabulky.

idcislo_objednavkyzakaznik_iddatum_objednavkycelkova_castka
1OBJ0011012024-02-011500.00
2OBJ0021022024-02-022000.00
3OBJ0031012024-02-011500.00
4OBJ0041032024-02-052500.00
5OBJ0051012024-02-011500.00
6OBJ0061022024-02-022000.00
Sql pro vytvoření tabulky
CREATE TABLE objednavky (
    id INT AUTO_INCREMENT PRIMARY KEY,
    cislo_objednavky VARCHAR(20) NOT NULL,
    zakaznik_id INT NOT NULL,
    datum_objednavky DATE NOT NULL,
    celkova_castka DECIMAL(10,2) NOT NULL
);
Sql pro naplnění daty
INSERT INTO objednavky (cislo_objednavky, zakaznik_id, datum_objednavky, celkova_castka) VALUES
('OBJ001', 101, '2024-02-01', 1500.00),
('OBJ002', 102, '2024-02-02', 2000.00),
('OBJ003', 101, '2024-02-01', 1500.00), -- Duplicitní OBJ001
('OBJ004', 103, '2024-02-05', 2500.00),
('OBJ005', 101, '2024-02-01', 1500.00), -- Duplicitní OBJ001 & OBJ003
('OBJ006', 102, '2024-02-02', 2000.00); -- Duplicitní OBJ002
Dotaz pro nalezení duplicitních objednávek
SELECT
o1.id AS objednavka_id_1,
o1.cislo_objednavky AS cislo_objednavky_1,
o2.id AS objednavka_id_2,
o2.cislo_objednavky AS cislo_objednavky_2,
o1.zakaznik_id,
o1.datum_objednavky,
o1.celkova_castka
FROM objednavky o1
JOIN objednavky o2
ON o1.zakaznik_id = o2.zakaznik_id  -- Stejný zákazník
AND o1.datum_objednavky = o2.datum_objednavky  -- Stejný datum objednávky
AND o1.celkova_castka = o2.celkova_castka  -- Stejná částka
AND o1.id > o2.id;  -- Zabrání zobrazení stejných párů dvakrát
  • JOIN objednavky o2 ON ... - Propojíme tabulku sama se sebou (o1 a o2 reprezentují různé řádky).
  • o1.zakaznik_id = o2.zakaznik_id - Objednávky musí patřit stejnému zákazníkovi.
  • o1.datum_objednavky = o2.datum_objednavky - Objednávky musí být vytvořeny ve stejný den.
  • o1.celkova_castka = o2.celkova_castka - Objednávky musí mít stejnou částku.
  • o1.id > o2.id - Každý duplicitní pár se zobrazí jen jednou (zabrání zobrazení OBJ001-OBJ003 i OBJ003-OBJ001).
Výstup dotazu
objednavka_id_1cislo_objednavky_1objednavka_id_2cislo_objednavky_2zakaznik_iddatum_objednavkycelkova_castka
3OBJ0031OBJ0011012024-02-011500.00
5OBJ0051OBJ0011012024-02-011500.00
5OBJ0053OBJ0031012024-02-011500.00
6OBJ0062OBJ0021022024-02-022000.00
  • OBJ003 je duplicitní objednávka k OBJ001 (stejný zákazník, datum a částka).
  • OBJ005 je duplicitní k OBJ001 a OBJ003, což tvoří skupinu tří duplicitních objednávek.
  • OBJ006 je duplicitní k OBJ002.
  • podobné názvy knih v knihovně
SELECT k1.id AS id_kniha_1, k1.nazev AS nazev_kniha_1,
       k2.id AS id_kniha_2, k2.nazev AS nazev_kniha_2
FROM knihy k1
JOIN knihy k2 ON k1.id_kniha != k2.id_kniha
WHERE k1.nazev LIKE CONCAT('%', SUBSTRING(k2.nazev, 1, 5), '%')
ORDER BY k1.id_kniha, k2.id_kniha;
  • Podmínka b1.id != b2.id zajišťuje, že neporovnáváme knihu samu se sebou
  • Podmínka LIKE ověřuje, jestli prvních 5 písmen názvu knihy se vyskytuje kdekoliv v názvu jiné knihy

3. Porovnávání dat v čase (současný vs. předchozí stav)

vývoj ceny akcií

vytvoření tabulky s cenami akcií v čase
CREATE TABLE ceny_akcii (
    id INT AUTO_INCREMENT PRIMARY KEY,
    symbol_akcie VARCHAR(10),
    cena DECIMAL(10, 2),
    datum DATE
);
naplnění tabulky daty
INSERT INTO ceny_akcii (symbol_akcie, cena, datum) VALUES
('AAPL', 145.00, '2025-02-18'),
('AAPL', 140.00, '2025-02-11'),
('TSLA', 700.00, '2025-02-18'),
('TSLA', 680.00, '2025-02-11'),
('GOOG', 2800.00, '2025-02-18'),
('GOOG', 2750.00, '2025-02-11');
dotaz na změcu cen akcií za poslední týden
SELECT
    dnes.symbol_akcie,
    dnes.cena AS aktualni_cena,
    minuly_tyden.cena AS cena_minuly_tyden,
    (dnes.cena - minuly_tyden.cena) AS zmena_ceny
FROM
    ceny_akcii dnes
JOIN
    ceny_akcii minuly_tyden
    ON dnes.symbol_akcie = minuly_tyden.symbol_akcie
    AND dnes.datum = CURDATE()
    AND minuly_tyden.datum = CURDATE() - INTERVAL 7 DAY;
    ```