PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Auto join ricorsivo postgresql

Questo è un uso classico di una semplice espressione ricorsiva di tabella comune (WITH RECURSIVE ), disponibile in PostgreSQL 8.4 e versioni successive.

Dimostrato qui:http://sqlfiddle.com/#!12/78e15/9

Dati i dati di esempio come SQL:

CREATE TABLE Table1
    ("ID1" text, "ID2" text)
;

INSERT INTO Table1
    ("ID1", "ID2")
VALUES
    ('vc1', 'vc2'),
    ('vc2', 'vc3'),
    ('vc3', 'vc4'),
    ('vc4', 'rc7')
;

Potresti scrivere:

WITH RECURSIVE chain(from_id, to_id) AS (
  SELECT NULL, 'vc2'
  UNION
  SELECT c.to_id, t."ID2"
  FROM chain c
  LEFT OUTER JOIN Table1 t ON (t."ID1" = to_id)
  WHERE c.to_id IS NOT NULL
)
SELECT from_id FROM chain WHERE to_id IS NULL;

Ciò che fa è percorrere iterativamente la catena, aggiungendo ogni riga alla chain tabella come da e verso i puntatori. Quando incontra una riga per la quale il riferimento "a" non esiste, aggiungerà un riferimento "a" nullo per quella riga. L'iterazione successiva noterà che il riferimento "a" è nullo e produce zero righe, il che provoca la fine dell'iterazione.

La query esterna raccoglie quindi le righe che sono state determinate come la fine della catena avendo un to_id inesistente.

Ci vuole un po' di sforzo per capire le CTE ricorsive. Le cose fondamentali da capire sono:

  • Iniziano con l'output di una query iniziale, che uniscono ripetutamente all'output della "parte ricorsiva" (la query dopo UNION o UNION ALL ) finché la parte ricorsiva non aggiunge righe. Ciò interrompe l'iterazione.

  • Non sono realmente ricorsivi, più iterativi, anche se vanno bene per il genere di cose per cui potresti usare la ricorsione.

Quindi stai fondamentalmente costruendo una tabella in un ciclo. Non è possibile eliminare righe o modificarle, ma solo aggiungerne di nuove, quindi in genere è necessaria una query esterna che filtri i risultati per ottenere le righe dei risultati desiderate. Spesso aggiungerai colonne extra contenenti dati intermedi che utilizzi per tenere traccia dello stato dell'iterazione, controllare le condizioni di arresto, ecc.

Può aiutare a guardare il risultato non filtrato. Se sostituisco la query di riepilogo finale con una semplice SELECT * FROM chain Posso vedere la tabella che è stata generata:

 from_id | to_id 
---------+-------
         | vc2
 vc2     | vc3
 vc3     | vc4
 vc4     | rc7
 rc7     | 
(5 rows)

La prima riga è la riga del punto di partenza aggiunta manualmente, in cui specifichi cosa vuoi cercare, in questo caso era vc2 . Ogni riga successiva è stata aggiunta da UNION ed termine ricorsivo, che esegue un LEFT OUTER JOIN sul risultato precedente e restituisce un nuovo insieme di righe che accoppiano il precedente to_id (ora nel from_id colonna) al successivo to_id . Se il LEFT OUTER JOIN non corrisponde quindi a to_id sarà null, facendo sì che la chiamata successiva restituisca ora le righe e termini l'iterazione.

Perché questa query non tenta di aggiungere solo l'ultimo riga ogni volta, in realtà sta ripetendo un bel po' di lavoro ogni iterazione. Per evitare ciò, dovresti usare un approccio più simile a quello di Gordon, ma filtrare ulteriormente sul campo di profondità precedente quando hai scansionato la tabella di input, quindi hai unito solo la riga più recente. In pratica, questo di solito non è necessario, ma può essere un problema per set di dati molto grandi o per i quali non è possibile creare indici appropriati.

Maggiori informazioni possono essere apprese nella documentazione di PostgreSQL sui CTE.