PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Oracle para PostgreSQL — Cursores e ltrees


Em nosso último artigo sobre cursores no PostgreSQL, falamos sobre ommonable xpressions (CTE). Hoje, continuamos descobrindo novas alternativas aos cursores usando um recurso menos conhecido do PostgreSQL.

Usaremos os dados que importamos no artigo anterior (link acima). Vou esperar um momento para você seguir o procedimento lá.

Percebido? OK.

Os dados são um gráfico de taxonomia do mundo natural. Como um lembrete da biologia básica do ensino médio, esses dados são organizados por Carl Linnaeus em Reino, Filo, Classe, Ordem, Família, Gênero e Espécie. É claro que a ciência avançou ligeiramente nos últimos 250 anos, então o gráfico taxonômico tem 21 níveis de profundidade. Encontramos a árvore de hierarquia em uma tabela que é (sem surpresa) chamada itis.hierarchy .

O tópico deste artigo é como usar ltrees no PostgreSQL. Especificamente, como usá-los para percorrer um conjunto de registros complexo com muita eficiência. Nesse sentido, podemos considerá-los mais um substituto para cursores.

Os dados não são curados (infelizmente para nós) em um formato ltree, então vamos transformá-los um pouco por causa do artigo.

Primeiro, você terá que instalar o ltree no banco de dados que está usando para seguir este artigo. Claro, você precisa ser um superusuário para instalar extensões.
CREATE EXTENSION IF NOT EXISTS ltree;

Agora vamos usar esta extensão para fornecer algumas pesquisas muito eficientes. Vamos precisar transformar os dados em uma tabela de pesquisa. Para realizar essa transformação, usaremos a técnica CTE que abordamos no último artigo. Ao longo do caminho, adicionaremos os nomes latinos e os nomes ingleses à árvore de taxonomia. Isso nos ajudará a procurar itens por número, nomes latinos ou nomes ingleses.


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

Vamos parar por um momento e cheirar as flores nesta consulta. Para começar, nós o criamos sem preencher nenhum dado. Isso nos dá a chance de cuidar de quaisquer problemas sintáticos antes de gerar muitos dados inúteis. Estamos usando a natureza iterativa da expressão de tabela comum para montar uma estrutura bastante profunda aqui, e poderíamos estendê-la facilmente para cobrir mais idiomas adicionando dados à tabela vernacular. A visão materializada também tem algumas características de desempenho interessantes. Ele irá truncar e reconstruir a tabela sempre que uma REFRESH MATERIALIZED VIEW é chamado.

O que vamos fazer a seguir é atualizar nossa visão de mundo. Principalmente porque é saudável fazer isso de vez em quando. Mas neste caso, o que ele realmente faz é preencher a visão materializada com dados do itis esquema.
REFRESH MATERIALIZED VIEW itis.world_view;

Isso levará alguns minutos para criar as mais de 600 mil linhas dos dados.

As primeiras linhas ficarão assim:
┌────────────┬─────────┬───────────────────────────────────────────────────────────────────────────────┐
│ 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                                                                │
└────────────┴─────────┴───────────────────────────────────────────────────────────────────────────────┘

Em uma taxonomia, o gráfico ficaria assim:

Claro, na verdade seriam 21 níveis de profundidade e mais de 600 mil registros no total.

Agora vamos para a parte divertida! As ltrees fornecem uma maneira de fazer algumas consultas muito complexas em uma hierarquia. A ajuda para isso está na documentação do PostgreSQL, então não vamos entrar em detalhes aqui. Para uma compreensão (muito rápida), cada segmento de uma ltree é chamado de rótulo. Então, esta árvore kingdom.phylum.class.order.family.genus.species tem 7 etiquetas.

Consultas em uma ltree usam uma notação especial que é como expressões regulares em uma forma limitada.

Aqui está um exemplo simples:Animalia.*.Homo_sapiens

Então, uma consulta para encontrar a humanidade no mundo seria assim:
SELECT tsn, parent_tsn, latin_taxonomy, english_taxonomy 
FROM itis.world_view WHERE latin_taxonomy ~ 'Animalia.*.Homo_sapiens';

O que resulta no esperado:
┌────────┬────────────┬────────────────────────────────────────────────┬─────────────────────────────────────────────┐
│  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                               │
└────────┴────────────┴────────────────────────────────────────────────┴─────────────────────────────────────────────┘

É claro que o PostgreSQL nunca deixaria por aqui. Há um extenso conjunto de operadores, índices, transformações e exemplos.

Vá dar uma olhada na vasta gama de recursos que essa técnica desbloqueia.

Agora imagine essa técnica aplicada a outros tipos de dados complexos, como números de peças, números de identificação de veículos, estruturas de listas de materiais ou qualquer outro sistema de classificação. Não é necessário expor essa estrutura ao usuário final devido à curva de aprendizado proibitivamente complexa para usá-la diretamente. Mas é perfeitamente possível construir uma tela de “pesquisa” baseada em uma estrutura como esta que é muito poderosa e esconde a complexidade da implementação.

Para nosso próximo artigo da série, exploraremos o uso de linguagens plug-in. No contexto de encontrar alternativas aos cursores no PostgreSQL, usaremos uma linguagem de nossa escolha para modelar os dados da forma mais adequada às nossas necessidades. Vejo você na próxima vez!