SQL Server Error 8632 debido a más de 100,000 entradas en la cláusula WHERE


16

Mi problema (o al menos el mensaje de error) es muy similar al del procesador de consultas que se quedó sin recursos internos: consulta SQL extremadamente larga .

Mi cliente está trabajando con una consulta de selección SQL, que contiene una cláusula where con exactamente 100,000 entradas.

La consulta falla con el error 8632 y el mensaje de error

Error interno: se ha alcanzado un límite de servicios de expresión. Busque expresiones potencialmente complejas en su consulta e intente simplificarlas).

Me resulta muy peculiar que este mensaje de error se arroje, exactamente a 100,000 entradas, así que me pregunto si este es un valor configurable. ¿Es este el caso y, en caso afirmativo, cómo puedo aumentar este valor a uno más alto?

En MSDN , existe la propuesta de reescribir la consulta, pero me gustaría evitar esto.

Mientras tanto, descubrí que la lista de entradas de la que estoy hablando contiene números naturales, algunos de ellos parecen ser secuenciales (algo así como (1,2,3,6,7,8,9,10,12, 13,15,16,17,18,19,20).

Esto hace que la cláusula where de SQL sea algo así como:

where entry in (1,2,3,6,7,8,9,10,12,13,15,16,17,18,19,20)

Podría transformar esto en:

where (entry between 1 and 3) OR
      (entry between 6 and 10) OR
      (entry between 12 and 13) OR
      (entry between 15 and 20)

¿Puede esto ser acortado por:

where entry in (1,...,3,6,...,10,12,13,15,...,20)

...¿o algo similar? (Sé que es una posibilidad remota, pero haría las actualizaciones de software más fáciles y más legibles)

Para su información: los datos en la cláusula where son el resultado de un cálculo, realizado en otra tabla: primero las entradas de esa tabla se leen y se filtran al principio, luego se realiza un procesamiento adicional (que es imposible hacer usando SQL), el resultado de ese procesamiento adicional es más filtrado y el resultado se usa en la cláusula where. Como era imposible escribir el filtrado completo en SQL, se ha utilizado el método mencionado. Obviamente, el contenido de la cláusula where podría cambiar en cada procesamiento, de ahí la necesidad de una solución dinámica.


77
En respuesta a su edición: no, WHERE INno admite ese tipo de sintaxis de rango. Además, no debería ser WHERE () OR () OR ()AND. Pero para usar la sugerencia de Brent, en realidad no tiene que cambiar toda la consulta, simplemente puede hacerlo WHERE IN (SELECT myID FROM #biglist). Y #biglistpodría ser una tabla real (permanente) o una tabla temporal que haga sobre la marcha.
BradC

Por favor, publique toda la consulta y lo que está calculando externamente, esto es probablemente algo que podría hacer en SQL por completo. Cambie el nombre de los campos si le preocupa la privacidad o lo que sea.
Mike

Respuestas:


67

Para buscar más de 100.000 valores, colóquelos en una tabla temporal, una fila por valor que esté buscando. Luego, une tu consulta a esa tabla temporal para filtrar.

Algo con más de 100,000 valores no es un parámetro, es una tabla. En lugar de pensar en aumentar el límite, considere la regla del diez por ciento de Swart : si se acerca al 10% del límite de SQL Server, probablemente lo pasará mal.


1
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Paul White reinstala a Monica

10

Si vas a cambiar la aplicación de todos modos, considera

(a) usando un TVP para todo el conjunto de valores: crearía un DataTableC # y lo pasaría a un procedimiento almacenado usando StructuredType, como lo demuestro aquí . (Esperemos que 100,000 entradas no sean normales, ya que la escalabilidad puede ser un problema sin importar el enfoque que use).

CREATE TYPE dbo.optionA AS TABLE(value int PRIMARY KEY);
GO

CREATE PROCEDURE dbo.procedure_optionA
  @t dbo.optionA READONLY
AS
BEGIN
  SET NOCOUNT ON;
  SELECT <cols> FROM dbo.<table> AS t
    INNER JOIN @t AS tvp
    ON t.entry = tvp.value;
END
GO

o

(b) usar un TVP para pasar en los límites superior e inferior de los rangos, y escribir una unión ligeramente diferente (gracias @ypercube).

  CREATE TYPE dbo.optionB AS TABLE
  (
    LowerBound int,
    UpperBound int,
    PRIMARY KEY (LowerBound, UpperBound)
  );
  GO

  CREATE PROCEDURE dbo.procedure_optionB
    @t dbo.OptionB READONLY
  AS
  BEGIN
    SET NOCOUNT ON;
    SELECT <cols> FROM dbo.<table> AS t
      INNER JOIN @t AS tvp
      ON  t.entry >= tvp.LowerBound 
      AND t.entry <= tvp.UpperBound;
  END
  GO


4

Solo mis 2 ¢ con respecto a acortar la condición de consulta: -

Si puede determinar todos los valores posibles de entryantemano, ¿sería factible si toma el complemento de su consulta?

antes de

where entry in (1,2,3,6,7,8,9,10,12,13,15,16,17,18,19,20)

Después

where entry not in (4,5,11,14)

0

Es difícil darse cuenta de lo que está tratando de lograr sin poder ver realmente la consulta, pero aquí vamos, suponiendo que su consulta solo necesita dos tablas, una con los datos y otra con los valores que desea evitar:

  • El uso where entry in (<list of 100.000 values>)es escandalosamente horrible, tanto desde el punto de vista de la eficiencia como del mantenimiento.
  • Usar where entry in (select whatever from table)es apenas tan atrozmente horrible como el anterior. Sin embargo, dependiendo de la idiosincrasia de ambas tablas y del motor SQL, podría funcionar bien a pesar del cáncer de córnea. (Puede estar bien en Oracle, nunca estará en MySQL, no puedo recordar acerca de MSSQL).
  • Usar PLSQL para lograr esto es simplemente horrendo.
  • En mi opinión (sin conocer la consulta), debe volver a escribir la consulta de la siguiente manera:

    • Si esos 100.000 valores son siempre los mismos, sin depender del resto de la consulta, debe cargar esos valores en una tabla (table_fixed_values) y usar

      SELECT * -- whatever you might need
      FROM
         table_a A
         LEFT JOIN table_fixed_values ON A.entry=B.entry
      WHERE B.entry IS NOT NULL
    • Si esos 100.000 valores no son los mismos, debe haber algún tipo de lógica para recoger esos 100.000 valores, lógica que debe incrustar en la ONconsulta anterior.


3
¿Por qué LEFT JOIN table_fixed_values ON A.entry=B.entry WHERE B.entry IS NOT NULLy no el equivalente, más simple y más fácil de leer INNER JOIN table_fixed_values ON A.entry=B.entry?
ypercubeᵀᴹ

@ ypercubeᵀᴹ completamente correcto, INNER JOIN tiene más sentido.
glezo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.