5

Ho 3 tabelle, con Tabella B & C tabella di riferimento A tramite chiave esterna. Voglio scrivere una query in PostgreSQL per ottenere tutti gli ID di A e anche le loro occorrenze totali da B & C.Calcolo del numero di chiavi esterne da più tabelle

a  |  b  |  c 
-----------------------------------  
id | txt | id | a_id | id | a_id 
---+---- | ---+----- | ---+------ 
1 | a | 1 | 1 | 1 | 3  
2 | b | 2 | 1 | 2 | 4  
3 | c | 3 | 3 | 3 | 4  
4 | d | 4 | 4 | 4 | 4  

uscita desiderato (solo l'id da A conteggio totale & in B & C):

id | Count 
---+------- 
1 | 2  -- twice in B 
2 | 0  -- occurs nowhere 
3 | 2  -- once in B & once in C 
4 | 4  -- once in B & thrice in C 

SQL finora SQL Fiddle:

SELECT a_id, COUNT(a_id) 
FROM 
(SELECT a_id FROM b 
    UNION ALL 
    SELECT a_id FROM c 
) AS union_table 
GROUP BY a_id 

La query che ho scritto recupera da B & C e conta le occorrenze. Ma se la chiave non si verifica in B o C, non viene visualizzata nell'output (ad es. Id = 2 in output). Come posso iniziare la mia selezione dalla tabella A & join/unione B & C per ottenere il risultato desiderato

risposta

4

Se la query riguarda grandi parti di b e/o c è più efficiente aggregare prima e iscriversi in seguito.
mi aspetto queste due varianti di essere molto più veloce:

SELECT a.id, 
     ,COALESCE(b.ct, 0) + COALESCE(c.ct, 0) AS bc_ct 
FROM a 
LEFT JOIN (SELECT a_id, count(*) AS ct FROM b GROUP BY 1) b USING (a_id) 
LEFT JOIN (SELECT a_id, count(*) AS ct FROM c GROUP BY 1) c USING (a_id); 

, è necessario tenere conto della possibilità che alcuni a_id non sono presenti a tutti in a e/o b. count() non restituisce mai NULL, ma questo è il comfort a freddo di LEFT JOIN, che tuttavia ti lascia con valori NULL per le righe mancanti. È deve prepararsi per NULL. Utilizzare COALESCE().

O UNION ALL a_id da entrambe le tabelle, aggregare, poi registrazione:

SELECT a.id 
     ,COALESCE(ct.bc_ct, 0) AS bc_ct 
FROM a 
LEFT JOIN (
    SELECT a_id, count(*) AS bc_ct 
    FROM (
     SELECT a_id FROM b 
     UNION ALL 
     SELECT a_id FROM c 
    ) bc 
    GROUP BY 1 
    ) ct USING (a_id); 

Probabilmente più lento. Ma ancora più veloce delle soluzioni presentate finora. E si potrebbe fare senza COALESCE() e ancora non perdere le righe. In questo caso potresti ottenere occasionalmente i valori NULL per bc_ct.

+0

Perché dici che la tua seconda opzione è "probabilmente più lenta"? Ha meno JOIN, non dovrebbe essere più veloce? – click

+0

@click Non sono un esperto di PostgreSQL (lontano da esso), ma probabilmente utilizzando una funzione di aggregazione come 'GROUP BY' sarà effettivamente più lento. Ma pensandoci meglio, non capisco perché la prima domanda di Erwin dovrebbe essere più veloce della mia risposta (credo che entrambi avrebbero piani di esecuzione simili in database come Oracle e SQL Server), ma * suppongo * che lui sappia cosa sta parlando. – rsenna

+0

@click: il numero di join è solo uno dei tanti fattori. Tutto dipende dalle cardinalità e dalla distribuzione dei dati (tra gli altri). Ecco perché parlo di casi e probabilità tipici. Devi verificare con il tuo scenario * attuale * per sapere con certezza. La prima query tipicamente colpisce un punto debole tra semplicità ed efficacia. –

2

Usa ha lasciato unirsi con una subquery:

SELECT a.id, COUNT(x.id) 
FROM a 
LEFT JOIN (
    SELECT id, a_id FROM b 
    UNION ALL 
    SELECT id, a_id FROM c 
) x ON (a.id = x.a_id) 
GROUP BY a.id; 
+0

Vorrei poter accettare entrambe le risposte. Quando avrò più dati, verrò a rivisitare questo per scoprire quale funziona meglio, ma sembra che l'altro eviti JOIN e potrebbe essere più veloce. – click

+0

Esiste anche una terza soluzione possibile. – wildplasser

3

Un'altra opzione:

SELECT 
    a.id, 
    (SELECT COUNT(*) FROM b WHERE b.a_id = a.id) + 
    (SELECT COUNT(*) FROM c WHERE c.a_id = a.id) 
FROM 
    a 
+0

Questo è pulito. Non sapevo che puoi selezionare delle istruzioni all'interno di select e aggiungerle semplicemente. – click

+3

@click: per più di una manciata di valori 'a_id', ** [sottoquery correlate] (http://en.wikipedia.org/wiki/Correlated_subquery) ** (questo è il termine) sono tipicamente * più lenti * di alternative soluzioni. Postgres deve eseguire una sottoquery per il valore 'a_id'. Assicurati di testare con un set di dati realistico. –

+0

Modificata la risposta accettata a una da @ErwinBrandstetter per le prestazioni. Ma per tavoli più piccoli, questo vince per compattezza. Lo visiterò di nuovo quando avrò più dati da confrontare. – click

Problemi correlati