Ошибка SQL Server 8632 из-за более 100 000 записей в предложении WHERE

16

Моя проблема (или, по крайней мере, сообщение об ошибке) очень похожа на обработчике запросов закончились внутренние ресурсы - очень длинный SQL-запрос .

Мой клиент работает с SQL-запросом выбора, содержащим предложение where с ровно 100 000 записей.

Запрос не выполняется с ошибкой 8632 и сообщением об ошибке

Внутренняя ошибка: достигнут лимит служб экспрессии. Пожалуйста, поищите потенциально сложные выражения в вашем запросе и постарайтесь упростить их.)

Я нахожу очень странным, что это сообщение об ошибке выдается точно в 100 000 записей, поэтому мне интересно, если это настраиваемое значение. Это так, и если да, как я могу увеличить это значение до более высокого?

На MSDN есть предложение переписать запрос, но я бы хотел этого избежать.

Между тем я обнаружил, что список записей, о которых я говорю, содержит натуральные числа, довольно многие из них кажутся последовательными (что-то вроде (1,2,3,6,7,8,9,10,12, 13,15,16,17,18,19,20).

Это делает выражение SQL where-что-то вроде:

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

Я мог бы преобразовать это в:

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

Можно ли это сократить на:

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

... или что-то подобное? (Я знаю, что это длинный путь, но это сделает обновления программного обеспечения более легкими и удобочитаемыми)

К вашему сведению: данные в предложении where являются результатом вычислений, выполненных в другой таблице: сначала записи этой таблицы читаются и фильтруются в начале, затем выполняется некоторая дополнительная обработка (что невозможно сделать с помощью SQL), результатом этой дополнительной обработки является дополнительная фильтрация, результат которой используется в предложении where. Поскольку было невозможно написать полную фильтрацию в SQL, упомянутый метод был использован. Очевидно, что содержимое предложения where может меняться при каждой обработке, поэтому необходимо динамическое решение.

Dominique
источник
7
В ответ на ваши изменения: нет, WHERE INне поддерживает такой синтаксис диапазона. Кроме того , она должна быть WHERE () OR () OR ()не AND. Но чтобы воспользоваться предложением Брента, вам не нужно менять весь запрос, вы можете просто сделать это WHERE IN (SELECT myID FROM #biglist). И это #biglistможет быть либо реальная (постоянная) таблица, либо временная таблица, которую вы создаете на лету.
BradC
Пожалуйста, пожалуйста, опубликуйте весь запрос и то, что вы рассчитываете извне, это, вероятно, действительно то, что вы могли бы сделать в SQL полностью. Переименуйте имена полей, если вы беспокоитесь о конфиденциальности или что-то еще.
Майк

Ответы:

67

Чтобы найти более 100 000 значений, поместите их во временную таблицу, по одной строке на каждое искомое значение. Затем присоедините ваш запрос к этой временной таблице для фильтрации.

Что-то с более чем 100 000 значений не является параметром - это таблица. Вместо того, чтобы думать о повышении лимита, рассмотрите правило десяти процентов Swart : если вы приближаетесь к 10% лимита SQL Server, у вас, вероятно, будет плохое время.

Брент Озар
источник
1
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Пол Уайт восстановил Монику
10

Если вы все равно хотите изменить приложение, подумайте

(a) используя TVP для всего набора значений - вы должны создать DataTableв C # и передать его в хранимую процедуру, используя StructuredType, как я продемонстрирую здесь . (Надеюсь, 100 000 записей - это ненормально, поскольку масштабируемость может быть проблемой независимо от того, какой подход вы используете.)

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

или

(б) использование TVP для передачи верхних и нижних границ диапазонов и написание немного другого соединения (спасибо @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
Аарон Бертран
источник
6

Нет, это не настраивается, и вы не можете увеличить его до более высокого.

В упомянутой статье MSDN и других статьях предлагается обходной путь. Я упомянул два здесь, но вы можете искать больше.

SqlWorldWide
источник
4

Просто мои 2 ¢ относительно сокращения условия запроса: -

Если вы можете entryзаранее определить все возможные значения , будет ли это возможным, если вы примете дополнение к своему запросу?

Перед

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

После

where entry not in (4,5,11,14)
зефир
источник
0

Трудно понять, что вы пытаетесь выполнить, не имея возможности увидеть запрос, но здесь мы идем, предполагая, что вашему запросу нужны только две таблицы, одна с данными, другая со значениями, которых вы хотите избежать:

  • С помощью where entry in (<list of 100.000 values>) ужасно ужасно, как с точки зрения эффективности, так и с точки зрения обслуживания.
  • Использовать where entry in (select whatever from table)это так же ужасно, как и раньше. Тем не менее, в зависимости от особенностей таблиц и механизма SQL, он может работать нормально, несмотря на рак роговицы. (Может быть, хорошо в Oracle, никогда не будет в MySQL, не могу вспомнить о MSSQL).
  • Использование PLSQL для достижения этой цели просто ужасно.
  • По моему мнению (не зная запроса вообще), вы должны переписать запрос следующим образом:

    • Если эти 100 000 значений всегда одинаковы, не полагаясь на остальную часть запроса, вы должны загрузить эти значения в таблицу (table_fixed_values) и использовать

      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
    • Если эти 100 000 значений не совпадают, должна быть какая-то логика для подбора этих 100 000 значений, логика, которую вы должны встроить в ONпредыдущий запрос.

glezo
источник
3
Почему, LEFT JOIN table_fixed_values ON A.entry=B.entry WHERE B.entry IS NOT NULLа не эквивалент, проще и проще для чтения INNER JOIN table_fixed_values ON A.entry=B.entry?
ypercubeᵀᴹ
@ ypercubeᵀᴹ совершенно верно, ВНУТРЕННЕЕ СОЕДИНЕНИЕ имеет больше смысла.
17