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

170

Necesito escribir una consulta para recuperar una gran lista de identificadores.

Admitimos muchos backends (MySQL, Firebird, SQLServer, Oracle, PostgreSQL ...), así que necesito escribir un SQL estándar.

El tamaño del conjunto de identificación podría ser grande, la consulta se generaría mediante programación. Entonces, ¿cuál es el mejor enfoque?

1) Escribir una consulta usando IN

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

Mi pregunta aquí es. ¿Qué pasa si n es muy grande? Además, ¿qué pasa con el rendimiento?

2) Escribir una consulta usando OR

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

Creo que este enfoque no tiene límite n, pero ¿qué pasa con el rendimiento si n es muy grande?

3) Escribir una solución programática:

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

Experimentamos algunos problemas con este enfoque cuando se consulta el servidor de la base de datos a través de la red. Normalmente es mejor hacer una consulta que recupere todos los resultados en lugar de hacer muchas consultas pequeñas. Puede ser que esté equivocado.

¿Cuál sería una solución correcta para este problema?

Daniel Peñalba
fuente
1
La opción 1 reduce significativamente el tiempo de respuesta del servidor SQL, seleccionando 7k ID, de los cuales algunos no existían. Normalmente la consulta tomó alrededor de 1300 ms, ¡se reduce a 80 ms usando IN! Hice la mía como su solución 1 + 3. Solo la consulta final fue una, cadena de consulta larga enviada a SQL para ejecutar.
Piotr Kula

Respuestas:

108

La opción 1 es la única buena solución.

¿Por qué?

  • La opción 2 hace lo mismo pero repite el nombre de la columna muchas veces; Además, el motor SQL no sabe de inmediato que desea comprobar si el valor es uno de los valores de una lista fija. Sin embargo, un buen motor SQL podría optimizarlo para tener el mismo rendimiento que con IN. Sin embargo, todavía hay un problema de legibilidad ...

  • La opción 3 es simplemente horrible en cuanto al rendimiento. Envía una consulta cada bucle y martilla la base de datos con pequeñas consultas. También evita que use optimizaciones para "el valor es uno de esos en una lista dada"

ThiefMaster
fuente
2
Estoy de acuerdo, pero tenga en cuenta que la lista de entrada está limitada en muchos RDMS, por lo que necesitará que usemos la solución de @Ed Guiness, pero aquí las tablas temporales difieren entre los RDBMS. (Efectivamente para problemas complejos no puede usar solo SQL estándar puro)
mmmmmm
28

Un enfoque alternativo podría ser utilizar otra tabla para contener valores de identificación. Esta otra tabla se puede unir internamente en su TABLE para restringir las filas devueltas. Esto tendrá la gran ventaja de que no necesitará SQL dinámico (problemático en el mejor de los casos), y no tendrá una cláusula IN infinitamente larga.

Truncaría esta otra tabla, insertaría su gran número de filas y luego crearía un índice para ayudar al rendimiento de la unión. También le permitiría separar la acumulación de estas filas de la recuperación de datos, quizás ofreciéndole más opciones para ajustar el rendimiento.

Actualización : aunque podría usar una tabla temporal, no quise decir que debiera o incluso debería. Una tabla permanente utilizada para datos temporales es una solución común con méritos más allá de lo descrito aquí.

Ed Guiness
fuente
1
Pero, ¿cómo pasarías la lista de identificadores que necesitas? (Al ver que no puede seleccionar un rango o algo así).
raam86
1
@ raam86: la lista de ID podría haberse obtenido utilizando una selectdeclaración en otra tabla. La lista se pasa como la otra tabla inner joincontra la que se está enfrentando.
bdforbes
19

Lo que sugirió Ed Guiness es realmente un refuerzo de rendimiento, tuve una consulta como esta

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

lo que hice :

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

Luego interior se unió a la temperatura con la mesa principal:

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

Y el rendimiento mejoró drásticamente.

Ritu
fuente
1
Hola, ¿fnSplitter es una función de MSSQL? Porque no pude encontrarlo.
WiiMaxx
No es una cosa estándar. Deben significar que escribieron esa función para este propósito, o por ejemplo, tenían una aplicación que ya la proporcionaba.
underscore_d
fnSplitter es una función creada por Ritu, que puede encontrar en internet / google similar
Bashar Abu Shamaa
9

La primera opción es definitivamente la mejor opción.

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

Sin embargo, teniendo en cuenta que la lista de identificadores es muy grande , digamos millones, debe considerar los tamaños de fragmentos como a continuación:

  • Divida su lista de ID en trozos de número fijo, digamos 100
  • El tamaño del fragmento debe decidirse en función del tamaño de la memoria de su servidor
  • Supongamos que tiene 10000 Ids, tendrá 10000/100 = 100 trozos
  • Procese un fragmento a la vez dando como resultado 100 llamadas a la base de datos para seleccionar

¿Por qué deberías dividirte en trozos?

Nunca obtendrá una excepción de desbordamiento de memoria, que es muy común en escenarios como el suyo. Habrás optimizado el número de llamadas a la base de datos, lo que dará como resultado un mejor rendimiento.

Siempre ha funcionado a las mil maravillas para mí. Espero que funcione para mis compañeros desarrolladores también :)

Adarsh ​​Kumar
fuente
4

¡Hacer el comando SELECT * FROM MyTable where id in () en una tabla Azure SQL con 500 millones de registros resultó en un tiempo de espera de> 7min!

Hacer esto en su lugar devolvió los resultados de inmediato:

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

Usa una unión.

JakeJ
fuente
3

En la mayoría de los sistemas de bases de datos, IN (val1, val2, …)y una serie de ORestán optimizados para el mismo plan.

La tercera forma sería importar la lista de valores en una tabla temporal y unirla, que es más eficiente en la mayoría de los sistemas, si hay muchos valores.

Es posible que desee leer estos artículos:

Quassnoi
fuente
3

El ejemplo 3 sería el peor de todos porque está accediendo a la base de datos innumerables veces sin razón aparente.

Cargar los datos en una tabla temporal y luego unirse a ellos sería, con mucho, el más rápido. Después de eso, el IN debería funcionar un poco más rápido que el grupo de OR.

judda
fuente
2

Creo que te refieres a SqlServer, pero en Oracle tienes un límite estricto de cuántos elementos IN puedes especificar: 1000.

flq
fuente
1
Incluso SQL Server deja de funcionar después de ~ 40k elementos IN. Según MSDN: la inclusión de una cantidad extremadamente grande de valores (muchos miles) en una cláusula IN puede consumir recursos y devolver los errores 8623 u 8632. Para solucionar este problema, almacene los elementos en la lista IN en una tabla.
jahav