sql >> Database teknologi >  >> RDS >> PostgreSQL

Oracle til PostgreSQL:START MED / FORBIND VED

Og nu kommer vi til den anden artikel i vores migration fra Oracle til PostgreSQL-serien. Denne gang tager vi et kig på START WITH/CONNECT BY konstruere.

I Oracle, START WITH/CONNECT BY bruges til at oprette en enkelt linket listestruktur, der starter ved en given vagtpostrække. Den linkede liste kan have form af et træ og har ingen balanceringskrav.

For at illustrere det, lad os starte med en forespørgsel og antage, at tabellen har 5 rækker i sig.

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)

Her er den hierarkiske forespørgsel i tabellen ved hjælp af Oracle-syntaks.

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

Og her bruger den igen 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)

Denne forespørgsel gør brug af en masse PostgreSQL-funktioner, så lad os gå langsomt igennem det.

WITH RECURSIVE

Dette er et "Common Table Expression" (CTE). Den definerer et sæt forespørgsler, der vil blive udført i den samme sætning, ikke kun i den samme transaktion. Du kan have et hvilket som helst antal parentetiske udtryk og en sidste erklæring. Til denne brug har vi kun brug for én. Ved at erklære den erklæring som RECURSIVE , vil det iterativt udføres, indtil der ikke returneres flere rækker.

SELECT
UNION ALL
SELECT

Dette er en foreskrevet sætning til en rekursiv forespørgsel. Det er i dokumentationen defineret som metoden til at skelne mellem udgangspunktet og rekursionsalgoritmen. I Oracle-termer kan du tænke på dem som START WITH-klausulen forbundet med CONNECT BY-klausulen.

JOIN a ON a.id = d.parent_id

Dette er en selv-join til CTE-sætningen, der leverer de foregående rækkedata til den efterfølgende iteration.

For at illustrere, hvordan dette virker, lad os tilføje en iterationsindikator til forespørgslen.

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)

Vi initialiserer rekursionsniveauindikatoren med en værdi. Bemærk, at i de rækker, der returneres, forekommer det første rekursionsniveau kun én gang. Det er fordi den første klausul kun udføres én gang.

Den anden klausul er, hvor den iterative magi sker. Her har vi synlighed af de foregående rækkedata sammen med de aktuelle rækkedata. Det giver os mulighed for at udføre de rekursive beregninger.

Simon Riggs har en meget flot video om, hvordan man bruger denne funktion til grafdatabasedesign. Det er meget informativt, og du bør tage et kig.

Du har måske bemærket, at denne forespørgsel kan føre til en cirkulær tilstand. Det er korrekt. Det er op til udvikleren at tilføje en begrænsende klausul til den anden forespørgsel for at forhindre denne endeløse rekursion. For eksempel kun tilbagevendende 4 niveauer dybe, før du bare giver op.

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;

Kolonnenavnene og datatyperne bestemmes af den første klausul. Bemærk, at eksemplet bruger en casting-operator til rekursionsniveauet. I en meget dyb graf kunne denne datatype også defineres som 1::bigint recursion_level .

Denne graf er meget nem at visualisere med et lille shell-script og graphviz-værktøjet.

#!/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<>

Dette script kræver denne SQL-sætning i en fil kaldet 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;

Så kalder du det sådan her:

chmod +x pggraph
./pggraph

Og du vil se den resulterende graf.

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

Kør værktøjet igen, og se de øjeblikkelige ændringer af din rettede graf:

Det var nu ikke så svært, vel?


  1. ST_HexagonGrid geomvektor for at finde alle punkter

  2. Codeigniter-transaktioner

  3. PreparedStatement-spørgsmål i Java mod Oracle

  4. Hvornår skal man bruge Common Table Expression (CTE)