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

Как да изберете с помощта на клауза WITH RECURSIVE

Първо, нека се опитаме да опростим и изясним описанието на алгоритъма, дадено на страницата с ръководството. За да го опростите, разгледайте само union all в with recursive клауза засега (и union по-късно):

WITH RECURSIVE pseudo-entity-name(column-names) AS (
    Initial-SELECT
UNION ALL
    Recursive-SELECT using pseudo-entity-name
)
Outer-SELECT using pseudo-entity-name

За да го изясним, нека опишем процеса на изпълнение на заявка в псевдокод:

working-recordset = result of Initial-SELECT

append working-recordset to empty outer-recordset

while( working-recordset is not empty ) begin

    new working-recordset = result of Recursive-SELECT 
        taking previous working-recordset as pseudo-entity-name

    append working-recordset to outer-recordset

end

overall-result = result of Outer-SELECT 
    taking outer-recordset as pseudo-entity-name

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

Тази заявка се изчислява факторно от 3:

WITH RECURSIVE factorial(F,n) AS (
    SELECT 1 F, 3 n
UNION ALL
    SELECT F*n F, n-1 n from factorial where n>1
)
SELECT F from factorial where n=1

Първоначално изберете SELECT 1 F, 3 n ни дава начални стойности:3 за аргумент и 1 за стойност на функцията.
Рекурсивно избиране SELECT F*n F, n-1 n from factorial where n>1 заявява, че всеки път, когато трябва да умножим стойността на последната функция по стойността на последния аргумент и да намалим стойността на аргумента.
Механизмът на база данни го изпълнява по следния начин:

Преди всичко изпълнява initail select, което дава първоначалното състояние на работния набор от записи:

F | n
--+--
1 | 3

След това трансформира работния набор от записи с рекурсивна заявка и получава второто си състояние:

F | n
--+--
3 | 2

След това трето състояние:

F | n
--+--
6 | 1

В третото състояние няма ред, който следва n>1 условие при рекурсивен избор, така нататък работният набор е изход от цикъл.

Външният набор от записи вече съдържа всички редове, върнати от начален и рекурсивен избор:

F | n
--+--
1 | 3
3 | 2
6 | 1

Външният избор филтрира всички междинни резултати от външния набор от записи, като показва само крайната факторна стойност, която става общ резултат от заявката:

F 
--
6

И сега нека разгледаме таблицата forest(id,parent_id,name) :

id | parent_id | name
---+-----------+-----------------
1  |           | item 1
2  | 1         | subitem 1.1
3  | 1         | subitem 1.2
4  | 1         | subitem 1.3
5  | 3         | subsubitem 1.2.1
6  |           | item 2
7  | 6         | subitem 2.1
8  |           | item 3

'Разгъване на цялото дърво ' тук означава сортиране на елементи от дърво в четим от човека ред в дълбочина, докато се изчисляват техните нива и (може би) пътища. И двете задачи (на правилно сортиране и изчисляване на ниво или път) не са разрешими в един (или дори постоянен брой) SELECT без използване на клауза WITH RECURSIVE (или клауза на Oracle CONNECT BY, която не се поддържа от PostgreSQL). Но тази рекурсивна заявка върши работата (е, почти върши, вижте бележката по-долу):

WITH RECURSIVE fulltree(id,parent_id,level,name,path) AS (
    SELECT id, parent_id, 1 as level, name, name||'' as path from forest where parent_id is null
UNION ALL
    SELECT t.id, t.parent_id, ft.level+1 as level, t.name, ft.path||' / '||t.name as path
    from forest t, fulltree ft where t.parent_id = ft.id
)
SELECT * from fulltree order by path

Двигателят за база данни го изпълнява по следния начин:

Първо, той изпълнява initail select, което дава всички елементи от най-високо ниво (корени) от forest таблица:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
1  |           | 1     | item 1           | item 1
8  |           | 1     | item 3           | item 3
6  |           | 1     | item 2           | item 2

След това изпълнява рекурсивно избиране, което дава всички елементи от 2-ро ниво от forest таблица:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
2  | 1         | 2     | subitem 1.1      | item 1 / subitem 1.1
3  | 1         | 2     | subitem 1.2      | item 1 / subitem 1.2
4  | 1         | 2     | subitem 1.3      | item 1 / subitem 1.3
7  | 6         | 2     | subitem 2.1      | item 2 / subitem 2.1

След това отново изпълнява рекурсивно избиране, извличайки елементи от 3d ниво:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
5  | 3         | 3     | subsubitem 1.2.1 | item 1 / subitem 1.2 / subsubitem 1.2.1

И сега той отново изпълнява рекурсивно избиране, опитвайки се да извлече елементи от 4-то ниво, но няма нито един от тях, така че цикълът излиза.

Външният SELECT задава правилния четим от човека ред на редове, сортирайки по колоната на пътя:

id | parent_id | level | name             | path
---+-----------+-------+------------------+----------------------------------------
1  |           | 1     | item 1           | item 1
2  | 1         | 2     | subitem 1.1      | item 1 / subitem 1.1
3  | 1         | 2     | subitem 1.2      | item 1 / subitem 1.2
5  | 3         | 3     | subsubitem 1.2.1 | item 1 / subitem 1.2 / subsubitem 1.2.1
4  | 1         | 2     | subitem 1.3      | item 1 / subitem 1.3
6  |           | 1     | item 2           | item 2
7  | 6         | 2     | subitem 2.1      | item 2 / subitem 2.1
8  |           | 1     | item 3           | item 3

ЗАБЕЛЕЖКА :Полученият ред на редовете ще остане правилен само докато няма препинателни знаци, предхождащи / в имената на артикули. Ако преименуваме Item 2 в Item 1 * , ще наруши реда на редовете, стоящ между Item 1 и неговите потомци.
По-стабилното решение е използването на табулаторен знак (E'\t' ) като разделител на пътя в заявка (който може да бъде заменен с по-четлив разделител на пътя по-късно:във външен избор, преди извеждане на човек или т.н.). Пътищата, разделени с табулатори, ще запазят правилния ред, докато в имената на елементите има табулатори или контролни знаци – които лесно могат да бъдат проверени и изключени без загуба на използваемост.

Много е лесно да промените последната заявка, за да разширите произволно поддърво - трябва само да замените условието parent_id is null с perent_id=1 (например). Имайте предвид, че този вариант на заявка ще върне всички нива и пътища относно Item 1 .

А сега затипичните грешки . Най-забележимата типична грешка, специфична за рекурсивните заявки, е дефинирането на условия за лошо спиране в рекурсивния избор, което води до безкраен цикъл.

Например, ако пропуснем where n>1 условие във факторната извадка по-горе, изпълнението на рекурсивния избор никога няма да даде празен набор (тъй като нямаме условие за филтриране на един ред) и цикълът ще продължи безкрайно.

Това е най-вероятната причина, поради която някои от вашите заявки увисват (другата неспецифична, но все пак възможна причина е много неефективен избор, който се изпълнява за ограничено, но много дълго време).

Няма много насоки за заявки, специфични за РЕКУРСИВНО да спомена, доколкото знам. Но бих искал да предложа (доста очевидна) стъпка по стъпка процедура за изграждане на рекурсивна заявка.

  • Отделно изградете и отстранете грешки във вашия първоначален избор.

  • Увийте го със скеле WITH RECURSIVE конструкция
    и започнете да изграждате и отстранявате грешки в рекурсивния си избор.

Препоръчителната конструкция за изтриване е следната:

WITH RECURSIVE rec( <Your column names> ) AS (
    <Your ready and working initial SELECT>
UNION ALL
    <Recursive SELECT that you are debugging now>
)
SELECT * from rec limit 1000

Този най-прост външен избор ще изведе целия външен набор от записи, който, както знаем, съдържа всички изходни редове от първоначалния избор и всяко изпълнение на recusrive select в цикъл в техния оригинален изходен ред - точно както в примерите по-горе! limit 1000 част ще предотврати увисване, като я заменя с изход с голям размер, в който ще можете да видите пропуснатата точка на спиране.

  • След отстраняване на грешки в началния и рекурсивния избор изградете и отстранете грешките във външния си избор.

И сега последното нещо, което трябва да споменем – разликата в използването на union вместо union all в with recursive клауза. Той въвежда ограничение за уникалност на редове, което води до два допълнителни реда в нашия псевдокод за изпълнение:

working-recordset = result of Initial-SELECT

discard duplicate rows from working-recordset /*union-specific*/

append working-recordset to empty outer-recordset

while( working-recordset is not empty ) begin

    new working-recordset = result of Recursive-SELECT 
        taking previous working-recordset as pseudo-entity-name

    discard duplicate rows and rows that have duplicates in outer-recordset 
        from working-recordset /*union-specific*/

    append working-recordset to outer-recordset

end

overall-result = result of Outer-SELECT 
    taking outer-recordset as pseudo-entity-name



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как работи функцията Radians() в PostgreSQL

  2. Мониторинг на разпространението на Percona за PostgreSQL – ключови показатели

  3. Създаване на оптимална среда за PostgreSQL

  4. кодирането UTF8 не съответства на локала en_US; избраната настройка LC_CTYPE изисква кодиране LATIN1

  5. Вземете стойности от първия и последния ред за група