In ons laatste artikel over cursors in PostgreSQL hadden we het over ommonable xpressions (CTE). Tegenwoordig blijven we nieuwe alternatieven voor cursors ontdekken door een minder bekende functie van PostgreSQL te gebruiken.
We gebruiken de gegevens die we in het vorige artikel hebben geïmporteerd (hierboven gelinkt). Ik wacht even tot je de procedure daar volgt.
Heb het? Oké.
De gegevens zijn een taxonomiekaart van de natuurlijke wereld. Ter herinnering aan de basisbiologie van de middelbare school:die gegevens zijn door Carl Linnaeus geordend in Kingdom, Phylum, Class, Order, Family, Genus and Species. Natuurlijk is de wetenschap de afgelopen 250 jaar een beetje vooruitgegaan, dus de taxonomische kaart is 21 niveaus diep. We vinden de hiërarchieboom in een tabel die (niet verwonderlijk) itis.hierarchy
wordt genoemd. .
Het onderwerp van dit artikel is het gebruik van ltrees in PostgreSQL. In het bijzonder hoe je ze kunt gebruiken om een complexe recordset zeer efficiënt te doorkruisen. In die zin kunnen we ze beschouwen als een ander surrogaat voor cursors.
De gegevens zijn niet gecureerd (helaas voor ons) in een ltree-formaat, dus we gaan het een beetje transformeren omwille van het artikel.
Eerst moet je ltree installeren in de database die je gebruikt om dit artikel te volgen. Natuurlijk moet je een supergebruiker zijn om extensies te installeren.
CREATE EXTENSION IF NOT EXISTS ltree;
Nu zullen we deze extensie gebruiken om een aantal zeer efficiënte zoekopdrachten te bieden. We moeten de gegevens omzetten in een opzoektabel. Om deze transformatie uit te voeren, gaan we de CTE-techniek gebruiken die we in het laatste artikel hebben behandeld. Onderweg gaan we de Latijnse namen en de Engelse namen toevoegen aan de taxonomieboom. Dit helpt ons om items op nummer, Latijnse namen of Engelse namen op te zoeken.
-- 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;
Laten we even stoppen en de bloemen ruiken in deze vraag. Om te beginnen hebben we het gemaakt zonder gegevens in te vullen. Dit geeft ons de kans om eventuele syntactische problemen op te lossen voordat we veel nutteloze gegevens genereren. We gebruiken de iteratieve aard van de algemene tabeluitdrukking om hier een behoorlijk diepe structuur samen te stellen, en we zouden deze gemakkelijk kunnen uitbreiden om meer talen te bestrijken door gegevens toe te voegen aan de taaltabel. De gematerialiseerde weergave heeft ook enkele interessante prestatiekenmerken. Het zal de tabel afkappen en opnieuw opbouwen wanneer een REFRESH MATERIALIZED VIEW
wordt gebeld.
Wat we nu gaan doen, is ons wereldbeeld opfrissen. Vooral omdat het gezond is om dat af en toe te doen. Maar in dit geval, wat het eigenlijk doet, is de gematerialiseerde weergave vullen met gegevens van de itis
schema.
REFRESH MATERIALIZED VIEW itis.world_view;
Dit duurt een paar minuten om de 600K+ rijen van de gegevens te maken.
De eerste paar rijen zien er als volgt uit:
┌────────────┬─────────┬───────────────────────────────────────────────────────────────────────────────┐
│ 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 │
└────────────┴─────────┴───────────────────────────────────────────────────────────────────────────────┘
In een taxonomie zou de grafiek er ongeveer zo uitzien:
Natuurlijk zou het eigenlijk 21 niveaus diep zijn, en 600K+ records in totaal.
Nu komen we bij het leuke gedeelte! ltrees bieden een manier om een aantal zeer complexe query's op een hiërarchie uit te voeren. De hulp daarvoor staat in de PostgreSQL-documentatie, dus we zullen er hier niet heel diep op ingaan. Voor een (zeer snel) begrip wordt elk segment van een ltree een label genoemd. Dus deze ltree kingdom.phylum.class.order.family.genus.species
heeft 7 labels.
Query's tegen een ltree gebruiken een speciale notatie die lijkt op reguliere expressies in een beperkte vorm.
Hier is een eenvoudig voorbeeld:Animalia.*.Homo_sapiens
Dus een zoekopdracht om de mensheid in de wereld te vinden zou er als volgt uitzien:
SELECT tsn, parent_tsn, latin_taxonomy, english_taxonomy
FROM itis.world_view WHERE latin_taxonomy ~ 'Animalia.*.Homo_sapiens';
Wat resulteert in het verwachte:
┌────────┬────────────┬────────────────────────────────────────────────┬─────────────────────────────────────────────┐
│ 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 │
└────────┴────────────┴────────────────────────────────────────────────┴─────────────────────────────────────────────┘
Natuurlijk zou PostgreSQL het hier nooit bij laten. Er is een uitgebreide set operators, indexen, transformaties en voorbeelden.
Ga eens kijken naar de enorme hoeveelheid mogelijkheden die deze techniek ontgrendelt.
Stelt u zich eens voor dat deze techniek wordt toegepast op andere complexe gegevenstypen, zoals onderdeelnummers, voertuigidentificatienummers, stuklijststructuren of elk ander classificatiesysteem. Het is niet nodig om deze structuur aan de eindgebruiker bloot te stellen vanwege de onbetaalbaar complexe leercurve om het direct te gebruiken. Maar het is heel goed mogelijk om een "lookup"-scherm te bouwen op basis van een structuur als deze die erg krachtig is en de complexiteit van de implementatie verbergt.
Voor ons volgende artikel in de serie zullen we het gebruik van plug-in-talen onderzoeken. In de context van het vinden van alternatieven voor cursors in PostgreSQL, zullen we een taal naar keuze gebruiken om de gegevens op de meest geschikte manier voor onze behoeften te modelleren. Tot de volgende keer!