Först och främst, låt oss försöka förenkla och förtydliga algoritmbeskrivningen som ges på manualsidan. För att förenkla det, överväg bara union all
i with recursive
klausul för nu (och union
senare):
WITH RECURSIVE pseudo-entity-name(column-names) AS (
Initial-SELECT
UNION ALL
Recursive-SELECT using pseudo-entity-name
)
Outer-SELECT using pseudo-entity-name
Låt oss för att förtydliga det beskriva exekveringsprocessen i pseudokod:
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
Eller ännu kortare - Databasmotorn kör initialt urval och tar dess resultatrader som arbetsuppsättning. Sedan exekverar den upprepade gånger rekursivt urval på arbetsuppsättningen, varje gång ersätter innehållet i arbetsuppsättningen med erhållna frågeresultat. Denna process slutar när tom uppsättning returneras av rekursivt urval. Och alla resultatrader som ges först genom initialt urval och sedan genom rekursivt urval samlas in och matas till yttre urval, vilket resultat blir ett övergripande frågeresultat.
Den här frågan beräknar faktoriell av 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
Initialt välj SELECT 1 F, 3 n
ger oss initiala värden:3 för argument och 1 för funktionsvärde.
Rekursivt välj SELECT F*n F, n-1 n from factorial where n>1
anger att varje gång vi behöver multiplicera det sista funktionsvärdet med det sista argumentvärdet och minska argumentvärdet.
Databasmotorn kör det så här:
Först och främst kör den initail select, vilket ger det initiala tillståndet för fungerande postuppsättning:
F | n
--+--
1 | 3
Sedan omvandlar den fungerande postuppsättning med rekursiv fråga och får sitt andra tillstånd:
F | n
--+--
3 | 2
Sedan tredje tillstånd:
F | n
--+--
6 | 1
I det tredje tillståndet finns det ingen rad som följer efter n>1
villkor i rekursivt urval, så vidare är arbetsuppsättningen loop exits.
Yttre postuppsättning innehåller nu alla rader, som returneras av initialt och rekursivt val:
F | n
--+--
1 | 3
3 | 2
6 | 1
Outer Select filtrerar bort alla mellanliggande resultat från yttre postuppsättning, och visar endast det slutliga faktorvärdet som blir det övergripande frågeresultatet:
F
--
6
Och låt oss nu överväga tabellen 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
'Expanderar hela trädet ' betyder här att sortera trädobjekt i mänskligt läsbart djup-första ordning samtidigt som deras nivåer och (kanske) vägar beräknas. Båda uppgifterna (med korrekt sortering och beräkningsnivå eller sökväg) är inte lösbara i en (eller ens något konstant antal) SELECT utan att använda WITH RECURSIVE-satsen (eller Oracle CONNECT BY-satsen, som inte stöds av PostgreSQL). Men den här rekursiva frågan gör jobbet (ja, det gör nästan, se anteckningen nedan):
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
Databasmotorn kör det så här:
För det första kör den initail select, vilket ger alla objekt på högsta nivå (rötter) från forest
tabell:
id | parent_id | level | name | path
---+-----------+-------+------------------+----------------------------------------
1 | | 1 | item 1 | item 1
8 | | 1 | item 3 | item 3
6 | | 1 | item 2 | item 2
Sedan kör den rekursivt urval, vilket ger alla objekt på andra nivån från forest
tabell:
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
Sedan kör den rekursivt val igen och hämtar objekt på 3d-nivå:
id | parent_id | level | name | path
---+-----------+-------+------------------+----------------------------------------
5 | 3 | 3 | subsubitem 1.2.1 | item 1 / subitem 1.2 / subsubitem 1.2.1
Och nu kör den rekursivt urval igen och försöker hämta objekt på fjärde nivån, men det finns inga av dem, så slingan avslutas.
Den yttre SELECT ställer in rätt läsbar radordning, sortering på sökvägskolumnen:
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
OBS :Den resulterande radordningen förblir endast korrekt så länge det inte finns några skiljetecken, sortering före /
i objektnamnen. Om vi byter namn på Item 2
i Item 1 *
, kommer den att bryta radordning, stående mellan Item 1
och dess ättlingar.
En stabilare lösning är att använda tabbtecken (E'\t'
) som sökvägsseparator i fråga (som kan ersättas med mer läsbar sökvägsseparator senare:i yttre val, innan den förskjuts till människa eller etc). Flikseparerade sökvägar kommer att behålla korrekt ordning tills det finns flikar eller kontrolltecken i objektnamnen - vilket enkelt kan kontrolleras och uteslutas utan att användbarheten går förlorad.
Det är mycket enkelt att ändra den senaste frågan för att expandera vilket godtyckligt underträd som helst - du behöver bara ersätta villkoret parent_id is null
med perent_id=1
(till exempel). Observera att den här frågevarianten returnerar alla nivåer och sökvägar relativt till Item 1
.
Och nu om typiska misstag . Det mest anmärkningsvärda typiska misstaget specifikt för rekursiva frågor är att definiera dåliga stoppförhållanden i rekursivt urval, vilket resulterar i oändlig looping.
Till exempel, om vi utelämnar where n>1
villkor i faktorexempel ovan, exekvering av rekursivt urval kommer aldrig att ge en tom uppsättning (eftersom vi inte har något villkor för att filtrera bort en rad) och looping kommer att fortsätta i det oändliga.
Det är den mest sannolika anledningen till att några av dina frågor hänger sig (den andra ospecifika men fortfarande möjliga orsaken är mycket ineffektivt urval, som körs på ändlig men mycket lång tid).
Det finns inte många RECURSIVE-specifika riktlinjer för frågor att nämna, så vitt jag vet. Men jag skulle vilja föreslå (ganska uppenbart) steg för steg rekursiv frågebyggnadsprocedur.
-
Bygg och felsök ditt första val separat.
-
Linda in den med byggnadsställningar MED REKURSIV konstruktion
och börja bygga och felsöka ditt rekursiva urval.
Den rekommenderade byggnadskonstruktionen är så här:
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
Detta enklaste yttre val kommer att mata ut hela den yttre postuppsättningen, som, som vi vet, innehåller alla utdatarader från initialval och varje exekvering av rekusriv val i en slinga i deras ursprungliga utdataordning - precis som i samplen ovan! limit 1000
en del förhindrar att den hänger sig och ersätter den med överdimensionerad utgång där du kommer att kunna se den missade stopppunkten.
- När du har felsökt initialt och rekursivt välj bygg och felsök ditt yttre urval.
Och nu det sista att nämna - skillnaden i att använda union
istället för union all
i with recursive
klausul. Den introducerar radunikitetsbegränsning som resulterar i två extra rader i vår exekveringspseudokod:
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