la fonction se bloque avec une opération de casse nulle


9

J'ai créé une fonction qui accepte une date de début et de fin, la date de fin étant facultative. J'ai ensuite écrit un CASEdans le filtre pour utiliser la date de début si aucune date de fin n'est passée.

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

Lorsque j'appelle la fonction pour le mois le plus récent des données:

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

... la requête se bloque. Si je précise la date de fin:

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

... le résultat est renvoyé normalement. J'ai retiré le code de la fonction et l'ai exécuté correctement dans une fenêtre de requête. Je ne peux pas non plus reproduire le problème du violon. Une requête comme:

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

... fonctionne aussi très bien.

Y a-t-il quelque chose dans la requête (ci-dessous) qui pourrait entraîner le blocage de la fonction lorsque a NULLest passé pour la date de fin?

SQL Fiddle


Pouvez-vous poster plus de logique? Ce que vous avez là-bas ne devrait pas poser de problème.
Kenneth Fisher

3
Si vous remplacez le CASEpar COALESCE(@dateEnd,@dateStart), le problème apparaît-il toujours?
ypercubeᵀᴹ

2
Et avec ISNULL()?
ypercubeᵀᴹ

3
Est-il occupé ou attend quelque chose? Pendant qu'il est "accroché", que SELECT task_state FROM sys.dm_os_tasks WHERE session_id = x montre-t-il? S'il passe beaucoup de temps hors de l' RUNNINGétat, quels types d'attente cette session entre- sys.dm_os_waiting_taskst-elle?
Martin Smith

1
@ypercube Aucune amélioration avec COALESCE. ISNULLl'a corrigé.
Kermit

Réponses:


7

Une partie de votre requête initiale est la suivante.

  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) 

Cette section du plan est illustrée ci-dessous

entrez la description de l'image ici

Votre requête révisée BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart) a ceci pour la même jointure

entrez la description de l'image ici

La différence semble être de ISNULLsimplifier davantage et, par conséquent, vous obtenez des statistiques de cardinalité plus précises lors de la prochaine jointure. Il s'agit d'une fonction de valeur de table en ligne et vous l'appelez avec des valeurs littérales afin qu'elle puisse faire quelque chose comme.

 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'

Et comme il existe un prédicat de jointure équi, b.[Date] = a.dle plan affiche également un prédicat d'égalité b.[Date] = '2013-06-01'. Par conséquent, l'estimation de cardinalité des 28,393lignes est probablement assez précise.

Pour la version CASE/ COALESCEquand @dateStartet @dateEndont la même valeur, cela simplifie OK à la même expression d'égalité et donne le même plan mais quand @dateStart = '2013-06-01'et @dateEnd IS NULLcela ne va que jusqu'à

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

qu'il applique également comme prédicat implicite ColleagueList. Le nombre estimé de lignes cette fois est de 79.8lignes.

La prochaine jointure est

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

colleagueTimeest une 3,249,590table de lignes qui est (encore) apparemment un tas sans index utiles.

Cet écart dans les estimations affecte le choix de jointure utilisé. leISNULL plan choisit une jointure de hachage qui scanne la table une seule fois. Le COALESCEplan choisit une jointure de boucles imbriquées et estime qu'il lui suffira de scanner une fois la table et de pouvoir spouler le résultat et le rejouer 78 fois. c'est-à-dire qu'il estime que les paramètres corrélés ne changeront pas.

Du fait que le plan des boucles imbriquées était toujours en cours au bout de deux heures, cette hypothèse d'un scan unique contre colleagueTime semble très imprécise.

Quant à savoir pourquoi le nombre estimé de lignes entre les deux jointures est tellement inférieur, je ne suis pas sûr sans pouvoir voir les statistiques sur les tables. La seule façon dont j'ai réussi à fausser le nombre de lignes estimé dans mes tests était d'ajouter une charge deNULL lignes (cela a réduit le nombre de lignes estimé même si le nombre réel de lignes retournées est resté le même).

Le nombre de lignes estimé dans le COALESCEplan avec mes données de test était de l'ordre de

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

Ou en SQL

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

mais cela ne correspond pas à votre commentaire selon lequel la colonne n'a pas de NULLvaleurs.


"avez-vous une très forte proportion de valeurs NULL dans la colonne Date de ce tableau?" Je n'ai pas de NULLvaleurs pour les dates dans aucun de ces tableaux.
Kermit

@FreshPrinceOfSO - C'est dommage. Je ne sais toujours pas pourquoi il y a alors un si grand écart dans les deux estimations. Dans les tests, j'ai fait le filtre bitmap et un prédicat supplémentaire ne semble pas altérer les estimations de cardinalité, peut-être qu'il le fait ici.
Martin Smith

@FreshPrinceOfSO - Mais si vous avez envie d' écrire les statistiques, je peux essayer de le comprendre.
Martin Smith

Je suis sur 2008R2; lorsque j'arrive à Choisir les schémas , dbon'est pas répertorié. Juste d'autres schémas que je n'utilise pas.
Kermit

4

Il semble qu'il y ait eu un problème avec les types de données. ISNULLcorrection du problème (merci ypercube ). Après quelques recherches, COALESCEest l'équivalent de la CASEdéclaration que j'utilisais:

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

Paul White explique que:

COALESCE( expression [ ,...n ] ) renvoie le type de données de l'expression avec la priorité de type de données la plus élevée.

ISNULL(check_expression, replacement_value) renvoie le même type que check_expression.

Pour éviter tout problème de type de données, il semble que ISNULLc'est la fonction appropriée à utiliser pour traiter uniquement deux expressions.

Extraits de plan XML

Le plan XML utilisant l' CASEexpression 2 est 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>

Plan XML utilisant CASE, l'expression 2 est une date:

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>

Le plan XML utilisant l' ISNULLexpression 2 est 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>

Plan XML utilisant ISNULL, l'expression 2 est une date:

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>

Mais cela n'explique pas pourquoi cela a bien fonctionné SELECT * FROM theFunction ('2013-06-01', '2013-06-01'). Le type de données d'expression est toujours le même. Et les deux paramètres sont de datetoute façon du type de données. Pouvez-vous consulter les plans d'exécution?
Martin Smith

@MartinSmith Voici le plan de la requête qui renvoie un résultat. Je n'ai pas de plan quand la deuxième expression est NULL.
Kermit

La diffusion des expressions à l'intérieur du CASEn'a également eu aucun effet, la requête se bloque toujours.
Kermit

2
Pourquoi aucun plan pour le deuxième cas? Est-ce simplement parce que la requête ne se termine jamais? Si oui, pouvez-vous obtenir un plan estimatif? Vous vous demandez si les différentes expressions changent les estimations de cardinalité et vous vous retrouvez avec un plan différent.
Martin Smith

3
Le ISNULLplan semble simplifier mieux. Il a un prédicat d'égalité simple sur ColleagueList [Date]='2013-06-01'alors que CASEcelui a un prédicat sur [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date]). Les lignes estimées sortant de cette jointure sont 28 393 pour la ISNULLversion mais beaucoup plus faibles 79.8pour la CASEversion qui effectue le choix de la jointure plus tard dans le plan. Je ne sais pas pourquoi il y aurait un tel écart.
Martin Smith
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.