SQL Server-Fehler 8632 aufgrund von über 100.000 Einträgen in der WHERE-Klausel

16

Mein Problem (oder zumindest die Fehlermeldung) ist dem Abfrageprozessor sehr ähnlich, da ihm die internen Ressourcen ausgehen - extrem lange SQL-Abfrage .

Mein Kunde arbeitet mit einer SQL-Auswahlabfrage, die eine Where-Klausel mit genau 100.000 Einträgen enthält.

Die Abfrage schlägt mit Fehler 8632 und Fehlermeldung fehl

Interner Fehler: Ein Ausdrucksdienstlimit wurde erreicht. Bitte suchen Sie in Ihrer Anfrage nach potenziell komplexen Ausdrücken und versuchen Sie diese zu vereinfachen.)

Ich finde es sehr merkwürdig, dass diese Fehlermeldung genau bei 100.000 Einträgen ausgegeben wird, daher frage ich mich, ob dies ein konfigurierbarer Wert ist. Ist dies der Fall und falls ja, wie kann ich diesen Wert auf einen höheren Wert erhöhen?

Auf MSDN gibt es den Vorschlag, die Abfrage neu zu schreiben, aber ich möchte dies vermeiden.

In der Zwischenzeit habe ich herausgefunden, dass die Liste der Einträge, über die ich spreche, natürliche Zahlen enthält, von denen einige sequenziell zu sein scheinen (so etwas wie (1,2,3,6,7,8,9,10,12, 13,15,16,17,18,19,20).

Dies macht die SQL-WHERE-Klausel ungefähr so:

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

Ich könnte dies umwandeln in:

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

Kann dies verkürzt werden durch:

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

...oder etwas ähnliches? (Ich weiß, es ist ein langer Weg, aber es würde Software-Updates einfacher und lesbarer machen.)

Zu Ihrer Information: Die Daten in der where-Klausel sind das Ergebnis einer Berechnung, die in einer anderen Tabelle durchgeführt wurde: Zuerst werden die Einträge dieser Tabelle gelesen und zu Beginn gefiltert SQL), das Ergebnis dieser zusätzlichen Verarbeitung ist eine stärkere Filterung, und das Ergebnis wird in der where-Klausel verwendet. Da es unmöglich war, die vollständige Filterung in SQL zu schreiben, wurde die erwähnte Methode verwendet. Offensichtlich kann sich der Inhalt der where-Klausel bei jeder Verarbeitung ändern, weshalb eine dynamische Lösung erforderlich ist.

Dominique
quelle
7
Antwort auf Ihre Änderung: Nein, WHERE INdiese Art der Bereichssyntax wird nicht unterstützt. Auch sollte es WHERE () OR () OR ()nicht UND sein. Um den Vorschlag von Brent zu verwenden, müssen Sie jedoch nicht die gesamte Abfrage ändern, sondern können dies einfach tun WHERE IN (SELECT myID FROM #biglist). Und es kann #biglistsich entweder um einen echten (permanenten) Tisch oder einen temporären Tisch handeln, den Sie im Handumdrehen erstellen.
BradC
Bitte, bitte, bitte posten Sie die gesamte Abfrage und was Sie extern berechnen, das ist wahrscheinlich wirklich etwas, was Sie vollständig in SQL tun könnten. Benennen Sie die Feldnamen um, wenn Sie Bedenken hinsichtlich der Privatsphäre haben oder was auch immer.
Mike

Antworten:

67

Um nach mehr als 100.000 Werten zu suchen, fügen Sie diese stattdessen in eine temporäre Tabelle ein, und zwar eine Zeile pro Wert, nach dem Sie suchen. Verbinden Sie dann Ihre Abfrage mit dieser temporären Tabelle zum Filtern.

Etwas mit mehr als 100.000 Werten ist kein Parameter - es ist eine Tabelle. Ziehen Sie die Zehn-Prozent-Regel von Swart in Betracht, anstatt darüber nachzudenken, das Limit zu erhöhen : Wenn Sie sich 10% eines SQL Server-Limits nähern, werden Sie wahrscheinlich eine schlechte Zeit haben.

Brent Ozar
quelle
1
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
Paul White sagt GoFundMonica
10

Wenn Sie die App dennoch ändern möchten, sollten Sie dies in Betracht ziehen

(a) Verwenden eines TVP für den gesamten Wertesatz - Sie würden ein DataTablein C # erstellen und es mit in eine gespeicherte Prozedur übergeben StructuredType, wie ich hier demonstriere . (Hoffentlich sind 100.000 Einträge nicht normal, da die Skalierbarkeit ein Problem sein kann, egal welchen Ansatz Sie verwenden.)

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

oder

(b) Verwenden eines TVP, um die oberen und unteren Grenzen der Bereiche zu überschreiten, und Schreiben eines etwas anderen Joins (danke @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
quelle
4

Nur meine 2 ¢ zur Verkürzung der Abfragebedingung: -

Wenn Sie in der Lage sind, alle möglichen Werte von entryim Voraus zu bestimmen , wäre es möglich, wenn Sie die Ergänzung Ihrer Abfrage nehmen?

Vor

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

Nach

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

Es ist schwer herauszufinden, was Sie erreichen möchten, ohne die Abfrage tatsächlich sehen zu können. Wir gehen jedoch davon aus, dass Ihre Abfrage nur zwei Tabellen benötigt, eine mit den Daten und eine mit den Werten, die Sie vermeiden möchten:

  • Die Verwendung where entry in (<list of 100.000 values>)ist sowohl aus Effizienz- als auch aus Wartungssicht unverschämt schrecklich.
  • Das Benutzen where entry in (select whatever from table)ist kaum so schrecklich wie das vorhergehende. Abhängig von der Eigenart beider Tabellen und der SQL-Engine ist die Leistung trotz des Hornhautkrebses jedoch möglicherweise in Ordnung. (Könnte in Oracle in Ordnung sein, wird niemals in MySQL sein, kann mich nicht an MSSQL erinnern).
  • Die Verwendung von PLSQL, um dies zu erreichen, ist einfach horrend.
  • Meiner Meinung nach (ohne die Abfrage überhaupt zu kennen) sollten Sie die Abfrage wie folgt umschreiben:

    • Wenn diese 100.000 Werte immer gleich sind und nicht auf den Rest der Abfrage angewiesen sind, sollten Sie diese Werte in eine Tabelle (table_fixed_values) hochladen und verwenden

      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
    • Wenn diese 100.000 Werte nicht identisch sind, muss es eine Art Logik geben, um diese 100.000 Werte zu ermitteln. Diese Logik sollten Sie in die ONder vorherigen Abfrage einbetten .

glezo
quelle
3
Warum LEFT JOIN table_fixed_values ON A.entry=B.entry WHERE B.entry IS NOT NULLund nicht das Äquivalent, einfacher und leichter zu lesen INNER JOIN table_fixed_values ON A.entry=B.entry?
Ypercubeᵀᴹ
@ ypercubeᵀᴹ ganz richtig, der INNER JOIN macht mehr Sinn.
Glezo