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

Oracle a PostgreSQL:cursores y expresiones de tabla comunes

Duele cuando haces eso, así que no hagas eso.

En Oracle, los cursores se enseñan como parte de la programación 101. En muchos casos (si no en la mayoría), los cursores son lo primero que aprende el desarrollador de Oracle. La primera clase generalmente comienza con:"Hay 13 estructuras lógicas, la primera de las cuales es el ciclo, que es así..."

PostgreSQL, por otro lado, no depende en gran medida de los cursores. Sí, existen. Hay varios sabores de sintaxis sobre cómo usarlos. Cubriré todos los diseños principales en algún momento de esta serie de artículos. Pero la primera lección sobre los cursores de PostgreSQL es que existen bastantes (y mucho mejores) alternativas algorítmicas al uso de cursores en PostgreSQL. De hecho, en una carrera de 23 años con PostgreSQL, solo encontré la necesidad de usar los cursores dos veces. Y me arrepiento de uno de esos.

Los cursores son un hábito caro.

Iterar es mejor que hacer un bucle. “¿Cuál es la diferencia?”, te preguntarás. Bueno, la diferencia es sobre O(N) vs. O(N^2). Ok, lo diré de nuevo en inglés. La complejidad de usar cursores es que recorren conjuntos de datos usando el mismo patrón que un bucle for anidado. Cada conjunto de datos adicional eleva la complejidad del total por exponenciación. Esto se debe a que cada conjunto de datos adicional crea efectivamente otro bucle más interno. Dos conjuntos de datos son O(N^2), tres conjuntos de datos son O(N^3) y así sucesivamente. Adquirir el hábito de usar cursores cuando hay mejores algoritmos para elegir puede ser costoso.

Hacen esto sin ninguna de las optimizaciones que estarían disponibles para las funciones de nivel inferior de la propia base de datos. Es decir, no pueden usar índices de manera significativa, no pueden transformarse en subselecciones, incorporarse a uniones o usar lecturas paralelas. Tampoco se beneficiarán de ninguna optimización futura que tenga disponible la base de datos. Espero que sea un gran maestro programador que siempre obtenga el algoritmo correcto y lo codifique perfectamente la primera vez, porque acaba de vencer uno de los beneficios más importantes de una base de datos relacional. Rendimiento confiando en las mejores prácticas, o al menos en el código de otra persona.

Todo el mundo es mejor que tú. Tal vez no individualmente, pero colectivamente casi seguro. Además del argumento declarativo versus imperativo, la codificación en un lenguaje que una vez se eliminó de la biblioteca de funciones subyacente permite que todos los demás intenten hacer que su código se ejecute más rápido, mejor y de manera más eficiente sin consultarlo. Y eso es muy, muy bueno para ti.

Hagamos algunos datos para jugar.

Comenzaremos configurando algunos datos para jugar en los próximos artículos.

Contenido de cursors.bash:

set -o nounset                              # Treat unset variables as an error
# This script assumes that you have PostgreSQL running locally,
#  that you have a database with the same name as the local user,
#  and that you can create all this structure.
#  If not, then:
#   sudo -iu postgres createuser -s $USER
#   createdb

# Clean up from the last run
[[ -f itisPostgreSql.zip ]] && rm itisPostgreSql.zip
subdirs=$(ls -1 itisPostgreSql* | grep : | sed -e 's/://')
for sub in ${subdirs[@]}
do
    rm -rf $sub
done

# Get the newest file
wget https://www.itis.gov/downloads/itisPostgreSql.zip
# Unpack it
unzip itisPostgreSql.zip
# This makes a directory with the stupidest f-ing name possible
#  itisPostgreSqlDDMMYY
subdir=$(\ls -1 itisPostgreSql* | grep : | sed -e 's/://')
# The script wants to create an "ITIS" database.  Let's just make that a schema.
sed -i $subdir/ITIS.sql -e '/"ITIS"/d'  # Cut the lines about making the db
sed -i $subdir/ITIS.sql -e '/-- PostgreSQL database dump/s/.*/CREATE SCHEMA IF NOT EXISTS itis;/'
sed -i $subdir/ITIS.sql -e '/SET search_path = public, pg_catalog;/s/.*/SET search_path TO itis;/'
# ok, we have a schema to put the data in, let's do the import.
#  timeout if we can't connect, fail on error.
PG_TIMEOUT=5 psql -v "ON_ERROR_STOP=1" -f $subdir/ITIS.sql

Esto nos da un poco más de 600K registros para jugar en la tabla itis.hierarchy, que contiene una taxonomía del mundo natural. Usaremos estos datos para ilustrar varios métodos de tratar con interacciones de datos complejas.

La primera alternativa.

Mi patrón de diseño preferido para recorrer conjuntos de registros mientras se realizan operaciones costosas es la Expresión de tabla común (CTE).

Aquí hay un ejemplo de la forma básica:

WITH RECURSIVE fauna AS (
    SELECT tsn, parent_tsn, tsn::text taxonomy
    FROM itis.hierarchy
    WHERE parent_tsn = 0
    UNION ALL
    SELECT h1.tsn, h1.parent_tsn, f.taxonomy || '.' || h1.tsn
    FROM itis.hierarchy h1
    JOIN fauna f
    ON h1.parent_tsn = f.tsn
    )
SELECT *
FROM fauna
ORDER BY taxonomy;

Lo que produce los siguientes resultados:

┌─────────┬────────┬──────────────────────────────────────────────────────────┐
│   tsn   │ parent │             taxonomy                                     │
│         │ tsn    │                                                          │
├─────────┼────────┼──────────────────────────────────────────────────────────┤
│  202422 │      0 │202422                                                    │
│  846491 │ 202422 │202422.846491                                             │
│  660046 │ 846491 │202422.846491.660046                                      │
│  846497 │ 660046 │202422.846491.660046.846497                               │
│  846508 │ 846497 │202422.846491.660046.846497.846508                        │
│  846553 │ 846508 │202422.846491.660046.846497.846508.846553                 │
│  954935 │ 846553 │202422.846491.660046.846497.846508.846553.954935          │
│    5549 │ 954935 │202422.846491.660046.846497.846508.846553.954935.5549     │
│    5550 │   5549 │202422.846491.660046.846497.846508.846553.954935.5549.5550│
│  954936 │ 846553 │202422.846491.660046.846497.846508.846553.954936          │
│  954904 │ 660046 │202422.846491.660046.954904                               │
│  846509 │ 954904 │202422.846491.660046.954904.846509                        │
│   11473 │ 846509 │202422.846491.660046.954904.846509.11473                  │
│   11474 │  11473 │202422.846491.660046.954904.846509.11473.11474            │
│   11475 │  11474 │202422.846491.660046.954904.846509.11473.11474.11475      │
│   ...   │        │...snip...                                                │
└─────────┴────────┴──────────────────────────────────────────────────────────┘
(601187 rows)

Esta consulta es fácilmente modificable para realizar cualquier cálculo. Eso incluye enriquecimiento de datos, funciones complejas o cualquier otra cosa que tu corazón desee.

“¡Pero mira!”, exclamas. “Dice RECURSIVE justo ahí en el nombre! ¿No está haciendo exactamente lo que dijiste que no hiciera? Bueno, en realidad no. Debajo del capó, no usa recursión en el sentido anidado o bucle para realizar la "recursión". Es solo una lectura lineal de la tabla hasta que la consulta subordinada no devuelve ningún resultado nuevo. Y también funciona con índices.

Veamos el plan de ejecución:

┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                              QUERY PLAN                                              │
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Sort  (cost=211750.51..211840.16 rows=35858 width=40)                                                │
│   Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                                                │
│   Sort Key: fauna.taxonomy                                                                           │
│   CTE fauna                                                                                          │
│     ->  Recursive Union  (cost=1000.00..208320.69 rows=35858 width=40)                               │
│           ->  Gather  (cost=1000.00..15045.02 rows=18 width=40)                                      │
│                 Output: hierarchy.tsn, hierarchy.parent_tsn, ((hierarchy.tsn)::text)                 │
│                 Workers Planned: 2                                                                   │
│                 ->  Parallel Seq Scan on itis.hierarchy  (cost=0.00..14043.22 rows=8 width=40)       │
│                       Output: hierarchy.tsn, hierarchy.parent_tsn, (hierarchy.tsn)::text             │
│                       Filter: (hierarchy.parent_tsn = 0)                                             │
│           ->  Hash Join  (cost=5.85..19255.85 rows=3584 width=40)                                    │
│                 Output: h1.tsn, h1.parent_tsn, ((f.taxonomy || '.'::text) || (h1.tsn)::text)         │
│                 Hash Cond: (h1.parent_tsn = f.tsn)                                                   │
│                 ->  Seq Scan on itis.hierarchy h1  (cost=0.00..16923.87 rows=601187 width=8)         │
│                       Output: h1.hierarchy_string, h1.tsn, h1.parent_tsn, h1.level, h1.childrencount │
│                 ->  Hash  (cost=3.60..3.60 rows=180 width=36)                                        │
│                       Output: f.taxonomy, f.tsn                                                      │
│                       ->  WorkTable Scan on fauna f  (cost=0.00..3.60 rows=180 width=36)             │
│                             Output: f.taxonomy, f.tsn                                                │
│   ->  CTE Scan on fauna  (cost=0.00..717.16 rows=35858 width=40)                                     │
│         Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                                          │
│ JIT:                                                                                                 │
│   Functions: 13                                                                                      │
│   Options: Inlining false, Optimization false, Expressions true, Deforming true                      │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘

Avancemos y creemos un índice, y veamos cómo funciona.

CREATE UNIQUE INDEX taxonomy_parents ON itis.hierarchy (parent_tsn, tsn);

┌─────────────────────────────────────────────────────────────────────────────┐
│                             QUERY PLAN                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│Sort  (cost=135148.13..135237.77 rows=35858 width=40)                        │
│  Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                        │
│  Sort Key: fauna.taxonomy                                                   │
│  CTE fauna                                                                  │
│    ->  Recursive Union  (cost=4.56..131718.31 rows=35858 width=40)          │
│          ->  Bitmap Heap Scan on itis.hierarchy  (cost=4.56..74.69 rows=18) │
│              Output: hierarchy.tsn, hierarchy.parent_tsn, (hierarchy.tsn)   │
│                Recheck Cond: (hierarchy.parent_tsn = 0)                     │
│                ->  Bitmap Index Scan on taxonomy_parents                    │
│                                                   (cost=0.00..4.56 rows=18) │
│                      Index Cond: (hierarchy.parent_tsn = 0)                 │
│          ->  Nested Loop  (cost=0.42..13092.65 rows=3584 width=40)          │
│                Output: h1.tsn, h1.parent_tsn,((f.taxonomy || '.')||(h1.tsn))│
│                ->  WorkTable Scan on fauna f  (cost=0.00..3.60 rows=180)    │
│                      Output: f.tsn, f.parent_tsn, f.taxonomy                │
│                ->  Index Only Scan using taxonomy_parents on itis.hierarchy │
│                                   h1  (cost=0.42..72.32 rows=20 width=8)    │
│                      Output: h1.parent_tsn, h1.tsn                          │
│                      Index Cond: (h1.parent_tsn = f.tsn)                    │
│  ->  CTE Scan on fauna  (cost=0.00..717.16 rows=35858 width=40)             │
│        Output: fauna.tsn, fauna.parent_tsn, fauna.taxonomy                  │
│JIT:                                                                         │
│  Functions: 6                                                               │
└─────────────────────────────────────────────────────────────────────────────┘

Bueno, eso fue satisfactorio, ¿no? Y hubiera sido prohibitivamente difícil crear un índice en combinación con un cursor para hacer el mismo trabajo. Esta estructura nos lleva lo suficientemente lejos como para poder recorrer una estructura de árbol bastante compleja y usarla para búsquedas simples.

En la próxima entrega, hablaremos sobre otro método para obtener el mismo resultado aún más rápido. Para nuestro próximo artículo, hablaremos sobre la extensión ltree y cómo ver los datos jerárquicos de manera sorprendentemente rápida. Estén atentos.