sql >> Databasteknik >  >> RDS >> PostgreSQL

Oracle till PostgreSQL:BÖRJA MED/KOPPLA VID

Och nu kommer vi till den andra artikeln i vår migrering från Oracle till PostgreSQL-serien. Den här gången ska vi ta en titt på START WITH/CONNECT BY konstruera.

I Oracle, START WITH/CONNECT BY används för att skapa en enkellänkad liststruktur som börjar på en given sentinel-rad. Den länkade listan kan ha formen av ett träd och har inga balanseringskrav.

För att illustrera, låt oss börja med en fråga och anta att tabellen har 5 rader i den.

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)

Här är den hierarkiska frågan i tabellen med Oracle-syntax.

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

Och här använder den PostgreSQL igen.

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)

Den här frågan använder många PostgreSQL-funktioner, så låt oss gå igenom det långsamt.

WITH RECURSIVE

Detta är ett "Common Table Expression" (CTE). Den definierar en uppsättning frågor som kommer att köras i samma programsats, inte bara i samma transaktion. Du kan ha hur många uttryck som helst inom parentes och ett slutgiltigt uttalande. För denna användning behöver vi bara en. Genom att förklara det påståendet som RECURSIVE , kommer den att köras iterativt tills inga fler rader returneras.

SELECT
UNION ALL
SELECT

Detta är en föreskriven fras för en rekursiv fråga. Det definieras i dokumentationen som metoden för att särskilja utgångspunkt och rekursionsalgoritm. I Oracle-termer kan du se dem som START WITH-satsen kopplad till CONNECT BY-satsen.

JOIN a ON a.id = d.parent_id

Detta är en självkoppling till CTE-satsen som tillhandahåller föregående raddata till den efterföljande iterationen.

För att illustrera hur detta fungerar, låt oss lägga till en iterationsindikator i frågan.

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 initierar rekursionsnivåindikatorn med ett värde. Observera att den första rekursionsnivån endast inträffar en gång i raderna som returneras. Det beror på att den första klausulen bara körs en gång.

Den andra klausulen är där den iterativa magin sker. Här har vi synlighet av föregående raddata, tillsammans med nuvarande raddata. Det gör att vi kan utföra de rekursiva beräkningarna.

Simon Riggs har en mycket trevlig video om hur man använder den här funktionen för grafdatabasdesign. Det är mycket informativt och du borde ta en titt.

Du kanske har märkt att den här frågan kan leda till ett cirkulärt tillstånd. Det är korrekt. Det är upp till utvecklaren att lägga till en begränsningsklausul till den andra frågan för att förhindra denna ändlösa rekursion. Till exempel, bara återkommande 4 nivåer djupa innan du bara ger upp.

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;

Kolumnnamnen och datatyperna bestäms av den första satsen. Lägg märke till att exemplet använder en gjutoperator för rekursionsnivån. I ett mycket djupt diagram kan denna datatyp också definieras som 1::bigint recursion_level .

Den här grafen är mycket lätt att visualisera med ett litet skalskript och verktyget 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<>

Detta skript kräver denna SQL-sats i en fil som heter 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;

Sedan anropar du det så här:

chmod +x pggraph
./pggraph

Och du kommer att se den resulterande grafen.

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

Kör verktyget igen och se de omedelbara ändringarna i din riktade graf:

Nu var det inte så svårt nu, eller hur?


  1. Konvertera 'smalldatetime' till 'datetime' i SQL Server (T-SQL-exempel)

  2. Entity Framework 6 med Npgsql

  3. 7 sätt att hitta dubbletter av rader i PostgreSQL medan du ignorerar den primära nyckeln

  4. Hur man konverterar Excel-ark till SQLite-databas i Android