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

Oracle para PostgreSQL:INICIAR COM/CONECTAR POR


E agora chegamos ao segundo artigo em nossa migração da série Oracle para PostgreSQL. Desta vez, veremos o START WITH/CONNECT BY construir.

No Oracle, START WITH/CONNECT BY é usado para criar uma estrutura de lista vinculada individualmente começando em uma determinada linha sentinela. A lista encadeada pode ter a forma de uma árvore e não tem nenhum requisito de balanceamento.

Para ilustrar, vamos começar com uma consulta e presumir que a tabela tenha 5 linhas.
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)

Aqui está a consulta hierárquica da tabela usando a sintaxe 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

E aqui está novamente usando o 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)

Essa consulta faz uso de muitos recursos do PostgreSQL, então vamos analisá-la lentamente.
WITH RECURSIVE

Esta é uma “Expressão de Tabela Comum” (CTE). Ele define um conjunto de consultas que serão executadas na mesma instrução, não apenas na mesma transação. Você pode ter qualquer número de expressões entre parênteses e uma declaração final. Para este uso, precisamos apenas de um. Ao declarar essa instrução como RECURSIVE , ele será executado iterativamente até que não sejam retornadas mais linhas.
SELECT
UNION ALL
SELECT

Esta é uma frase prescrita para uma consulta recursiva. Ele é definido na documentação como o método para distinguir o ponto de partida e o algoritmo de recursão. Nos termos do Oracle, você pode pensar neles como a cláusula START WITH unida à cláusula CONNECT BY.
JOIN a ON a.id = d.parent_id

Essa é uma associação automática à instrução CTE que fornece os dados da linha anterior para a iteração subsequente.

Para ilustrar como isso funciona, vamos adicionar um indicador de iteração à 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 o indicador de nível de recursão com um valor. Observe que nas linhas que são retornadas, o primeiro nível de recursão ocorre apenas uma vez. Isso porque a primeira cláusula é executada apenas uma vez.

A segunda cláusula é onde a mágica iterativa acontece. Aqui, temos visibilidade dos dados da linha anterior, juntamente com os dados da linha atual. Isso nos permite realizar os cálculos recursivos.

Simon Riggs tem um vídeo muito bom sobre como usar esse recurso para o design de banco de dados gráfico. É altamente informativo e você deve dar uma olhada.





Você deve ter notado que essa consulta pode levar a uma condição circular. Está correto. Cabe ao desenvolvedor adicionar uma cláusula de limitação à segunda consulta para evitar essa recursão sem fim. Por exemplo, apenas recuando 4 níveis de profundidade antes de desistir.
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;

Os nomes das colunas e os tipos de dados são determinados pela primeira cláusula. Observe que o exemplo usa um operador de conversão para o nível de recursão. Em um gráfico muito profundo, esse tipo de dados também pode ser definido como 1::bigint recursion_level .

Este gráfico é muito fácil de visualizar com um pequeno script de shell e o utilitário 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 requer esta instrução SQL em um arquivo chamado 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;

Então você invoca assim:
chmod +x pggraph
./pggraph

E você verá o gráfico resultante.
INSERT INTO person (id, parent_id) VALUES (6,2);

Execute o utilitário novamente e veja as alterações imediatas em seu gráfico direcionado:

Agora, isso não era tão difícil agora, era?