V našem posledním článku o kurzorech v PostgreSQL jsme mluvili o ommon schopných xpressions (CTE). Dnes pokračujeme v objevování nových alternativ ke kurzorům pomocí méně známé funkce PostgreSQL.
Použijeme data, která jsme importovali v předchozím článku (odkaz výše). Chvíli počkám, až budete postupovat podle tamního postupu.
Mám to? Dobře.
Data jsou taxonomickou tabulkou přírodního světa. Jako připomenutí ze základní středoškolské biologie, tato data jsou organizována Carlem Linné do království, kmene, třídy, řádu, čeledi, rodu a druhu. Samozřejmě se věda za posledních 250 let posunula jen nepatrně dopředu, takže taxonomická tabulka je hluboká 21 úrovní. Strom hierarchie najdeme v tabulce, která se (nepřekvapivě) jmenuje itis.hierarchy
.
Tématem tohoto článku je, jak používat ltrees v PostgreSQL. Konkrétně, jak je používat pro velmi efektivní procházení složité sady záznamů. V tomto smyslu je můžeme považovat za další náhradu za kurzory.
Data nejsou spravována (bohužel pro nás) ve formátu ltree, takže je pro účely článku trochu transformujeme.
Nejprve budete muset nainstalovat ltree do databáze, kterou používáte, abyste mohli postupovat podle tohoto článku. K instalaci rozšíření samozřejmě musíte být super uživatel.
CREATE EXTENSION IF NOT EXISTS ltree;
Nyní použijeme toto rozšíření k poskytování velmi efektivních vyhledávání. Budeme muset transformovat data do vyhledávací tabulky. K provedení této transformace použijeme techniku CTE, kterou jsme probrali v minulém článku. Po cestě přidáme latinské názvy a anglické názvy do stromu taxonomie. To nám pomůže vyhledávat položky podle čísla, latinských názvů nebo anglických názvů.
-- We need a little helper function to strip out illegal label names. CREATE OR REPLACE FUNCTION strip_label(thelabel text) RETURNS TEXT AS $$ -- make sure all the characters in the label are legal SELECT SELECT regexp_replace( regexp_replace( regexp_replace( regexp_replace( -- strip anything not alnum (yes, this could be way more accurate) thelabel, '[^[:alnum:]]', '_','g'), -- consolidate underscores '_+', '_', 'g'), -- strip leading/trailing underscores '^_*', '', 'g'), '_*$', '', 'g'); $$ LANGUAGE sql; CREATE MATERIALIZED VIEW itis.world_view AS WITH RECURSIVE world AS ( -- Start with the basic kingdoms SELECT h1.tsn, h1.parent_tsn, h1.tsn::text numeric_taxonomy, -- There is no guarantee that there will be a textual name COALESCE(l1.completename,h1.tsn::text,'')::text latin_taxonomy, -- and again no guarantee of a common english name COALESCE(v1.vernacular_name, lower(l1.completename),h1.tsn::text,'unk')::text english_taxonomy FROM itis.hierarchy h1 LEFT JOIN itis.longnames l1 ON h1.tsn = l1.tsn LEFT JOIN itis.vernaculars v1 ON (h1.tsn, 'English') = (v1.tsn, v1.language) WHERE h1.parent_tsn = 0 UNION ALL SELECT h1.tsn, h1.parent_tsn, w1.numeric_taxonomy || '.' || h1.tsn, w1.latin_taxonomy || '.' || COALESCE(strip_label(l1.completename), h1.tsn::text,'unk'), w1.english_taxonomy || '.' || strip_label(COALESCE(v1.vernacular_name, lower(l1.completename), h1.tsn::text, 'unk')) FROM itis.hierarchy h1 JOIN world w1 ON h1.parent_tsn = w1.tsn LEFT JOIN itis.longnames l1 ON h1.tsn = l1.tsn LEFT JOIN -- just change this to "itis.vernaculars v1" to allow mulitples and all languages. (Millions of records.) (SELECT tsn, min(vernacular_name) vernacular_name FROM itis.vernaculars WHERE language = 'English' GROUP BY tsn) v1 ON (h1.tsn) = (v1.tsn) ) SELECT w2.tsn, w2.parent_tsn, w2.numeric_taxonomy::ltree, w2.latin_taxonomy::ltree latin_taxonomy, w2.english_taxonomy::ltree english_taxonomy FROM world w2 ORDER BY w2.numeric_taxonomy WITH NO DATA;
Zastavme se na chvíli a přivoněme ke květinám v tomto dotazu. Pro začátek jsme jej vytvořili bez naplnění jakýchkoli dat. To nám dává šanci postarat se o jakékoli syntaktické problémy, než vygenerujeme spoustu zbytečných dat. Využíváme iterativní povahu běžného tabulkového výrazu, abychom zde sestavili pěkně hlubokou strukturu a mohli bychom ji snadno rozšířit tak, aby pokryla více jazyků přidáním dat do tabulky lidových jazyků. Zhmotněný pohled má také některé zajímavé výkonnostní charakteristiky. Zkrátí a znovu sestaví tabulku pokaždé, když dojde k REFRESH MATERIALIZED VIEW
se nazývá.
Co uděláme dále, je obnovit náš pohled na svět. Většinou proto, že je zdravé to čas od času dělat. Ale v tomto případě to, co ve skutečnosti dělá, je naplnění materializovaného pohledu daty z itis
schéma.
REFRESH MATERIALIZED VIEW itis.world_view;
Vytvoření více než 600 tisíc řádků z dat zabere několik minut.
Prvních několik řádků bude vypadat takto:
┌────────────┬─────────┬───────────────────────────────────────────────────────────────────────────────┐
│ parent_tsn │ tsn │ english_taxonomy │
├────────────┼─────────┼───────────────────────────────────────────────────────────────────────────────┤
│ 768374 │ 1009037 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.gastrosericus.gastrosericus…│
│ │ │…_xanthophilus │
│ 768374 │ 1009038 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.gastrosericus.gastrosericus…│
│ │ │…_zoyphion │
│ 768374 │ 1009039 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.gastrosericus.gastrosericus…│
│ │ │…_zyx │
│ 768216 │ 768387 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.holotachysphex │
│ 768387 │ 1009040 │ animals.bilateria.protostomia.ecdysozoa.arthropods.hexapods.insects.winged_in…│
│ │ │…sects.modern_wing_folding_insects.holometabola.ants.ants.aculeata.apoid_wasps…│
│ │ │….cicadakillers.crabroninae.larrini.gastrosericina.holotachysphex.holotachysph…│
│ │ │…ex_holognathus │
└────────────┴─────────┴───────────────────────────────────────────────────────────────────────────────┘
V taxonomii by graf vypadal asi takto:
Samozřejmě, ve skutečnosti by to bylo 21 úrovní a celkem 600 tisíc záznamů.
Nyní se dostáváme k zábavnější části! Stromy poskytují způsob, jak provádět některé velmi složité dotazy v hierarchii. Nápověda k tomu je v dokumentaci PostgreSQL, takže se zde nebudeme moc podrobně zabývat. Pro (velmi rychlé) pochopení se každý segment lstromu nazývá štítek. Takže tento ltree kingdom.phylum.class.order.family.genus.species
má 7 štítků.
Dotazy na ltree používají speciální zápis, který je jako regulární výrazy v omezené formě.
Zde je jednoduchý příklad:Animalia.*.Homo_sapiens
Takže dotaz na nalezení lidstva ve světě by vypadal takto:
SELECT tsn, parent_tsn, latin_taxonomy, english_taxonomy
FROM itis.world_view WHERE latin_taxonomy ~ 'Animalia.*.Homo_sapiens';
Což má za následek očekávané:
┌────────┬────────────┬────────────────────────────────────────────────┬─────────────────────────────────────────────┐
│ tsn │ parent_tsn │ latin_taxonomy │ english_taxonomy │
├────────┼────────────┼────────────────────────────────────────────────┼─────────────────────────────────────────────┤
│ 180092 │ 180091 │ Animalia.Bilateria.Deuterostomia.Chordata.Vert…│ animals.bilateria.deuterostomia.chordates.v…│
│ │ │…ebrata.Gnathostomata.Tetrapoda.Mammalia.Theria…│…ertebrates.gnathostomata.tetrapoda.mammals.…│
│ │ │….Eutheria.Primates.Haplorrhini.Simiiformes.Hom…│…theria.eutheria.primates.haplorrhini.simiif…│
│ │ │…inoidea.Hominidae.Homininae.Homo.Homo_sapiens │…ormes.hominoidea.Great_Apes.African_apes.ho…│
│ │ │ │…minoids.Human │
└────────┴────────────┴────────────────────────────────────────────────┴─────────────────────────────────────────────┘
PostgreSQL by to tak samozřejmě nikdy nenechal. Existuje rozsáhlá sada operátorů, indexů, transformací a příkladů.
Podívejte se na širokou škálu možností, které tato technika odemyká.
Nyní si představte, že by tato technika byla aplikována na další komplexní datové typy, jako jsou čísla dílů, identifikační čísla vozidel, struktury kusovníků nebo jakýkoli jiný klasifikační systém. Není nutné vystavovat tuto strukturu koncovému uživateli kvůli neúměrně složité křivce učení, aby ji mohl přímo používat. Je však zcela možné vytvořit „vyhledávací“ obrazovku založenou na struktuře, jako je tato, která je velmi výkonná a skrývá složitost implementace.
V našem dalším článku ze série prozkoumáme použití zásuvných jazyků. V kontextu hledání alternativ ke kurzorům v PostgreSQL použijeme jazyk dle našeho výběru k modelování dat způsobem, který je pro naše potřeby nejvhodnější. Uvidíme se příště!