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

Oracle a PostgreSQL:COMENZAR CON/CONECTAR POR

Y ahora llegamos al segundo artículo de nuestra serie sobre migración de Oracle a PostgreSQL. Esta vez vamos a echar un vistazo a START WITH/CONNECT BY construir.

En Oracle, START WITH/CONNECT BY se utiliza para crear una estructura de lista enlazada individualmente que comienza en una fila de centinela dada. La lista enlazada puede tomar la forma de un árbol y no tiene ningún requisito de equilibrio.

Para ilustrar, comencemos con una consulta y supongamos que la tabla tiene 5 filas.

SELECT * FROM person;
 last_name  | first_name | id | parent_id
------------+------------+----+-----------
 Dunstan    | Andrew     |  1 |    (null)
 Roybal     | Kirk       |  2 |         1
 Riggs      | Simon      |  3 |         1
 Eisentraut | Peter      |  4 |         1
 Thomas     | Shaun      |  5 |         3
(5 rows)

Aquí está la consulta jerárquica de la tabla usando la sintaxis de Oracle.

select id, parent_id
from person
start with parent_id IS NULL
connect by prior id = parent_id;
 id | parent_id
----+-----------
  1 |    (null)
  4 |         1
  3 |         1
  2 |         1
  5 |         3

Y aquí está de nuevo usando PostgreSQL.

WITH RECURSIVE a AS (
SELECT id, parent_id
FROM person
WHERE parent_id IS NULL
UNION ALL
SELECT d.id, d.parent_id
FROM person d
JOIN a ON a.id = d.parent_id )
SELECT id, parent_id FROM a;
 id | parent_id
----+-----------
  1 |    (null)
  4 |         1
  3 |         1
  2 |         1
  5 |         3
(5 rows)

Esta consulta hace uso de muchas características de PostgreSQL, así que analicémosla lentamente.

WITH RECURSIVE

Esta es una "expresión de tabla común" (CTE). Define un conjunto de consultas que se ejecutarán en la misma declaración, no solo en la misma transacción. Puede tener cualquier cantidad de expresiones entre paréntesis y una declaración final. Para este uso, solo necesitamos uno. Al declarar que esa declaración es RECURSIVE , se ejecutará iterativamente hasta que no se devuelvan más filas.

SELECT
UNION ALL
SELECT

Esta es una frase prescrita para una consulta recursiva. Se define en la documentación como el método para distinguir el punto de partida y el algoritmo de recursión. En términos de Oracle, puede considerarlos como la cláusula START WITH unida a la cláusula CONNECT BY.

JOIN a ON a.id = d.parent_id

Esta es una autounión a la instrucción CTE que proporciona los datos de la fila anterior a la iteración posterior.

Para ilustrar cómo funciona esto, agreguemos un indicador de iteración a la consulta.

WITH RECURSIVE a AS (
SELECT id, parent_id, 1::integer recursion_level
FROM person
WHERE parent_id IS NULL
UNION ALL
SELECT d.id, d.parent_id, a.recursion_level +1
FROM person d
JOIN a ON a.id = d.parent_id )
SELECT * FROM a;

 id | parent_id | recursion_level
----+-----------+-----------------
  1 |    (null) |               1
  4 |         1 |               2
  3 |         1 |               2
  2 |         1 |               2
  5 |         3 |               3
(5 rows)

Inicializamos el indicador de nivel de recursión con un valor. Tenga en cuenta que en las filas que se devuelven, el primer nivel de recurrencia solo ocurre una vez. Eso es porque la primera cláusula solo se ejecuta una vez.

La segunda cláusula es donde ocurre la magia iterativa. Aquí, tenemos visibilidad de los datos de la fila anterior, junto con los datos de la fila actual. Eso nos permite realizar los cálculos recursivos.

Simon Riggs tiene un video muy bueno sobre cómo usar esta función para el diseño de bases de datos de gráficos. Es muy informativo y deberías echarle un vistazo.

Es posible que haya notado que esta consulta podría conducir a una condición circular. Eso es correcto. Depende del desarrollador agregar una cláusula limitante a la segunda consulta para evitar esta repetición interminable. Por ejemplo, recurriendo solo a 4 niveles de profundidad antes de darse por vencido.

WITH RECURSIVE a AS (
SELECT id, parent_id, 1::integer recursion_level  --<-- initialize it here
FROM person
WHERE parent_id IS NULL
UNION ALL
SELECT d.id, d.parent_id, a.recursion_level +1    --<-- iteration increment
FROM person d
JOIN a ON a.id = d.parent_id
WHERE d.recursion_level <= 4  --<-- bail out here
) SELECT * FROM a;

Los nombres de las columnas y los tipos de datos están determinados por la primera cláusula. Observe que el ejemplo usa un operador de conversión para el nivel de recurrencia. En un gráfico muy profundo, este tipo de datos también podría definirse como 1::bigint recursion_level .

Este gráfico es muy fácil de visualizar con un pequeño script de shell y la utilidad graphviz.

#!/bin/bash -
#===============================================================================
#
#          FILE: pggraph
#
#         USAGE: ./pggraph
#
#   DESCRIPTION:
#
#       OPTIONS: ---
#  REQUIREMENTS: ---
#          BUGS: ---
#         NOTES: ---
#        AUTHOR: Kirk Roybal (), [email protected]
#  ORGANIZATION:
#       CREATED: 04/21/2020 14:09
#      REVISION:  ---
#===============================================================================

set -o nounset                              # Treat unset variables as an error

dbhost=localhost
dbport=5432
dbuser=$USER
dbname=$USER
ScriptVersion="1.0"
output=$(basename $0).dot

#===  FUNCTION  ================================================================
#         NAME:  usage
#  DESCRIPTION:  Display usage information.
#===============================================================================
function usage ()
{
cat <<- EOT

  Usage :  ${0##/*/} [options] [--]

  Options:
  -h|host     name Database Host Name default:localhost
  -n|name     name Database Name      default:$USER
  -o|output   file Output file        default:$output.dot
  -p|port   number TCP/IP port        default:5432
  -u|user     name User name          default:$USER
  -v|version    Display script version

EOT
}    # ----------  end of function usage  ----------

#-----------------------------------------------------------------------
#  Handle command line arguments
#-----------------------------------------------------------------------

while getopts ":dh:n:o:p:u:v" opt
do
  case $opt in

    d|debug    )  set -x ;;

    h|host     )  dbhost="$OPTARG" ;;

    n|name     )  dbname="$OPTARG" ;;

    o|output   )  output="$OPTARG" ;;

    p|port     )  dbport=$OPTARG ;;

    u|user     )  dbuser=$OPTARG ;;

    v|version  )  echo "$0 -- Version $ScriptVersion"; exit 0   ;;

    \? )  echo -e "\n  Option does not exist : $OPTARG\n"
          usage; exit 1   ;;

  esac    # --- end of case ---
done
shift $(($OPTIND-1))

[[ -f "$output" ]] && rm "$output"

tee "$output" <<eof< span="">
digraph g {
    node [shape=rectangle]
    rankdir=LR
EOF

psql -h $dbhost -U $dbuser -d $dbname -p $dbport -qtAf cte.sql |
    sed -e 's/^/node/' -e 's/.*(null)|/node/' -e 's/^/\t/' -e 's/|[[:digit:]]*$//' |
    sed -e 's/|/ -> node/' | tee -a "$output"

tee -a "$output" <<eof< span="">
}
EOF

dot -Tpng "$output" > "${output/dot/png}"

[[ -f "$output" ]] && rm "$output"

open "${output/dot/png}"</eof<></eof<>

Este script requiere esta sentencia SQL en un archivo llamado cte.sql

WITH RECURSIVE a AS (
SELECT id, parent_id, 1::integer recursion_level
FROM person
WHERE parent_id IS NULL
UNION ALL
SELECT d.id, d.parent_id, a.recursion_level +1
FROM person d
JOIN a ON a.id = d.parent_id )
SELECT parent_id, id, recursion_level FROM a;

Luego lo invocas así:

chmod +x pggraph
./pggraph

Y verás el gráfico resultante.

INSERT INTO person (id, parent_id) VALUES (6,2);

Vuelva a ejecutar la utilidad y vea los cambios inmediatos en su gráfico dirigido:

Ahora, eso no fue tan difícil ahora, ¿verdad?