PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Oracle към PostgreSQL:ЗАПОЧНЕТЕ С/СВЪРЖЕТЕ ОТ

И сега стигаме до втората статия от нашата серия за миграция от Oracle към PostgreSQL. Този път ще разгледаме START WITH/CONNECT BY конструкция.

В Oracle START WITH/CONNECT BY се използва за създаване на едносвързана списъчна структура, започваща от даден сентинален ред. Свързаният списък може да бъде под формата на дърво и няма изискване за балансиране.

За да илюстрираме, нека започнем със заявка и да предположим, че таблицата има 5 реда.

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)

Ето йерархичната заявка на таблицата, използваща синтаксис на 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

И ето го отново с помощта на 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)

Тази заявка използва много функции на PostgreSQL, така че нека преминем през нея бавно.

WITH RECURSIVE

Това е „Общ израз на таблица“ (CTE). Той дефинира набор от заявки, които ще бъдат изпълнени в същия оператор, а не само в една и съща транзакция. Може да имате произволен брой изрази в скоби и окончателно изявление. За тази употреба ни трябва само един. Като декларирате това изявление за RECURSIVE , той ще се изпълнява итеративно, докато не бъдат върнати повече редове.

SELECT
UNION ALL
SELECT

Това е предписана фраза за рекурсивна заявка. В документацията е дефиниран като метод за разграничаване на началната точка и рекурсивния алгоритъм. От гледна точка на Oracle, можете да ги мислите като клауза START WITH, обединена с клаузата CONNECT BY.

JOIN a ON a.id = d.parent_id

Това е самоприсъединяване към оператора CTE, който предоставя данните от предишния ред за последващата итерация.

За да илюстрираме как работи това, нека добавим индикатор за итерация към заявката.

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)

Инициализираме индикатора за ниво на рекурсия със стойност. Имайте предвид, че в редовете, които се връщат, първото ниво на рекурсия се появява само веднъж. Това е така, защото първата клауза се изпълнява само веднъж.

Втората клауза е мястото, където се случва итеративната магия. Тук имаме видимост на данните от предишния ред, заедно с данните за текущия ред. Това ни позволява да извършваме рекурсивните изчисления.

Саймън Ригс има много хубаво видео за това как да използвате тази функция за дизайн на графична база данни. Той е много информативен и трябва да го разгледате.

Може би сте забелязали, че тази заявка може да доведе до кръгово условие. Това е вярно. От разработчика зависи да добави ограничителна клауза към втората заявка, за да предотврати тази безкрайна рекурсия. Например повтаряне само на 4 нива, преди просто да се откажете.

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;

Имената на колоните и типовете данни се определят от първата клауза. Забележете, че примерът използва оператор за кастинг за нивото на рекурсия. В много дълбока графика този тип данни може също да бъде дефиниран като 1::bigint recursion_level .

Тази графика е много лесна за визуализиране с малък шел скрипт и помощната програма 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<>

Този скрипт изисква този SQL израз във файл, наречен 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;

След това го извиквате така:

chmod +x pggraph
./pggraph

И ще видите получената графика.

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

Стартирайте отново помощната програма и вижте незабавните промени във вашата насочена графика:

Сега това не беше толкова трудно, нали?


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Връзката, предадена на # или трябва да е структурно съвместима. Несъвместими стойности:[:references]

  2. Как да получа локални данни в база данни само за четене с помощта на dplyr?

  3. Изтриване на дублиращи се редове (не изтривайте всички дублирани)

  4. PostgreSQL функция/запаметена процедура CURRENT_TIMESTAMP не се променя

  5. Добавяне на едно от две ненулево ограничение в postgresql