PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak wybrać za pomocą klauzuli WITH RECURSIVE

Przede wszystkim spróbujmy uprościć i doprecyzować opis algorytmu podany na stronie podręcznika. Aby to uprościć, rozważ tylko union all w with recursive na razie klauzula (i union później):

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

Aby to wyjaśnić, opiszmy proces wykonywania zapytania w pseudokodzie:

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

Lub nawet krócej — aparat bazy danych wykonuje wstępne zaznaczenie, przyjmując wiersze wyników jako zestaw roboczy. Następnie wielokrotnie wykonuje rekurencyjne wybieranie na zbiorze roboczym, za każdym razem zastępując zawartość zbioru roboczego otrzymanym wynikiem zapytania. Ten proces kończy się, gdy rekurencyjny wybór zwraca pusty zestaw. Wszystkie wiersze wyników podane najpierw przez wybór początkowy, a następnie przez wybór rekurencyjny są gromadzone i przesyłane do zewnętrznego wyboru, który to wynik staje się ogólnym wynikiem zapytania.

To zapytanie oblicza silnik z 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

Wybór początkowy SELECT 1 F, 3 n daje nam wartości początkowe:3 dla argumentu i 1 dla wartości funkcji.
Wybierz rekurencyjnie SELECT F*n F, n-1 n from factorial where n>1 stwierdza, że ​​za każdym razem musimy pomnożyć wartość ostatniej funkcji przez wartość ostatniego argumentu i zmniejszyć wartość argumentu.
Silnik bazy danych wykonuje to w następujący sposób:

Przede wszystkim wykonuje initail select, który daje początkowy stan działającego zestawu rekordów:

F | n
--+--
1 | 3

Następnie przekształca działający zestaw rekordów za pomocą zapytania rekurencyjnego i uzyskuje jego drugi stan:

F | n
--+--
3 | 2

Następnie trzeci stan:

F | n
--+--
6 | 1

W trzecim stanie nie ma wiersza następującego po n>1 warunek w rekurencyjnym wyborze, więc kolejnym zbiorem roboczym są wyjścia z pętli.

Zewnętrzny zestaw rekordów zawiera teraz wszystkie wiersze zwrócone przez wybór początkowy i rekurencyjny:

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

Zewnętrzny wybór odfiltrowuje wszystkie pośrednie wyniki z zewnętrznego zestawu rekordów, pokazując tylko końcową wartość silni, która staje się ogólnym wynikiem zapytania:

F 
--
6

A teraz rozważmy tabelę 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

'Rozwijanie pełnego drzewa Oznacza tutaj sortowanie elementów drzewa w czytelnej dla człowieka kolejności w głąb przy obliczaniu ich poziomów i (być może) ścieżek. Oba zadania (prawidłowego sortowania i obliczania poziomu lub ścieżki) nie są możliwe do rozwiązania w jednym (lub nawet stałej liczbie) SELECT bez użycia klauzuli WITH RECURSIVE (lub klauzuli Oracle CONNECT BY, która nie jest obsługiwana przez PostgreSQL). Ale to rekurencyjne zapytanie spełnia swoje zadanie (no, prawie tak, patrz uwaga poniżej):

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

Silnik bazy danych wykonuje to w następujący sposób:

Po pierwsze, wykonuje initail select, który daje wszystkie elementy najwyższego poziomu (root) z forest tabela:

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

Następnie wykonuje rekurencyjne zaznaczanie, które daje wszystkie elementy drugiego poziomu z forest tabela:

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

Następnie ponownie wykonuje rekurencyjne zaznaczanie, pobierając elementy poziomu 3D:

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

A teraz ponownie wykonuje rekurencyjne zaznaczenie, próbując pobrać elementy czwartego poziomu, ale ich nie ma, więc pętla się kończy.

Zewnętrzny SELECT ustawia prawidłową, czytelną dla człowieka kolejność wierszy, sortując według kolumny ścieżki:

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

UWAGA :Wynikowa kolejność wierszy pozostanie poprawna tylko wtedy, gdy nie będzie żadnych znaków interpunkcyjnych poprzedzających sortowanie / w nazwach przedmiotów. Jeśli zmienimy nazwę Item 2 w Item 1 * , zepsuje kolejność wierszy, stojąc między Item 1 i jego potomków.
Bardziej stabilnym rozwiązaniem jest użycie znaku tabulacji (E'\t' ) jako separator ścieżki w zapytaniu (który można później zastąpić bardziej czytelnym separatorem ścieżki:w zewnętrznym zaznaczeniu, przed wyświetleniem do człowieka itp.). Ścieżki oddzielone tabulatorami zachowają prawidłową kolejność, dopóki w nazwach elementów nie pojawią się tabulatory lub znaki kontrolne - co można łatwo sprawdzić i wykluczyć bez utraty użyteczności.

Modyfikowanie ostatniego zapytania w celu rozwinięcia dowolnego poddrzewa jest bardzo proste - wystarczy podstawić warunek parent_id is null z perent_id=1 (na przykład). Pamiętaj, że ten wariant zapytania zwróci wszystkie poziomy i ścieżki względem Item 1 .

A teraz o typowych błędach . Najbardziej zauważalnym typowym błędem charakterystycznym dla zapytań rekurencyjnych jest zdefiniowanie warunków złego zatrzymania w rekurencyjnym wyborze, co skutkuje nieskończoną pętlą.

Na przykład, jeśli pominiemy where n>1 warunek w próbce silniowej powyżej, wykonanie rekurencyjnego wyboru nigdy nie da pustego zestawu (ponieważ nie mamy warunku do odfiltrowania pojedynczego wiersza), a pętla będzie kontynuowana w nieskończoność.

Jest to najbardziej prawdopodobny powód, dla którego niektóre z Twoich zapytań zawieszają się (innym niespecyficznym, ale wciąż możliwym powodem jest bardzo nieefektywny wybór, który wykonuje się w skończonym, ale bardzo długim czasie).

Nie ma wiele wskazówek dotyczących zapytań REKURSOWYCH wspomnieć, o ile wiem. Ale chciałbym zasugerować (raczej oczywistą) procedurę rekurencyjnego budowania zapytań krok po kroku.

  • Oddzielnie skompiluj i debuguj swój początkowy wybór.

  • Owiń to rusztowaniem Z konstrukcją REKURSOWNĄ
    i zacznij budować i debugować wybór rekurencyjny.

Zalecana konstrukcja rusztowania jest następująca:

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

Ten najprostszy wybór zewnętrzny wygeneruje cały zewnętrzny zestaw rekordów, który, jak wiemy, zawiera wszystkie wiersze wyjściowe z początkowego wyboru i każde wykonanie rekurencyjnego wyboru w pętli w oryginalnej kolejności wyjściowej — tak jak w powyższych przykładach! limit 1000 część zapobiegnie zawieszeniu, zastępując ją zbyt dużym wyjściem, w której będziesz mógł zobaczyć pominięty punkt zatrzymania.

  • Po debugowaniu początkowym i rekurencyjnym wybierz kompilację i debuguj zewnętrzny wybór.

A teraz ostatnia rzecz, o której należy wspomnieć - różnica w używaniu union zamiast union all w with recursive klauzula. Wprowadza ograniczenie unikalności wiersza, co skutkuje dwoma dodatkowymi wierszami w naszym pseudokodzie wykonania:

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. Zmień kodowanie bazy danych PostgreSql

  2. Jak działa funkcja Ln() w PostgreSQL

  3. Zapytanie krzyżowe PostgreSQL

  4. Wydajność OLTP od PostgreSQL 8.3

  5. Rails:FATAL - Uwierzytelnienie peera nie powiodło się dla użytkownika (PG::Error)