È possibile effettuare una query SQL ricorsiva?
-
09-06-2019 - |
Domanda
Ho una tabella simile a questa:
CREATE TABLE example (
id integer primary key,
name char(200),
parentid integer,
value integer);
Posso utilizzare il campo genitore per organizzare i dati in una struttura ad albero.
Ora ecco la parte che non riesco a risolvere.Dato un genitore, è possibile scrivere un'istruzione SQL per sommare tutti i campi valore sotto quel genitore e ricorsire lungo il ramo dell'albero?
AGGIORNAMENTO: Sto utilizzando posgreSQL quindi le fantasiose funzionalità di MS-SQL non sono disponibili per me.In ogni caso, vorrei che questa fosse trattata come una domanda SQL generica.
A proposito, sono molto colpito dal fatto di avere 6 risposte entro 15 minuti dalla domanda!Vai in overflow dello stack!
Soluzione
Esistono alcuni modi per fare ciò di cui hai bisogno in PostgreSQL.
Se puoi installare moduli, guarda il contrib tablefunc.Ha una funzione connectby() che gestisce l'attraversamento degli alberi. http://www.postgresql.org/docs/8.3/interactive/tablefunc.html
Controlla anche il contributo ltree, che potresti adattare alla tua tabella per utilizzare: http://www.postgresql.org/docs/8.3/interactive/ltree.html
Oppure puoi attraversare tu stesso l'albero con una funzione PL/PGSQL.
Qualcosa come questo:
create or replace function example_subtree (integer)
returns setof example as
'declare results record;
child record;
begin
select into results * from example where parent_id = $1;
if found then
return next results;
for child in select id from example
where parent_id = $1
loop
for temp in select * from example_subtree(child.id)
loop
return next temp;
end loop;
end loop;
end if;
return null;
end;' language 'plpgsql';
select sum(value) as value_sum
from example_subtree(1234);
Altri suggerimenti
Ecco uno script di esempio che utilizza un'espressione di tabella comune:
with recursive sumthis(id, val) as (
select id, value
from example
where id = :selectedid
union all
select C.id, C.value
from sumthis P
inner join example C on P.id = C.parentid
)
select sum(val) from sumthis
Lo script sopra crea una tabella "virtuale" chiamata sumthis
che ha colonne id
E val
.È definito come il risultato di due selezioni unite union all
.
Primo select
ottiene la radice (where id = :selectedid
).
Secondo select
segue iterativamente i figli dei risultati precedenti finché non c'è nulla da restituire.
Il risultato finale può quindi essere elaborato come una normale tabella.In questo caso viene sommata la colonna val.
Dalla versione 8.4, PostgreSQL ha supporto per query ricorsive per le espressioni di tabella comuni che utilizzano lo standard SQL WITH
sintassi.
Se desideri una soluzione portatile che funzioni su qualsiasi file ANSI SQL-92 RDBMS, dovrai aggiungere una nuova colonna alla tua tabella.
Joe Celko è l'autore originale del Insiemi nidificati approccio alla memorizzazione delle gerarchie in SQL.Puoi Google Gerarchia degli "insiemi nidificati". per capire meglio lo sfondo.
Oppure puoi semplicemente rinominare parentid in sinistra e aggiungi a rightid.
Ecco il mio tentativo di riassumere i Nested Sets, che fallirà tristemente perché non sono Joe Celko:SQL è un linguaggio basato su insiemi e il modello di adiacenza (memorizzazione dell'ID genitore) NON è una rappresentazione di una gerarchia basata su insiemi.Pertanto non esiste un metodo basato su insiemi puro per interrogare uno schema di adiacenza.
Tuttavia, negli ultimi anni la maggior parte delle principali piattaforme ha introdotto estensioni per affrontare questo preciso problema.Quindi, se qualcuno risponde con una soluzione specifica per Postgres, usala con tutti i mezzi.
Un modo standard per effettuare una query ricorsiva in SQL
sono ricorsivi CTE
. PostgreSQL
li supporta da allora 8.4
.
Nelle versioni precedenti, puoi scrivere una funzione ricorsiva che restituisce set:
CREATE FUNCTION fn_hierarchy (parent INT)
RETURNS SETOF example
AS
$$
SELECT example
FROM example
WHERE id = $1
UNION ALL
SELECT fn_hierarchy(id)
FROM example
WHERE parentid = $1
$$
LANGUAGE 'sql';
SELECT *
FROM fn_hierarchy(1)
Vedi questo articolo:
Se utilizzi SQL Server 2005, esiste un modo davvero interessante per farlo utilizzando le espressioni di tabella comuni.
Elimina tutta la fatica dalla creazione di una tabella temporanea e in pratica ti consente di fare tutto solo con WITH e UNION.
Ecco un buon tutorial:
http://searchwindevelopment.techtarget.com/tip/0,289483,sid8_gci1278207,00.html
usare un espressione di tabella comune.
Potrebbe voler indicare che si tratta solo di SQL Server 2005 o versione successiva. Dale Ragan
ecco un articolo sulla ricorsione da parte di SqlTeam senza espressioni di tabella comuni.
Il seguente codice viene compilato ed è testato correttamente.
create or replace function subtree (bigint) returns setof example as $$ declare results record; entry record; recs record; begin select into results * from example where parent = $1; if found then for entry in select child from example where parent = $1 and child parent loop for recs in select * from subtree(entry.child) loop return next recs; end loop; end loop; end if; return next results; end; $$ language 'plpgsql';
La condizione "figlio <> genitore" è necessaria nel mio caso perché i nodi puntano a se stessi.
Divertiti :)
Oracle ha "INIZIA CON" e "COLLEGA DA"
select
lpad(' ',2*(level-1)) || to_char(child) s
from
test_connect_by
start with parent is null
connect by prior child = parent;
Solo per una breve parentesi, sebbene alla domanda sia stata data una risposta molto buona, va notato che se consideriamo questo come un:
domanda SQL generica
quindi l'implementazione SQL è abbastanza semplice, poiché SQL'99 consente la ricorsione lineare nelle specifiche (anche se credo che nessun RDBMS implementi completamente lo standard) attraverso WITH RECURSIVE
dichiarazione.Quindi da un punto di vista teorico possiamo farlo proprio ora.
Nessuno degli esempi ha funzionato bene per me, quindi l'ho risolto in questo modo:
declare results record; entry record; recs record; begin for results in select * from project where pid = $1 loop return next results; for recs in select * from project_subtree(results.id) loop return next recs; end loop; end loop; return; end;
è questo SQL Server?Non potresti scrivere una procedura memorizzata TSQL che esegua il loop e unisca i risultati insieme?
Sono anche interessato se esiste un modo solo SQL per farlo.Da quello che ricordo dal mio corso sui database geografici, dovrebbe esserci.
Penso che sia più semplice in SQL 2008 con ID gerarchia
Se hai bisogno di archiviare grafici arbitrari, non solo gerarchie, potresti mettere Postgres da parte e provare un database di grafici come AllegroGrafico:
Tutto nel database del grafico è archiviato come un triplo (nodo di origine, bordo, nodo di destinazione) e offre un supporto di prima classe per manipolare la struttura del grafico e interrogarla utilizzando un linguaggio simile a SQL.
Non si integra bene con qualcosa come Hibernate o Django ORM, ma se prendi sul serio le strutture dei grafici (non solo le gerarchie come ti offre il modello Nested Set), dai un'occhiata.
Credo anche che Oracle abbia finalmente aggiunto il supporto per i grafici reali nei suoi ultimi prodotti, ma sono stupito che ci sia voluto così tanto tempo e che molti problemi potrebbero trarre vantaggio da questo modello.