Erro 8632 do SQL Server devido a mais de 100.000 entradas na cláusula WHERE

16

Meu problema (ou pelo menos a mensagem de erro) é muito semelhante ao processador de consultas que ficou sem recursos internos - consulta sql extremamente longa .

Meu cliente está trabalhando com uma consulta de seleção SQL, contendo uma cláusula where com exatamente 100.000 entradas.

A consulta está falhando com o erro 8632 e a mensagem de erro

Erro interno: um limite de serviços de expressão foi atingido. Procure expressões potencialmente complexas em sua consulta e tente simplificá-las.)

Acho muito peculiar que essa mensagem de erro seja lançada, exatamente com 100.000 entradas, então, me pergunto se esse é um valor configurável. É esse o caso e, no caso afirmativo, como posso aumentar esse valor para um valor mais alto?

No MSDN , existe a proposta de reescrever a consulta, mas eu gostaria de evitar isso.

Enquanto isso, descobri que a lista de entradas de que estou falando contém números naturais, alguns deles parecem ser seqüenciais (algo como (1,2,3,6,7,8,9,10,12, 13,15,16,17,18,19,20).

Isso torna a cláusula where do SQL algo como:

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

Eu poderia transformar isso em:

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

Isso pode ser reduzido por:

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

... ou algo parecido? (Eu sei que é um tiro no escuro, mas isso tornaria as atualizações de software mais fáceis e legíveis)

Para sua informação: os dados na cláusula where são o resultado de um cálculo, feito em outra tabela: primeiro, as entradas dessa tabela são lidas e filtradas no início e, em seguida, algum processamento extra é feito (o que é impossível fazer usando SQL), o resultado desse processamento extra é mais filtrado e o resultado disso é usado na cláusula where. Como era impossível gravar a filtragem completa no SQL, o método mencionado foi usado. Obviamente, o conteúdo da cláusula where pode mudar a cada processamento, daí a necessidade de uma solução dinâmica.

Dominique
fonte
7
Em resposta à sua edição: não, WHERE INnão suporta esse tipo de sintaxe de intervalo. Além disso, não deve ser WHERE () OR () OR ()AND. Mas, para usar a sugestão de Brent, na verdade você não precisa alterar a consulta inteira, basta fazê-lo WHERE IN (SELECT myID FROM #biglist). E #biglistpode ser uma tabela real (permanente) ou uma tabela temporária criada em tempo real.
BradC
Por favor, poste toda a consulta e o que você está calculando externamente; isso provavelmente é algo que você pode fazer completamente no SQL. Renomeie os nomes dos campos se estiver preocupado com a privacidade ou o que for.
Mike

Respostas:

67

Para pesquisar mais de 100.000 valores, coloque-os em uma tabela temporária, uma linha por valor que você está procurando. Em seguida, ingresse sua consulta nessa tabela temporária para filtragem.

Algo com mais de 100.000 valores não é um parâmetro - é uma tabela. Em vez de pensar em aumentar o limite, considere a regra dos dez por cento de Swart : se você estiver chegando a 10% de um limite do SQL Server, provavelmente terá problemas.

Brent Ozar
fonte
11
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Paul White Reinstate Monica
10

Se você quiser alterar o aplicativo de qualquer maneira, considere

(a) usando um TVP para todo o conjunto de valores - você criaria um DataTableem C # e o passaria para um procedimento armazenado usando StructuredType, como demonstro aqui . (Espero que 100.000 entradas não sejam normais, pois a escalabilidade pode ser um problema, independentemente da abordagem usada).

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

ou

(b) usando um TVP para passar nos limites superior e inferior dos intervalos e escrevendo uma junção ligeiramente diferente (obrigado @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
Aaron Bertrand
fonte
4

Apenas meus 2 ¢ em relação a reduzir a condição da consulta: -

Se você puder determinar todos os valores possíveis entryantecipadamente, seria possível se você aceitasse o complemento de sua consulta?

Antes

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

Depois de

where entry not in (4,5,11,14)
Zephyr
fonte
0

É difícil descobrir o que você está tentando realizar sem conseguir ver a consulta, mas aqui vamos nós, supondo que sua consulta precise apenas de duas tabelas, uma com os dados e outra com os valores que você deseja evitar:

  • O uso where entry in (<list of 100.000 values>)é escandalosamente terrível, tanto do ponto de vista da eficiência quanto da manutenção.
  • O uso where entry in (select whatever from table)é tão atrozmente horrível quanto o anterior. No entanto, dependendo da idiossincrasia das tabelas e do mecanismo SQL, ele pode funcionar bem, apesar do câncer de córnea. (Pode estar OK no Oracle, nunca estará no MySQL, não se lembra do MSSQL).
  • Usar o PLSQL para conseguir isso é simplesmente horrendo.
  • Na minha opinião (sem conhecer a consulta), você deve reescrever a consulta da seguinte maneira:

    • Se esses 100.000 valores forem sempre os mesmos, sem depender do restante da consulta, você deverá fazer upload desses valores em uma tabela (table_fixed_values) e 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
    • Se esses 100.000 valores não forem os mesmos, deve haver algum tipo de lógica para selecionar esses 100.000, lógica que você deve incorporar na ONconsulta anterior.

glezo
fonte
3
Por que LEFT JOIN table_fixed_values ON A.entry=B.entry WHERE B.entry IS NOT NULLe não o equivalente, mais simples e mais fácil de ler INNER JOIN table_fixed_values ON A.entry=B.entry?
precisa saber é o seguinte
@ ypercubeᵀᴹ completamente certo, o INNER JOIN faz mais sentido.
glezo