Hai tre livelli di tabelle nidificate.
Dati di esempio:
CREATE TABLE a(
a_id integer primary key,
name text
);
CREATE TABLE b(
b_id integer primary key,
a_id integer references a(a_id),
val text
);
CREATE TABLE c(
c_id serial primary key,
b_id integer references b(b_id),
blah text
);
INSERT INTO a(a_id, name) VALUES (1, 'fred'),(2, 'bert');
INSERT INTO b(b_id, a_id, val) VALUES
(11, 1, 'x'), (12, 1, 'y'), (21, 2, 'a'), (22, 2, 'b');
INSERT INTO c(b_id, blah) VALUES
(11, 'whatever'), (11, 'gah'), (12, 'borkbork'), (22, 'fuzz');
Metodo 1:esegui un join sinistro, gestisci XML nel client
Il modo più semplice per gestirlo è eseguire un join sinistro su tutte e tre le tabelle, ordinate dalla più esterna alla più interna. Quindi ripeti il set di risultati, chiudendo un elemento e aprendone un altro ogni volta che il soggetto a quel livello cambia.
select *
from a left join b on (a.a_id = b.a_id)
left join c on (b.b_id = c.b_id)
order by a.a_id, b.b_id, c.c_id;
quindi scorrere le righe restituite e, per ogni riga, pseudocodice :
cur_row = get_new_row()
if (cur_row[b_id] != prev_row[b_id]) {
emit_close_tableb();
}
if (cur_row[a_id] != prev_row[a_id]) {
emit_close_tablea();
emit_open_tablea(cur_row);
}
if (cur_row[b_id] != prev_row[b_id]) {
emit_open_tableb(cur_row);
}
emit_tablec(cur_row);
prev_row = cur_row;
Per scrivere l'XML useresti qualcosa come XMLWriter
. Per leggere i dati della query puoi usare qualcosa come PDO o qualsiasi driver tu preferisca. Se il set di dati è grande, considera l'utilizzo di un cursore per leggere i dati.
Funziona bene, ma trasferisce un lotto di dati in eccesso, poiché trasferisci n
copie dei dati della tabella esterna per ogni n
righe della tabella interna ad essa associata.
Per ridurre l'eccesso di dati scambiati puoi selezionare solo gli ID per le tabelle esterne
select a.a_id, b.b_id, c.*
from a left join b on (a.a_id = b.a_id)
left join c on (b.b_id = c.b_id)
order by a.a_id, b.b_id, c.c_id;
... quindi quando passi a un nuovo tablea / tableb, SELECT
il resto delle sue righe quindi. Probabilmente utilizzerai una seconda connessione per farlo in modo da non interrompere il set di risultati e lo stato del cursore sulla connessione principale da cui stai leggendo le righe.
Metodo 2:fai tutto in PostgreSQL
Per set di dati più piccoli, o per i livelli interni di set di dati più grandi, puoi utilizzare il supporto XML di PostgreSQL per costruire i documenti XML, ad esempio:
WITH xmlinput AS (
SELECT a, b, c
FROM a
LEFT JOIN b ON (a.a_id = b.a_id)
LEFT JOIN c on (b.b_id = c.b_id)
ORDER BY a.a_id, b.b_id, c.c_id
)
SELECT
XMLELEMENT(name items,
xmlagg(
XMLELEMENT(name a,
XMLFOREST((a).a_id AS a_id, (a)."name" AS name),
b_xml
)
ORDER BY (a).a_id)
) AS output
FROM
(
SELECT
a,
xmlagg(
XMLELEMENT(name b,
XMLFOREST((b).b_id AS b_id, (b).val AS val),
c_xml
)
ORDER BY (b).b_id)
AS b_xml
FROM
(
SELECT
a, b,
xmlagg(
XMLELEMENT(name c,
XMLFOREST((c).c_id AS c_id, (c).blah AS blah)
)
ORDER BY (c).c_id)
AS c_xml
FROM xmlinput
GROUP BY a, b
) c_as_xml
GROUP BY a
) b_as_xml;
... ma in realtà, devi essere una specie di masochista per scrivere codice del genere. Anche se potrebbe rivelarsi abbastanza veloce.
Per comprendere la query dovrai leggere i documenti XML di PostgreSQL . La stravagante sintassi è stata escogitata dal comitato SQL/XML, non biasimarci.
Tieni inoltre presente che variabili di riga sono usati pesantemente nel codice sopra per mantenerlo organizzato. a
, b
e c
vengono passati come righe intere ai livelli esterni della query. Ciò evita la necessità di pasticciare con gli alias quando i nomi entrano in conflitto. La sintassi (a).a_id
, ecc, significa "il a_id
campo della variabile di riga a
". Consulta il manuale di PostgreSQL per i dettagli.
Quanto sopra utilizza una migliore struttura XML (vedi commenti sotto). Se vuoi emettere attributi e non elementi, puoi cambiare XMLFOREST
chiamate a XMLATTRIBUTES
chiamate.
Uscita:
<items><a><a_id>1</a_id><name>fred</name><b><b_id>11</b_id><val>x</val><c><c_id>1</c_id><blah>whatever</blah></c><c><c_id>2</c_id><blah>gah</blah></c></b><b><b_id>12</b_id><val>y</val><c><c_id>3</c_id><blah>borkbork</blah></c></b></a><a><a_id>2</a_id><name>bert</name><b><b_id>21</b_id><val>a</val><c/></b><b><b_id>22</b_id><val>b</val><c><c_id>4</c_id><blah>fuzz</blah></c></b></a></items>
o, con una bella stampa:
<?xml version="1.0" encoding="utf-16"?>
<items>
<a>
<a_id>1</a_id>
<name>fred</name>
<b>
<b_id>11</b_id>
<val>x</val>
<c>
<c_id>1</c_id>
<blah>whatever</blah>
</c>
<c>
<c_id>2</c_id>
<blah>gah</blah>
</c>
</b>
<b>
<b_id>12</b_id>
<val>y</val>
<c>
<c_id>3</c_id>
<blah>borkbork</blah>
</c>
</b>
</a>
<a>
<a_id>2</a_id>
<name>bert</name>
<b>
<b_id>21</b_id>
<val>a</val>
<c />
</b>
<b>
<b_id>22</b_id>
<val>b</val>
<c>
<c_id>4</c_id>
<blah>fuzz</blah>
</c>
</b>
</a>
</items>
Emetti un XML migliore
In una nota a margine, l'utilizzo di attributi come quello in XML sembra allettante, ma diventa rapidamente difficile e brutto lavorarci. Si prega di utilizzare i normali elementi XML:
<Table 1>
<Nr>1</Nr>
<Name>blah</Name>
<Table 2>
<Nr>1</Nr>
<Table 3>
<Col1>42</Col1>
<Col2>...</Col2>
<Col3>...</Col3>
<Col4>...</Col4>
...
</Table 3>
</Table 2>
</Table 1>