WHERE句の100,000を超えるエントリが原因のSQL Serverエラー8632


16

私の問題(あるいは少なくとも、エラーメッセージは)非常によく似ているクエリプロセッサの内部リソースを使い果たした-非常に長いSQLクエリを

私の顧客は、正確に100,000エントリのwhere-clauseを含むSQL select-queryを使用しています。

クエリはエラー8632とエラーメッセージで失敗します

内部エラー:式サービスの制限に達しました。クエリで潜在的に複雑な式を探し、それらを単純化してみてください。

このエラーメッセージが正確に100,000エントリでスローされることは非常に奇妙であるため、これが設定可能な値であるかどうか疑問に思います。これは事実ですか?そうであれば、この値をより高い値に増やすにはどうすればよいですか?

上のMSDN、そこにクエリを再書き換えるための提案ですが、私はこの問題を回避したいと思います。

一方、私が話しているエントリのリストには自然数が含まれていることがわかりました。それらのかなりの数は連続しているように見えます( 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-clauseのデータは計算の結果であり、別のテーブルで行われます。最初にそのテーブルのエントリが読み取られ、最初にフィルタリングされ、次に追加の処理が行われます(使用することは不可能です) SQL)、その追加処理の結果はより多くのフィルタリングであり、その結果はwhere-clauseで使用されます。SQLで完全なフィルタリングを記述することは不可能であったため、前述の方法が使用されています。where-clauseの内容は処理ごとに変わる可能性があるため、動的なソリューションが必要であることは明らかです。


7
編集への応答:いいえ、WHERE INそのような範囲の構文はサポートしていません。また、WHERE () OR () OR ()AND であってはなりません。しかし、ブレントの提案を使用するには、実際にクエリ全体を変更する必要はありませんWHERE IN (SELECT myID FROM #biglist)。また#biglist、実際の(永続的な)テーブル、またはオンザフライで作成する一時テーブルのいずれかです。
BradC

クエリ全体と外部で計算していることを投稿してください。これはおそらくSQLで完全にできることです。プライバシーなどが心配な場合は、フィールド名を変更してください。
マイク

回答:


67

100,000を超える値を検索するには、代わりに、検索する値ごとに1行の一時テーブルにそれらを配置します。次に、フィルタリングのためにクエリをその一時テーブルに結合します。

100,000を超える値を持つものはパラメーターではなく、テーブルです。制限の引き上げについて考えるのではなく、Swartの10%ルールを検討してください。SQLServerの制限の10%に近づいている場合、おそらく悪い時間を過ごすことになります。


1
コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
ポール・ホワイト・ライステート・モニカ

10

とにかくアプリを変更する場合は、次のいずれかを検討してください

()の値のセット全体のため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

または

(b)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の記事や他の記事で提案されている回避策があります。ここで2つ挙げましたが、さらに検索することができます。


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

クエリを実際に見ることができずに何を達成しようとしているのかを理解するのは難しいですが、ここでは、クエリに必要なのはデータと回避したい値の2つのテーブルのみであると仮定します:

  • 使用where entry in (<list of 100.000 values>)は、効率と保守の両方の観点からとてつもなくひどいです。
  • 使用することwhere entry in (select whatever from table)は、前のものほどひどくひどいものではありません。しかし、両方のテーブルの特異性とSQLエンジンによっては、角膜がんにもかかわらず正常に実行される場合があります。(オラクルでは大丈夫かもしれませんが、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前のクエリのロジックに埋め込む必要があります。


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ᵀᴹ完全に正しい、INNER JOINはより理にかなっています。
グレゾ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.