sql >> Base de Datos >  >> RDS >> PostgreSQL

Oracle a PostgreSQL — Cursores y ltrees

En nuestro último artículo sobre cursores en PostgreSQL, hablamos sobre expresiones comunes (CTE). Hoy, seguimos descubriendo nuevas alternativas a los cursores mediante el uso de una función menos conocida de PostgreSQL.

Usaremos los datos que importamos en el artículo anterior (enlace arriba). Esperaré un momento para que sigas el procedimiento allí.

¿Lo tengo? Está bien.

Los datos son una tabla de taxonomía del mundo natural. Como recordatorio de la biología básica de la escuela secundaria, Carl Linnaeus organiza los datos en Reino, Phylum, Clase, Orden, Familia, Género y Especie. Por supuesto, la ciencia ha avanzado muy levemente en los últimos 250 años, por lo que el cuadro taxonómico tiene 21 niveles de profundidad. Encontramos el árbol de jerarquía en una tabla que (como era de esperar) se llama itis.hierarchy .

El tema de este artículo es cómo usar ltrees en PostgreSQL. Específicamente, cómo usarlos para atravesar un conjunto de registros complejo de manera muy eficiente. En ese sentido, podemos considerarlos otro sustituto de los cursores.

Los datos no están seleccionados (desafortunadamente para nosotros) en formato ltree, por lo que vamos a transformarlos un poco por el bien del artículo.

Primero, deberá instalar ltree en la base de datos que está utilizando para seguir este artículo. Por supuesto, debe ser un superusuario para instalar extensiones.

CREATE EXTENSION IF NOT EXISTS ltree;

Ahora usaremos esta extensión para proporcionar algunas búsquedas muy eficientes. Vamos a necesitar transformar los datos en una tabla de búsqueda. Para realizar esta transformación, vamos a utilizar la técnica CTE que cubrimos en el último artículo. En el camino, agregaremos los nombres latinos y los nombres ingleses al árbol de taxonomía. Esto nos ayudará a buscar elementos por número, nombres en latín o nombres en inglés.

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

Detengámonos un momento y olemos las flores en esta consulta. Para empezar, lo creamos sin completar ningún dato. Esto nos da la oportunidad de solucionar cualquier problema sintáctico antes de generar una gran cantidad de datos inútiles. Estamos utilizando la naturaleza iterativa de la expresión de tabla común para armar una estructura bastante profunda aquí, y podríamos extenderla fácilmente para cubrir más idiomas agregando datos a la tabla de lenguas vernáculas. La vista materializada también tiene algunas características de rendimiento interesantes. Truncará y reconstruirá la tabla cada vez que REFRESH MATERIALIZED VIEW se llama.

Lo que vamos a hacer a continuación es refrescar nuestra visión del mundo. Sobre todo porque es saludable hacerlo de vez en cuando. Pero en este caso, lo que realmente hace es llenar la vista materializada con datos del itis esquema.

REFRESH MATERIALIZED VIEW itis.world_view;

Tomará unos minutos crear las más de 600 000 filas a partir de los datos.

Las primeras filas se verán así:

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

En una taxonomía, el gráfico se vería así:

Por supuesto, en realidad tendría 21 niveles de profundidad y más de 600 000 registros en total.

¡Ahora llegamos a la parte divertida! Los árboles proporcionan una forma de realizar algunas consultas muy complejas en una jerarquía. La ayuda para eso está en la documentación de PostgreSQL, por lo que no profundizaremos mucho aquí. Para una comprensión (muy rápida), cada segmento de un ltree se denomina etiqueta. Entonces, este ltree kingdom.phylum.class.order.family.genus.species tiene 7 etiquetas.

Las consultas contra un ltree usan una notación especial que es como expresiones regulares en una forma limitada.

He aquí un ejemplo simple:Animalia.*.Homo_sapiens

Entonces, una consulta para encontrar humanidad en el mundo se vería así:

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

Lo que resulta en lo 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                               │
└────────┴────────────┴────────────────────────────────────────────────┴─────────────────────────────────────────────┘

Por supuesto, PostgreSQL nunca lo dejaría así. Hay un amplio conjunto de operadores, índices, transformaciones y ejemplos.

Eche un vistazo a la amplia gama de capacidades que desbloquea esta técnica.

Ahora imagine esta técnica aplicada a otros tipos de datos complejos, como números de pieza, números de identificación de vehículos, estructuras de listas de materiales o cualquier otro sistema de clasificación. No es necesario exponer esta estructura al usuario final debido a la curva de aprendizaje prohibitivamente compleja para usarla directamente. Pero es completamente posible construir una pantalla de "búsqueda" basada en una estructura como esta que es muy poderosa y oculta la complejidad de la implementación.

Para nuestro próximo artículo de la serie, exploraremos el uso de lenguajes complementarios. En el contexto de encontrar alternativas a los cursores en PostgreSQL, utilizaremos un lenguaje de nuestra elección para modelar los datos de la forma más adecuada a nuestras necesidades. ¡Hasta la próxima!