PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Oracle vers PostgreSQL :COMMENCER PAR/CONNECTER PAR

Et maintenant, nous arrivons au deuxième article de notre série sur la migration d'Oracle vers PostgreSQL. Cette fois, nous allons jeter un œil au START WITH/CONNECT BY construire.

Dans Oracle, START WITH/CONNECT BY est utilisé pour créer une structure de liste chaînée à partir d'une ligne sentinelle donnée. La liste liée peut prendre la forme d'un arbre et n'a aucune exigence d'équilibrage.

Pour illustrer, commençons par une requête et supposons que la table comporte 5 lignes.

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)

Voici la requête hiérarchique de la table en utilisant la syntaxe 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

Et le voici à nouveau en utilisant 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)

Cette requête utilise de nombreuses fonctionnalités de PostgreSQL, alors allons-y lentement.

WITH RECURSIVE

Il s'agit d'une "expression de table commune" (CTE). Il définit un ensemble de requêtes qui seront exécutées dans la même instruction, pas seulement dans la même transaction. Vous pouvez avoir n'importe quel nombre d'expressions entre parenthèses et une déclaration finale. Pour cet usage, nous n'en avons besoin que d'un. En déclarant cette instruction RECURSIVE , il s'exécutera de manière itérative jusqu'à ce qu'aucune autre ligne ne soit renvoyée.

SELECT
UNION ALL
SELECT

Il s'agit d'une expression prescrite pour une requête récursive. Il est défini dans la documentation comme la méthode permettant de distinguer le point de départ et l'algorithme de récursivité. En termes Oracle, vous pouvez les considérer comme la clause START WITH unie à la clause CONNECT BY.

JOIN a ON a.id = d.parent_id

Il s'agit d'une auto-jointure à l'instruction CTE qui fournit les données de la ligne précédente à l'itération suivante.

Pour illustrer comment cela fonctionne, ajoutons un indicateur d'itération à la requête.

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)

Nous initialisons l'indicateur de niveau de récursivité avec une valeur. Notez que dans les lignes renvoyées, le premier niveau de récursivité ne se produit qu'une seule fois. C'est parce que la première clause n'est exécutée qu'une seule fois.

La deuxième clause est l'endroit où la magie itérative se produit. Ici, nous avons une visibilité des données de la ligne précédente, ainsi que des données de la ligne actuelle. Cela nous permet d'effectuer les calculs récursifs.

Simon Riggs a une très belle vidéo sur l'utilisation de cette fonctionnalité pour la conception de bases de données de graphes. C'est très instructif, et vous devriez y jeter un coup d'œil.

Vous avez peut-être remarqué que cette requête pouvait conduire à une condition circulaire. C'est exact. Il appartient au développeur d'ajouter une clause limitative à la deuxième requête pour éviter cette récursivité sans fin. Par exemple, ne récurrencez que 4 niveaux de profondeur avant d'abandonner.

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;

Les noms de colonne et les types de données sont déterminés par la première clause. Notez que l'exemple utilise un opérateur de transtypage pour le niveau de récursivité. Dans un graphique très détaillé, ce type de données peut également être défini comme 1::bigint recursion_level .

Ce graphique est très facile à visualiser avec un petit script shell et l'utilitaire 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<>

Ce script nécessite cette instruction SQL dans un fichier appelé 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;

Ensuite, vous l'invoquez comme ceci :

chmod +x pggraph
./pggraph

Et vous verrez le graphique résultant.

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

Exécutez à nouveau l'utilitaire et observez les modifications immédiates apportées à votre graphique orienté :

Maintenant, ce n'était pas si difficile maintenant, n'est-ce pas ?