SQL WHERE ID IN (id1, id2, ..., idn)


170

Devo scrivere una query per recuperare un grande elenco di ID.

Supportiamo molti backend (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ...), quindi ho bisogno di scrivere un SQL standard.

La dimensione del set di ID potrebbe essere grande, la query verrebbe generata a livello di codice. Quindi, qual è l'approccio migliore?

1) Scrivere una query usando IN

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

La mia domanda qui è. Cosa succede se n è molto grande? Inoltre, per quanto riguarda le prestazioni?

2) Scrivere una query usando OR

SELECT * FROM TABLE WHERE ID = id1 OR ID = id2 OR ... OR ID = idn

Penso che questo approccio non abbia n limiti, ma che dire delle prestazioni se n è molto grande?

3) Scrivere una soluzione programmatica:

  foreach (var id in myIdList)
  {
      var item = GetItemByQuery("SELECT * FROM TABLE WHERE ID = " + id);
      myObjectList.Add(item);
  }

Abbiamo riscontrato alcuni problemi con questo approccio quando il server di database viene interrogato sulla rete. Normalmente è meglio fare una query che recuperi tutti i risultati invece di fare molte piccole query. Forse sto sbagliando.

Quale sarebbe una soluzione corretta per questo problema?


1
L'opzione 1 riduce significativamente i tempi di risposta del server SQL, selezionando ID 7k, di cui alcuni non esistevano. Normalmente la query ha richiesto circa 1300ms, si riduce a 80ms usando IN! Ho fatto il mio come soluzione 1 + 3. Solo la query finale è stata una, lunga stringa di query inviata a SQL per l'esecuzione.
Piotr Kula,

Risposte:


108

L'opzione 1 è l'unica buona soluzione.

Perché?

  • L'opzione 2 fa lo stesso ma ripeti il ​​nome della colonna molte volte; inoltre il motore SQL non sa immediatamente che si desidera verificare se il valore è uno dei valori in un elenco fisso. Tuttavia, un buon motore SQL potrebbe ottimizzarlo per avere le stesse prestazioni come con IN. C'è ancora il problema di leggibilità però ...

  • L'opzione 3 è semplicemente orribile dal punto di vista delle prestazioni. Invia una query ad ogni ciclo e martella il database con piccole query. Gli impedisce inoltre di utilizzare qualsiasi ottimizzazione per "il valore è uno di quelli in un determinato elenco"


2
Sono d'accordo, ma noto che l'elenco in è limitato in molti RDMS e quindi avresti bisogno che usiamo la soluzione di @Ed Guiness ma qui le tabelle temporanee differiscono tra RDBMS. (In effetti per problemi complessi non è possibile utilizzare solo SQL standard puro)
mmmmmm

28

Un approccio alternativo potrebbe essere quello di utilizzare un'altra tabella per contenere i valori ID. Quest'altra tabella può quindi essere unita internamente sulla TABELLA per vincolare le righe restituite. Ciò avrà il vantaggio principale che non avrai bisogno di SQL dinamico (problematico nella migliore delle ipotesi) e non avrai una clausola IN infinitamente lunga.

Dovresti troncare questa altra tabella, inserire il tuo gran numero di righe, quindi forse creare un indice per aiutare le prestazioni del join. Ti permetterebbe anche di staccare l'accumulo di queste righe dal recupero dei dati, dandoti forse più opzioni per ottimizzare le prestazioni.

Aggiornamento : sebbene sia possibile utilizzare una tabella temporanea, non intendevo implicare che si debba o addirittura si debba. Una tabella permanente utilizzata per i dati temporanei è una soluzione comune con meriti oltre a quelli descritti qui.


1
Ma come passeresti all'elenco degli ID di cui hai bisogno? (Vedendo che non puoi selezionare un intervallo o qualcosa del genere).
raam86,

1
@ raam86: l'elenco di ID potrebbe essere stato ottenuto usando selectun'istruzione su un'altra tabella. L'elenco viene passato come l'altra tabella in cui ti trovi inner join.
bdforbes,

19

Ciò che Ed Guiness ha suggerito è davvero un potenziatore delle prestazioni, ho avuto una domanda come questa

select * from table where id in (id1,id2.........long list)

cosa ho fatto :

DECLARE @temp table(
            ID  int
            )
insert into @temp 
select * from dbo.fnSplitter('#idlist#')

Quindi inner si è unito alla temp con la tabella principale:

select * from table inner join temp on temp.id = table.id

E le prestazioni sono migliorate drasticamente.


1
Ciao, fnSplitter è una funzione di MSSQL? Perché non sono riuscito a trovarlo.
WiiMaxx,

Non è una cosa standard. Devono significare che hanno scritto quella funzione per questo scopo, o per esempio avevano un'applicazione che l'aveva già fornita.
underscore_d

fnSplitter è una funzione creata da Ritu, puoi trovarla su internet / google simile
Bashar Abu Shamaa,

9

La prima opzione è sicuramente l'opzione migliore.

SELECT * FROM TABLE WHERE ID IN (id1, id2, ..., idn)

Tuttavia, considerando che l'elenco di ID è molto grande , diciamo milioni, dovresti considerare le dimensioni dei blocchi come di seguito:

  • Dividi il tuo elenco di ID in blocchi di numero fisso, ad esempio 100
  • La dimensione del blocco deve essere decisa in base alla dimensione della memoria del server
  • Supponiamo di avere 10000 ID, avrai 10000/100 = 100 blocchi
  • Elaborare un blocco alla volta con il risultato di 100 chiamate al database per la selezione

Perché dovresti dividere in pezzi?

Non otterrai mai un'eccezione di overflow della memoria, che è molto comune in scenari come il tuo. Avrai un numero ottimizzato di chiamate al database con prestazioni migliori.

Ha sempre funzionato come un incanto per me. Spero che funzionerebbe anche per i miei colleghi sviluppatori :)


4

Eseguendo il comando SELECT * FROM MyTable dove id in () su una tabella SQL di Azure con 500 milioni di record si è verificato un tempo di attesa di> 7 minuti!

In questo modo invece si ottengono immediatamente risultati:

select b.id, a.* from MyTable a
join (values (250000), (2500001), (2600000)) as b(id)
ON a.id = b.id

Usa un join.


3

Nella maggior parte dei sistemi di database, IN (val1, val2, …)e una serie di ORsono ottimizzati per lo stesso piano.

Il terzo modo sarebbe importare l'elenco di valori in una tabella temporanea e unirlo ad esso, che è più efficiente nella maggior parte dei sistemi, se ci sono molti valori.

Potresti voler leggere questo articolo:


3

L'esempio 3 sarebbe il peggiore in assoluto perché tutti colpiscono il database innumerevoli volte senza una ragione apparente.

Caricare i dati in una tabella temporanea e unirli a quello sarebbe di gran lunga il più veloce. Dopodiché IN dovrebbe funzionare leggermente più velocemente rispetto al gruppo di OR.


2

Penso che intendi SqlServer ma su Oracle hai un limite assoluto al numero di elementi IN che puoi specificare: 1000.


1
Anche SQL Server smette di funzionare dopo ~ 40k IN elementi. Secondo MSDN: Includere un numero estremamente elevato di valori (molte migliaia) in una clausola IN può consumare risorse e restituire errori 8623 o 8632. Per aggirare questo problema, archiviare gli elementi nell'elenco IN in una tabella.
Jahav,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.