Capisco che le sottoquery sono notoriamente cattive per le prestazioni se utilizzate in modo errato. Ho uno scenario molto specifico in cui l'utente deve recuperare un set filtrato di record da una tabella. Sarà disponibile un'ampia varietà di filtri e dovranno supportare la composizione. Inoltre, nuovi filtri verranno creati su base regolare da un gruppo di sviluppatori.Subquery SQL nella clausola FROM
Non mi piace l'idea di una query SQL monolitica in crescita con un numero lordo di parametri. Non mi piace l'idea di un gruppo di query SQL autonome con istruzioni SELECT identiche e clausole WHERE variabili. Mi piace l'idea di una query SQL dinamica, ma non sono sicuro del tipo di struttura che dovrei usare. Posso pensare di 4 opzioni di base: (se ci sono più che mi manca, allora non esitate a suggerire loro)
- "INNER JOIN": concatenare filtri via inner join per filtrare i risultati.
- "FROM subqueries": raggruppa i filtri tramite subquery nell'istruzione FROM.
- "WHERE subquery": consente di filtrare i filtri tramite subquery nella clausola WHERE.
- "subquery JOIN INTERNO": un ibrido strano.
Ho creato un violino SQL per dimostrare (e profilo) li:
Il sotto è un estratto dal violino di fornire un'idea di quello che ho' m parlando:
------------------------------------------------------------------------
--THIS IS AN EXCERPT FROM THE SQL FIDDLE -- IT IS NOT MEANT TO COMPILE--
------------------------------------------------------------------------
--
--"INNER JOIN" test
SELECT COUNT(*)
FROM
@TestTable Test0
INNER JOIN @TestTable Test1 ON Test1.ID=Test0.ID AND Test1.ID % @i = 0
INNER JOIN @TestTable Test2 ON Test2.ID=Test0.ID AND Test2.ID % @j = 0
INNER JOIN @TestTable Test3 ON Test3.ID=Test0.ID AND Test3.ID % @k = 0
--
--"FROM subqueries" test
SELECT COUNT(*) FROM (
SELECT * FROM (
SELECT * FROM (
SELECT * FROM @TestTable Test3 WHERE Test3.ID % @k = 0
) Test2 WHERE Test2.ID % @j = 0
) Test1 WHERE Test1.ID % @i = 0
) Test0
--
--"WHERE subqueries" test
SELECT COUNT(*)
FROM @TestTable Test0
WHERE
Test0.ID IN (SELECT ID FROM @TestTable Test1 WHERE Test1.ID % @i = 0)
AND Test0.ID IN (SELECT ID FROM @TestTable Test2 WHERE Test2.ID % @j = 0)
AND Test0.ID IN (SELECT ID FROM @TestTable Test3 WHERE Test3.ID % @k = 0)
--
--"INNER JOIN subqueries" test
SELECT COUNT(*)
FROM
TestTable Test0
INNER JOIN (SELECT ID FROM TestTable WHERE ID % @i = 0) Test1 ON Test1.ID=Test0.ID
INNER JOIN (SELECT ID FROM TestTable WHERE ID % @j = 0) Test2 ON Test2.ID=Test0.ID
INNER JOIN (SELECT ID FROM TestTable WHERE ID % @k = 0) Test3 ON Test3.ID=Test0.ID
--
--"EXISTS subqueries" test
SELECT COUNT(*)
FROM TestTable Test0
WHERE
EXISTS (SELECT 1 FROM TestTable Test1 WHERE Test1.ID = Test0.ID AND Test1.ID % @i = 0)
AND EXISTS (SELECT 1 FROM TestTable Test2 WHERE Test2.ID = Test0.ID AND Test2.ID % @j = 0)
AND EXISTS (SELECT 1 FROM TestTable Test3 WHERE Test3.ID = Test0.ID AND Test3.ID % @k = 0)
Classifica (tempo per eseguire i test)
SQL Fiddle:
|INNER JOIN|FROM SUBQUERIES|WHERE SUBQUERIES|INNER JOIN SUBQUERIES|EXISTS SUBQUERIES|
-------------------------------------------------------------------------------------
| 5174 | 777 | 7240 | 5478 | 7359 |
Ambiente locale: (senza cache: compensazione del buffer prima di ogni prova)
|INNER JOIN|FROM SUBQUERIES|WHERE SUBQUERIES|INNER JOIN SUBQUERIES|EXISTS SUBQUERIES|
-------------------------------------------------------------------------------------
| 3281 | 2851 | 2964 | 3148 | 3071 |
Ambiente locale: (con cache di: esecuzione di query due volte di fila e registrare il tempo della 2a corsa)
|INNER JOIN|FROM SUBQUERIES|WHERE SUBQUERIES|INNER JOIN SUBQUERIES|EXISTS SUBQUERIES|
-------------------------------------------------------------------------------------
| 284 | 50 | 3334 | 278 | 408 |
Ci sono vantaggi/svantaggi con ciascuna soluzione. Le sottoquery nella clausola WHERE hanno prestazioni piuttosto terribili. Le subquery nella clausola FROM hanno prestazioni piuttosto buone (in realtà di solito danno il meglio) (NOTA: credo che questo metodo possa negare i vantaggi degli indici?). Gli INNER JOIN hanno una performance piuttosto buona, sebbene introduca alcuni problemi di scoping interessanti perché a differenza delle sottoquery, i JOIN INNER funzionerebbero nello stesso contesto (ci dovrebbe essere un sistema intermedio per evitare la collisione degli alias di tabella).
Nel complesso, penso che la soluzione più pulita sia le subquery nella clausola FROM. I filtri sarebbero facili da scrivere e testare (perché a differenza degli INNER JOINs non avrebbero bisogno di essere forniti con la query context/base).
Pensieri? Si tratta di un uso valido di subquery o di un disastro in attesa di accadere?
UPDATE (2012/10/04):
- Aggiornato SQL Fiddle per includere un test per il metodo "esiste"
- Aggiunto test di prestazioni da SQL Fiddle e ambiente locale
test aggiunti per il metodo "esiste". Si posiziona vicino alla coda del pacchetto, anche se sono stato sorpreso di vedere alcune differenze tra "SELECT *" e "SELECT 1" (certamente non i test più controllati, ma c'erano alcune variazioni definite). La metodologia generale verrà applicata a una varietà di tabelle, quindi le dimensioni e gli indici sono difficili da prevedere in anticipo. Sto fondamentalmente cercando la "sandbox più amichevole". – makerplays