PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Da Oracle a PostgreSQL:cursori e ltree

Nel nostro ultimo articolo sui cursori in PostgreSQL, abbiamo parlato di ommonable xpressions (CTE). Oggi continuiamo a scoprire nuove alternative ai cursori utilizzando una funzionalità meno nota di PostgreSQL.

Utilizzeremo i dati che abbiamo importato nell'articolo precedente (collegato sopra). Aspetterò un momento affinché tu segua la procedura lì.

Capito? Ok.

I dati sono un grafico tassonomico del mondo naturale. Come promemoria della biologia di base delle scuole superiori, quei dati sono organizzati da Carlo Linneo in Regno, Phylum, Classe, Ordine, Famiglia, Genere e Specie. Naturalmente, negli ultimi 250 anni la scienza è leggermente progredita, quindi il grafico tassonomico è profondo 21 livelli. Troviamo l'albero della gerarchia in una tabella che (non sorprendentemente) si chiama itis.hierarchy .

L'argomento di questo articolo è come usare ltrees in PostgreSQL. In particolare, come utilizzarli per attraversare un recordset complesso in modo molto efficiente. In questo senso, possiamo considerarli un altro surrogato dei cursori.

I dati non sono curati (purtroppo per noi) in un formato ltree, quindi li trasformeremo un po' per il bene dell'articolo.

Innanzitutto, dovrai installare ltree nel database che stai utilizzando per seguire questo articolo. Ovviamente, devi essere un super utente per installare le estensioni.

CREATE EXTENSION IF NOT EXISTS ltree;

Ora useremo questa estensione per fornire alcune ricerche molto efficienti. Avremo bisogno di trasformare i dati in una tabella di ricerca. Per eseguire questa trasformazione, utilizzeremo la tecnica CTE di cui abbiamo parlato nell'ultimo articolo. Lungo la strada, aggiungeremo i nomi latini e i nomi inglesi all'albero della tassonomia. Questo ci aiuterà a cercare gli articoli per numero, nomi latini o inglesi.

-- 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;

Fermiamoci un momento e annusiamo i fiori in questa query. Per cominciare, l'abbiamo creato senza popolare alcun dato. Questo ci dà la possibilità di occuparci di eventuali problemi sintattici prima di generare molti dati inutili. Stiamo usando la natura iterativa dell'espressione della tabella comune per mettere insieme una struttura piuttosto profonda qui, e potremmo facilmente estenderla per coprire più lingue aggiungendo dati alla tabella delle lingue vernacolari. La vista materializzata ha anche alcune caratteristiche di performance interessanti. Troncherà e ricostruirà la tabella ogni volta che si verifica un REFRESH MATERIALIZED VIEW viene chiamato.

Quello che faremo dopo è aggiornare la nostra visione del mondo. Soprattutto perché è salutare farlo di tanto in tanto. Ma in questo caso, ciò che fa effettivamente è popolare la vista materializzata con i dati di itis schema.

REFRESH MATERIALIZED VIEW itis.world_view;

La creazione delle oltre 600.000 righe dai dati richiederà alcuni minuti.

Le prime righe avranno questo aspetto:

┌────────────┬─────────┬───────────────────────────────────────────────────────────────────────────────┐
│ 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 una tassonomia, il grafico sarebbe simile a questo:

Ovviamente, in realtà sarebbe 21 livelli di profondità e oltre 600.000 record in totale.

Ora arriviamo alla parte divertente! ltrees fornisce un modo per eseguire query molto complesse su una gerarchia. L'aiuto per questo è nella documentazione di PostgreSQL, quindi non lo approfondiremo molto qui. Per una comprensione (molto rapida), ogni segmento di un ltree è chiamato etichetta. Quindi, questo albero kingdom.phylum.class.order.family.genus.species ha 7 etichette.

Le query su un ltree utilizzano una notazione speciale che è come le espressioni regolari in una forma limitata.

Ecco un semplice esempio:Animalia.*.Homo_sapiens

Quindi una domanda per trovare l'umanità nel mondo sarebbe simile a questa:

SELECT tsn, parent_tsn, latin_taxonomy, english_taxonomy 
FROM itis.world_view WHERE latin_taxonomy ~ 'Animalia.*.Homo_sapiens';

Che si traduce nel previsto:

┌────────┬────────────┬────────────────────────────────────────────────┬─────────────────────────────────────────────┐
│  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                               │
└────────┴────────────┴────────────────────────────────────────────────┴─────────────────────────────────────────────┘

Naturalmente, PostgreSQL non lo lascerebbe mai così. È disponibile un'ampia serie di operatori, indici, trasformazioni ed esempi.

Dai un'occhiata alla vasta gamma di funzionalità sbloccate da questa tecnica.

Immagina ora questa tecnica applicata ad altri tipi di dati complessi come numeri di parte, numeri di identificazione del veicolo, strutture della distinta base o qualsiasi altro sistema di classificazione. Non è necessario esporre questa struttura all'utente finale a causa della curva di apprendimento proibitivamente complessa per utilizzarla direttamente. Ma è del tutto possibile costruire una schermata di "ricerca" basata su una struttura come questa che è molto potente e nasconde la complessità dell'implementazione.

Per il nostro prossimo articolo della serie, esploreremo l'uso dei linguaggi plug-in. Nel contesto della ricerca di alternative ai cursori in PostgreSQL, utilizzeremo un linguaggio a nostra scelta per modellare i dati nel modo più appropriato per le nostre esigenze. Alla prossima volta!