Funktion hängt mit Null-Fall-Operation

9

Ich habe eine Funktion erstellt, die ein Start- und Enddatum akzeptiert, wobei das Enddatum optional ist. Ich habe dann ein CASEin den Filter geschrieben, um das Startdatum zu verwenden, wenn kein Enddatum überschritten wird.

CASE WHEN @dateEnd IS NULL
    THEN @dateStart
    ELSE @dateEnd
END

Wenn ich die Funktion für den letzten Monat der Daten aufrufe:

SELECT * FROM theFunction ('2013-06-01', NULL)

... die Abfrage hängt. Wenn ich das Enddatum spezifiziere:

SELECT * FROM theFunction ('2013-06-01', '2013-06-01')

... das Ergebnis wird normal zurückgegeben. Ich habe den Code aus der Funktion genommen und ihn in einem Abfragefenster einwandfrei ausgeführt. Ich kann das Problem der Geige auch nicht duplizieren. Eine Abfrage wie:

SELECT * FROM theFunction ('2013-04-01', '2013-06-01')

... funktioniert auch gut.

Gibt es irgendetwas in der Abfrage (unten), das dazu führen könnte, dass die Funktion hängen NULLbleibt, wenn a für das Enddatum übergeben wird?

SQL Fiddle

Kermit
quelle
Können Sie mehr von der Logik posten? Was Sie dort haben, sollte kein Problem verursachen.
Kenneth Fisher
3
Wenn Sie die ersetzen CASEmit COALESCE(@dateEnd,@dateStart), scheint das Problem noch?
Ypercubeᵀᴹ
2
Und mit ISNULL()?
Ypercubeᵀᴹ
3
Ist es beschäftigt oder wartet auf etwas? Während es "aufgehängt" ist, was SELECT task_state FROM sys.dm_os_tasks WHERE session_id = x zeigt sich? Wenn es viel Zeit nicht im RUNNINGZustand verbringt, in welche Wartezeiten kommt diese Sitzung sys.dm_os_waiting_tasks?
Martin Smith
1
@ypercube Keine Verbesserung mit COALESCE. ISNULLbehoben.
Kermit

Antworten:

7

Ein Teil Ihrer ersten Abfrage lautet wie folgt.

  FROM   [dbo].[calendar] a
          LEFT JOIN [dbo].[colleagueList] b
            ON b.[Date] = a.d
   WHERE  DAY(a.[d]) = 1
          AND a.[d] BETWEEN @dateStart AND COALESCE(@dateEnd,@dateStart) 

Dieser Abschnitt des Plans ist unten dargestellt

Geben Sie hier die Bildbeschreibung ein

Ihre überarbeitete Abfrage BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart)hat dies für denselben Join

Geben Sie hier die Bildbeschreibung ein

Der Unterschied scheint darin zu liegen, dass er sich ISNULLweiter vereinfacht und als Ergebnis genauere Kardinalitätsstatistiken für den nächsten Join erhalten. Dies ist eine Inline-Tabellenwertfunktion, und Sie rufen sie mit Literalwerten auf, damit sie so etwas tun kann.

 a.[d] BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart) 
 a.[d] BETWEEN '2013-06-01' AND ISNULL(NULL,'2013-06-01') 
 a.[d] BETWEEN '2013-06-01' AND '2013-06-01'
 a.[d] = '2013-06-01'

Und da es ein Equi-Join-Prädikat gibt b.[Date] = a.d, zeigt der Plan auch ein Gleichheitsprädikat b.[Date] = '2013-06-01'. Infolgedessen ist die Kardinalitätsschätzung von 28,393Zeilen wahrscheinlich ziemlich genau.

Für die CASE/ COALESCE-Version, wenn @dateStartund @dateEndderselbe Wert ist, vereinfacht sich OK für denselben Gleichheitsausdruck und gibt denselben Plan an, aber wann @dateStart = '2013-06-01'und @dateEnd IS NULLgeht nur so weit wie

a.[d]>='2013-06-01' AND a.[Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END

was es auch als implizites Prädikat gilt ColleagueList. Die geschätzte Anzahl der Zeilen beträgt diesmal 79.8Zeilen.

Der nächste Join ist

   LEFT JOIN colleagueTime
     ON colleagueTime.TC_DATE = colleagueList.Date
        AND colleagueTime.ASSOC_ID = CAST(colleagueList.ID AS VARCHAR(10)) 

colleagueTimeist eine 3,249,590Zeilentabelle, die (wieder) anscheinend ein Heap ohne nützliche Indizes ist.

Diese Diskrepanz bei den Schätzungen wirkt sich auf die verwendete Verknüpfungsauswahl aus. Der ISNULLPlan wählt einen Hash-Join, der die Tabelle nur einmal scannt. Der COALESCEPlan wählt einen Join für verschachtelte Schleifen aus und schätzt, dass die Tabelle nur noch einmal gescannt werden muss, um das Ergebnis zu spoolen und 78 Mal wiederzugeben. dh es wird geschätzt, dass sich die korrelierten Parameter nicht ändern werden.

Aufgrund der Tatsache, dass der Plan für verschachtelte Schleifen nach zwei Stunden noch lief, colleagueTimescheint diese Annahme eines einzelnen Scans gegen sehr ungenau zu sein.

Ich bin mir nicht sicher, warum die geschätzte Anzahl der Zeilen zwischen den beiden Verknüpfungen so viel geringer ist, ohne die Statistiken in den Tabellen sehen zu können. Die einzige Möglichkeit, die geschätzten Zeilenzahlen so stark zu verzerren, bestand darin, eine NULLAnzahl von Zeilen hinzuzufügen (dies reduzierte die geschätzte Zeilenanzahl, obwohl die tatsächliche Anzahl der zurückgegebenen Zeilen gleich blieb).

Die geschätzte Zeilenanzahl im COALESCEPlan mit meinen Testdaten lag in der Größenordnung von

number of rows matching >= condition * 30% * (proportion of rows in the table not null)

Oder in SQL

SELECT 1E0 * COUNT([Date]) / COUNT(*) * ( COUNT(CASE
                                                  WHEN [Date] >= '2013-06-01' THEN 1
                                                END) * 0.30 )
FROM   [dbo].[colleagueList] 

Dies entspricht jedoch nicht Ihrem Kommentar, dass die Spalte keine NULLWerte enthält.

Martin Smith
quelle
"Haben Sie einen sehr hohen Anteil an NULL-Werten in der Spalte Datum in dieser Tabelle?" Ich habe NULLin keiner dieser Tabellen Werte für Datumsangaben.
Kermit
@FreshPrinceOfSO - Das ist schade. Ich habe immer noch keine Ahnung, warum es dann eine so große Diskrepanz zwischen den beiden Schätzungen gibt. In den Tests habe ich den Bitmap-Filter durchgeführt und ein zusätzliches Prädikat schien die Kardinalitätsschätzungen nicht zu ändern, vielleicht tut es dies hier.
Martin Smith
@FreshPrinceOfSO - Wenn Sie Lust haben , die Statistiken zu erstellen, kann ich versuchen, es herauszufinden.
Martin Smith
Ich bin auf 2008R2; wenn ich zu Schemata wählen komme , dboist nicht aufgeführt. Nur andere Schemata, die ich nicht benutze.
Kermit
4

Es scheint, als ob es ein Problem mit den Datentypen gab. ISNULLDas Problem wurde behoben (danke ypercube ). Nach einigen Recherchen COALESCEentspricht dies der CASEAussage, die ich verwendet habe:

CASE
   WHEN (expression1 IS NOT NULL) THEN expression1
   WHEN (expression2 IS NOT NULL) THEN expression2
   ...
   ELSE expressionN
END

Paul White erklärt Folgendes:

COALESCE( expression [ ,...n ] ) Gibt den Datentyp des Ausdrucks mit der höchsten Priorität für den Datentyp zurück.

ISNULL(check_expression, replacement_value) Gibt den gleichen Typ wie check_expression zurück.

Um Datentypprobleme zu vermeiden, scheint ISNULLes die geeignete Funktion zu sein, nur zwei Ausdrücke zu behandeln.

XML-Planauszüge

XML-Plan mit CASEAusdruck 2 lautet NULL:

SELECT * FROM theFunction ('2013-06-01', NULL)
<ScalarOperator ScalarString="CASE WHEN (1) THEN '2013-06-01' ELSE NULL END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Const ConstValue="(1)"/>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="'2013-06-01'"/>
      </ScalarOperator>
    </Then>
    <Else>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>

XML-Plan mit CASE, Ausdruck 2 ist ein Datum:

SELECT * FROM theFunction ('2013-06-01', '2013-06-01')
<ScalarOperator ScalarString="CASE WHEN [Expr1035]=(0) THEN NULL ELSE [Expr1036] END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Compare CompareOp="EQ">
          <ScalarOperator>
            <Identifier>
              <ColumnReference Column="Expr1035"/>
            </Identifier>
          </ScalarOperator>
          <ScalarOperator>
            <Const ConstValue="(0)"/>
          </ScalarOperator>
        </Compare>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
      </Then>
    <Else>
      <ScalarOperator>
        <Identifier>
          <ColumnReference Column="Expr1036"/>
        </Identifier>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>

XML-Plan mit ISNULLAusdruck 2 lautet NULL:

SELECT * FROM theFunction ('2013-06-01', NULL)
<ScalarOperator ScalarString="CASE WHEN [Expr1035]=(0) THEN NULL ELSE [Expr1036] END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Compare CompareOp="EQ">
          <ScalarOperator>
            <Identifier>
              <ColumnReference Column="Expr1035"/>
            </Identifier>
          </ScalarOperator>
          <ScalarOperator>
            <Const ConstValue="(0)"/>
          </ScalarOperator>
        </Compare>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
    </Then>
    <Else>
      <ScalarOperator>
        <Identifier>
          <ColumnReference Column="Expr1036"/>
        </Identifier>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>

XML-Plan mit ISNULL, Ausdruck 2 ist ein Datum:

SELECT * FROM theFunction ('2013-06-01', '2013-06-01')
<ScalarOperator ScalarString="CASE WHEN [Expr1035]=(0) THEN NULL ELSE [Expr1036] END">
  <IF>
    <Condition>
      <ScalarOperator>
        <Compare CompareOp="EQ">
          <ScalarOperator>
            <Identifier>
              <ColumnReference Column="Expr1035"/>
            </Identifier>
          </ScalarOperator>
          <ScalarOperator>
            <Const ConstValue="(0)"/>
          </ScalarOperator>
        </Compare>
      </ScalarOperator>
    </Condition>
    <Then>
      <ScalarOperator>
        <Const ConstValue="NULL"/>
      </ScalarOperator>
    </Then>
    <Else>
      <ScalarOperator>
        <Identifier>
          <ColumnReference Column="Expr1036"/>
        </Identifier>
      </ScalarOperator>
    </Else>
  </IF>
</ScalarOperator>
Kermit
quelle
Aber das erklärt nicht, warum es gut funktioniert hat SELECT * FROM theFunction ('2013-06-01', '2013-06-01'). Der Ausdrucksdatentyp ist immer noch der gleiche. Und beide Parameter sind datesowieso Datentyp. Können Sie die Ausführungspläne anzeigen?
Martin Smith
@MartinSmith Hier ist der Plan für die Abfrage, die ein Ergebnis zurückgibt. Ich habe keinen Plan, wann der zweite Ausdruck ist NULL.
Kermit
Das Umwandeln der Ausdrücke in das hatte CASEebenfalls keine Auswirkung, die Abfrage bleibt weiterhin hängen.
Kermit
2
Wie kommt es, dass kein Plan für den zweiten Fall vorliegt? Liegt es nur daran, dass die Abfrage nie beendet wird? Wenn ja, können Sie einen geschätzten Plan erhalten? Sie fragen sich, ob die verschiedenen Ausdrücke die Kardinalitätsschätzungen ändern und Sie am Ende einen anderen Plan haben.
Martin Smith
3
Der ISNULLPlan scheint sich besser zu vereinfachen. Es hat ein einfaches Gleichheitsprädikat auf ColleagueList von, [Date]='2013-06-01'während das CASEeine ein Prädikat auf hat [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date]). Die geschätzten Zeilen, die aus diesem Join hervorgehen, sind 28.393 für die ISNULLVersion, aber viel niedriger 79.8für die CASEVersion, die sich später im Plan auf die Join-Auswahl auswirkt. Ich bin mir nicht sicher, warum es eine solche Diskrepanz geben würde.
Martin Smith