Od každého jen jednu (DISTINCT ON)
Pro/09 2
Klasický DISTINCT ALL
asi všichni znáte. V dnešním článku se podíváme na jeho variantu DISTINCT ON (…)
, která zajišťuje jedinečnost výsledku jen ve vyjmenovaných sloupcích.
Knížky a autoři
Naším cílem bude zjistit, jakou nejtlustší knížku každý autor napsal a počet jejích stránek. Chceme tedy vypsat: název knížky, počet stran a jméno autora.
Potřebná data najdeme ve dvou tabulkách: autor
a kniha
ve schématu sbirka
.
Kdybychom nepotřebovali vypsat i název každé knížky, mohli bychom výsledek zjistit celkem jednoduše pomocí GROUP BY
a agregační funkce max()
. Název knížky bychom si mohli následně připojit (JOIN
), ale bylo by to dost neefektivní řešení.
Naštěstí můžeme využít DISTINCT ON
a dotaz je pak výrazně jednodušší – a to jak na zápis, tak na provedení:
SELECT DISTINCT ON (a.id) k.nazev, k.pocet_stran, a.jmeno, a.prijmeni FROM sbirka.kniha AS k JOIN sbirka.autor AS a ON (k.autor = a.id) ORDER BY a.id, k.pocet_stran DESC;
Samotný DISTINCT
se ve výchozím stavu chová jako DISTINCT ALL
, což znamená, že nám z výsledku zbudou jen ty záznamy, které jsou ve všech sloupcích jedinečné (nebudeme mít žádné duplicity). Pokud ale za DISTINCT
uvedeme klíčové slovo ON
a výčet sloupců, zůstanou nám ve výsledku záznamy jedinečné jen v těchto sloupcích. Jelikož jsme uvedli ON (a.id)
, budeme mít ve výpisu každého autora jen jednou. Podle tohoto sloupce musíme mít i řazení. Který záznam se od každého autora vezme, záleží na řazení – vezme se ten první – proto: k.pocet_stran DESC;
. Pokud budeme chtít mít výsledek seřazený např. podle příjmení a jména autora, můžeme dotaz obalit ještě jedním dotazem a řazení provést v něm.
Více verzí článku
Dejme tomu, že máme redakční systém a verzujeme si články – tzn. uchováváme si i historické verze. Tabulka verzovaných článků by mohla vypadat nějak takhle:
CREATE TABLE verze_clanku ( clanek integer NOT NULL, -- ID článku verze integer NOT NULL, -- ID verze datum timestamp with time zone NOT NULL DEFAULT now(), nadpis character varying(255) NOT NULL, obsah text NOT NULL, CONSTRAINT verze_clanku_pk PRIMARY KEY (clanek, verze) );
Tímto dotazem získáme od každého článku jeho aktuální verzi a výsledek seřadíme od nejnovějších článků k nejstarším:
SELECT * FROM( SELECT DISTINCT ON (clanek) clanek, datum, nadpis FROM verze_clanku ORDER BY clanek, verze DESC ) AS verze ORDER BY clanek DESC;
Dalším případem, kdy se nám DISTINCT ON
bude hodit je hlasování a různé ankety. Buď umožníme každému uživateli hlasovat jen jednou, nebo ho necháme hlasovat, kolikrát chce, ale do výsledků budeme počítat jen jeho poslední hlas.
Závěr a odkazy
DISTINCT ON
je velice užitečná konstrukce pro psaní SQL dotazů, ovšem není dostupná na všech platformách. Někdy je potřeba ji suplovat kódem v aplikační vrstvě – řazení v dotazu zůstane stejné, ale při procházení výsledku v aplikaci si z každé skupiny (např. autor nebo článek) vezmeme jen jeden záznam a ostatní přeskočíme (dokud nenarazíme na záznam z následující skupiny). Tento postup je ale dost neefektivní – jednak nám zbytečně bobtná kód aplikace a jednak se mezi databází a aplikací přenášejí data, která vůbec nepotřebujeme. Další možností je, obejít problém složitějším SQL dotazem, ale ani to není moc elegantní. Jak řešíte tuto situaci vy, pokud nemůžete použít DISTINCT ON
?
Odkazy:
- ABC Linuxu – diskuse, která byla inspirací pro tento článek.
- DISTINCT – oficiální dokumentace PostgreSQL.
MySQL
Anonym7:07 odpoledne on Leden 8th, 2012
Funguje i v MySQL! Vyzkoušeno, dnes jsem to potřeboval.
pozor na distinct on
Pavel Stěhule6:06 odpoledne on Duben 1st, 2010
Je to docela uzitecna konstrukce, nicmene mam pocit, ze existuje pouze pro PostgreSQL.