2013-09-06 25 views
12

Googled e leggere throug alcuni articoli come this postgreSQL manual page o this blog page e provato a fare domande me con un discreto successo (parte di esse si blocca, mentre altri lavori bene e veloce), ma finora non posso completamente capire come funziona questa magia .Come selezionare con clausola WITH RECURSIVE

Qualcuno può dare spiegazione molto chiara dimostrazione tale semantica query e processo di esecuzione, meglio basato su campioni tipiche come calcolo fattoriale o espansione albero pieno da (id,parent_id,name) tavolo?

E quali sono le linee guida di base e gli errori tipici che si dovrebbero sapere per rendere buone le query with recursive?

risposta

28

Prima di tutto, proviamo a semplificare e chiarire la descrizione dell'algoritmo fornita nello manual page. Per semplificare si considerano solo union all in with recursive clausola per ora (e union in seguito):

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

Per chiarire che Descriviamo processo di esecuzione di query nel codice pseudo:

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 

O anche più brevi - motore di database esegue la selezione iniziale, prendendo le sue righe di risultati come working set. Quindi esegue ripetutamente la selezione ricorsiva sul working set, sostituendo ogni volta il contenuto del working set con il risultato della query ottenuto. Questo processo termina quando il set vuoto viene restituito dalla selezione ricorsiva. E tutte le righe di risultato date prima dalla selezione iniziale e poi dalla selezione ricorsiva vengono raccolte e alimentate alla selezione esterna, il cui risultato diventa il risultato complessivo della query.

Questa interrogazione sta calcolando fattoriale di 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 

iniziale selezionare SELECT 1 F, 3 n ci dà valori iniziali: 3 per argomento e 1 per valore di funzione.
Selezione ricorsiva SELECT F*n F, n-1 n from factorial where n>1 afferma che ogni volta è necessario moltiplicare l'ultimo valore di funzione per l'ultimo valore dell'argomento e il valore dell'argomento di decremento. motore
Database esegue in questo modo:

Innanzitutto esegue initail selezionare, che dà lo stato iniziale del recordset di lavoro:

F | n 
--+-- 
1 | 3 

Poi trasforma recordset lavorare con query ricorsiva ed ottenere il suo secondo stato :

F | n 
--+-- 
3 | 2 

Poi terzo stato:

F | n 
--+-- 
6 | 1 

Nel terzo stato non c'è riga che segue la condizione n>1 nella selezione ricorsiva, quindi il set di lavoro è le uscite del ciclo.

recordset esterno ora detiene tutte le righe, restituite da iniziale e ricorsiva selezionare:

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

Outer selezionare filtri su tutti i risultati intermedi dal recordset esterno, mostrando solo valore fattoriale finale che diventa risultato complessivo query:

F 
-- 
6 

Ed ora consideriamo tavolo 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 

'Espansione albero completo' qui significa ordinare gli elementi dell'albero in ordine di profondità in prima lettura durante il calcolo dei loro livelli e (forse) percorsi. Entrambe le attività (di corretto ordinamento e calcolo del livello o del percorso) non sono risolvibili in uno (o anche alcun numero costante di) SELECT senza utilizzare la clausola WITH RECURSIVE (o la clausola Oracle CONNECT BY, che non è supportata da PostgreSQL). Ma questa query ricorsive fa il lavoro (beh, quasi non, vedere la nota di seguito):

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 

motore di database esegue in questo modo:

In primo luogo, esso esegue initail selezionare, che dà tutti gli elementi di altissimo livello (radici) da forest tavolo:

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

Poi, esegue ricorsivo di selezione, che dà tutti gli elementi di 2 ° livello da forest tavolo:

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 

Poi, esegue ricorsivo selezionare ancora una volta, il recupero di oggetti 3D Level:

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

E ora esegue ricorsivo selezionare di nuovo, cercando di recuperare gli elementi 4 ° livello, ma ci sono nessuno di loro, in modo che il ciclo si interrompe .

SELECT esterno imposta l'ordine corretto fila leggibile, l'ordinamento su colonna di percorso:

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 

NOTA: Con conseguente ordine fila rimarrà corretta solo quando non ci sono i caratteri di punteggiatura collazione-precedono / in i nomi degli oggetti. Se rinominiamo Item 2 in Item 1 *, si interromperà l'ordine delle righe, che si trova tra Item 1 ei suoi discendenti.
La soluzione più stabile utilizza il carattere di tabulazione (E'\t') come separatore di percorso in query (che può essere sostituito da separatore di percorso più leggibile in seguito: in selezione esterna, prima che venga visualizzato in umani o ecc.). I percorsi separati da tabulazioni manterranno l'ordine corretto finché non ci saranno tabulazioni o caratteri di controllo nei nomi degli oggetti, che possono facilmente essere controllati e esclusi senza perdita di usabilità.

È molto semplice modificare l'ultima query per espandere qualsiasi sottoalbero arbitrario: è sufficiente sostituire la condizione parent_id is null con perent_id=1 (ad esempio). Si noti che questa variante di query restituirà tutti i livelli e i percorsi relativi aItem 1.

E ora circa errori tipici.L'errore tipico più notevole specifico per le query ricorsive è la definizione di condizioni di arresto anomalo nella selezione ricorsiva, che si traduce in un loop infinito.

Ad esempio, se omettiamo la condizione where n>1 nell'esempio fattoriale sopra, l'esecuzione della selezione ricorsiva non fornirà mai un set vuoto (perché non abbiamo alcuna condizione per filtrare una singola riga) e il ciclo continuerà all'infinito.

Questo è il motivo più probabile per cui alcune delle vostre domande appendere (l'altro motivo non specifico, ma ancora possibile è molto inefficace selezionare, che esegue in un tempo finito, ma molto lunga).

Non ci sono molte query RECURSIVE specifiche linee guida da menzionare, per quanto ne so. Ma vorrei suggerire (piuttosto ovvio) procedura di costruzione di query ricorsiva passo dopo passo.

  • Separatamente creare e eseguire il debug della selezione iniziale.

  • avvolgerlo con impalcature CON RECURSIVE costruire
    e iniziare a costruire e il debug selezionare ricorsiva.

Il costrutto scuffolding consigliata è così:

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 

Questo semplice esterno selezionate verranno uscita tutta recordset esterno, che, come è noto, contiene tutte le righe di uscita dal iniziale selezionare e ogni esecuzione recusrive seleziona in un ciclo nel loro ordine di uscita originale - proprio come negli esempi sopra! La parte limit 1000 impedisce l'impiccagione, sostituendola con un'uscita sovradimensionata in cui sarà possibile vedere il punto di arresto mancato.

  • Dopo il debug iniziale e ricorsivo, selezionare build ed eseguire il debug della selezione esterna.

E ora l'ultima cosa da menzionare - la differenza nell'uso union invece di union all in with recursive clausola. Introduce un vincolo di unicità della riga che risulta in due righe aggiuntive nel nostro pseudocodice di esecuzione:

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