2012-07-04 15 views
5

Ho questa tabellasql più efficace per il recupero

---------------- 
| X | Y | 
---------------- 
| a | 1 | 
| c | 6 | 
| e | 3 | 
| d | 6 | 
| c | 4 | 
| b | 1 | 
| a | 5 | 
| g | 1 | 
---------------- 

Quando mi sono dato una matrice [c, d] Ho bisogno di trovare "6" nella tabella sopra riportata. Cioè per ogni insieme di elementi ho bisogno di trovare il valore Y che è condiviso da tutti gli elementi nel set ma solo se non c'è nessun altro elemento (cioè un elemento che non è nell'array dato) che condivide quel valore. Il numero di elementi nella matrice non ha limiti teorici.

Altri esempi: per [a, b, c] non ho bisogno di trovare nulla. Anche per [a, b] non ho trovato nulla (perché g ha anche una voce per Y = 1, quindi per [a, b, g] ho bisogno di trovare "1").

Ovviamente potrei scorrere l'array, interrogare per query, ma sembra un modo così inefficiente di farlo. Qual è il modo migliore per farlo in SQL? Grazie.

+1

[Cosa hai provato?] (Http://whathaveyoutried.com) Stai cercando di passare il tuo "array" nella query utilizzando un elenco IN? –

+0

Cosa RMDB stai usando? –

+0

Il meglio è soggettivo. Dipende dai dati che hai, dal database e dalla versione che usi e da quali indici sono disponibili. –

risposta

2

Ecco un modo con i valori di "query" in una tabella separata.

create table t (x varchar(1), y int); 

insert into t (x, y) values ('a', 1); 
insert into t (x, y) values ('c', 6); 
insert into t (x, y) values ('e', 3); 
insert into t (x, y) values ('d', 6); 
insert into t (x, y) values ('c', 4); 
insert into t (x, y) values ('b', 1); 
insert into t (x, y) values ('a', 5); 
insert into t (x, y) values ('g', 1); 

create table q (x varchar(1)); 

insert into q (x) values ('a'); 
insert into q (x) values ('b'); 

select a.y from 
(
    select t.y 
    from t join q on (t.x = q.x) 
    group by t.y 
    having count(*) = (select count(*) from q) 
) a 
join t on (a.y = t.y) 
group by a.y 
having count(*) = (select count(*) from q) 

Here's an example SQLFiddle.

Ciò presuppone che non è possibile avere combinazioni duplicate.

Se si desidera eseguire l'operazione senza la seconda tabella, è possibile sostituire lo select count(*) con il numero di valori a cui si sta facendo la corrispondenza in un elenco IN e invece di eseguire il join sulla sottoquery interna, utilizzare una clausola where.

select a.y from 
(
select t.y 
from t 
    where t.x in ('c', 'd') 
group by t.y 
having count(*) = 2 
) a 
join t on (a.y = t.y) 
group by a.y 
having count(*) = 2 
+0

È necessario un GROUP BY nelle query esterne. Ma bel pensiero laterale per farlo con un solo tavolo. Penso che l'approccio LEFT JOIN sarà meno intenso (un join e un gruppo per, invece di due join e due groups), ma le prestazioni saranno molto scarse in entrambi i casi quindi questo mi sembra buono! (+1) – MatBailie

+0

@Dems - Grazie. MySQL non ha bisogno del gruppo in quanto raggruppa implicitamente, ma probabilmente dovrebbe includerlo per facilità di leggibilità. –

+0

@NWest: Mi piace molto anche la tua seconda soluzione, che è astuta, sembra funzionare. Grazie! – Vincent

2

Questi tipi di interrogazione sono mai particolarmente performante su grandi insiemi di dati e/o in cui molti valori in azioni Y gli stessi valori in X.

Detto questo, questa è la semplice versione del mio modo normale di farlo ...

CREATE TEMPORARY TABLE params (
    item VARCHAR(16) 
) 
INSERT INTO params SELECT 'a' 
INSERT INTO params SELECT 'b' 
INSERT INTO params SELECT 'g' 


SELECT 
    yourTable.Y 
FROM 
    yourTable 
LEFT JOIN 
    params 
    ON yourTable.X = params.item 
GROUP BY 
    yourTable.Y 
HAVING 
    COUNT(DISTINCT yourTable.X) = COUNT(DISTINCT params.item) 


Un'altra opzione che non ha bisogno di una tabella di parametri, anche se non credo che sia più performante ...

SELECT 
    y 
FROM 
    yourTable 
GROUP BY 
    y 
HAVING 
    COUNT(DISTINCT x) = COUNT(DISTINCT CASE WHEN x IN ('a', 'b', 'g') THEN x ELSE NULL END) 

Questo non ha join, ma è a costo di eseguire una scansione completa della tabella.

+2

Ciò fornirebbe anche true per {a, b} count = 2. Il suo esempio con {a, b} (es. Senza g) non dovrebbe produrre nulla. (se ho capito bene) BTW: questo sembra sudeku ... – wildplasser

+0

@wildplasser - Buon posto, non ho letto quel bit *** oops ***. Aggiornato su LEFT JOIN su un tavolo e aggiornato HAVING per indicare che entrambe le parti devono avere lo stesso numero di elementi distinti. – MatBailie

+0

@Dems, questa soluzione è probabilmente buona anche grazie mille, ma sto usando symfony e DQL quindi mi piacerebbe evitare di creare tabelle param. E sicuramente non voglio una scansione completa della tabella. – Vincent

Problemi correlati