la función se bloquea con la operación de caso nulo

9

Creé una función que acepta una fecha de inicio y finalización, siendo la fecha de finalización opcional. Luego escribí un CASEen el filtro para usar la fecha de inicio si no se pasa ninguna fecha de finalización.

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

Cuando llamo a la función para el mes más reciente de los datos:

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

... la consulta se cuelga. Si especifico la fecha de finalización:

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

... el resultado se devuelve normalmente. Saqué el código de la función y lo ejecuté bien dentro de una ventana de consulta. No puedo duplicar el problema del violín tampoco. Una consulta como:

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

... también funciona bien.

¿Hay algo en la consulta (a continuación) que pueda causar que la función se bloquee cuando NULLse pasa un para la fecha de finalización?

Violín de SQL

Kermit
fuente
¿Puedes publicar más de la lógica? Lo que tienes allí no debería estar causando un problema.
Kenneth Fisher
3
Si reemplaza el CASEcon COALESCE(@dateEnd,@dateStart), ¿sigue apareciendo el problema?
ypercubeᵀᴹ
2
Y con ISNULL()?
ypercubeᵀᴹ
3
¿Está ocupado o esperando algo? Mientras está "colgado", ¿qué SELECT task_state FROM sys.dm_os_tasks WHERE session_id = x muestra? Si pasa mucho tiempo sin estar en el RUNNINGestado, ¿qué tipos de espera está recibiendo esa sesión sys.dm_os_waiting_tasks?
Martin Smith
1
@ypercube No hay mejora con COALESCE. ISNULLarreglado.
Kermit

Respuestas:

7

Parte de su consulta inicial es la siguiente.

  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) 

Esa sección del plan se muestra a continuación

ingrese la descripción de la imagen aquí

Su consulta revisada BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart)tiene esto para la misma combinación

ingrese la descripción de la imagen aquí

La diferencia parece ser que se ISNULLsimplifica aún más y, como resultado, obtienes estadísticas de cardinalidad más precisas en la próxima unión. Esta es una función con valores de tabla en línea y la está llamando con valores literales para que pueda hacer algo así.

 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'

Y como hay un predicado de equi join, b.[Date] = a.del plan también muestra un predicado de igualdad b.[Date] = '2013-06-01'. Como resultado, 28,393es probable que la estimación de cardinalidad de las filas sea bastante precisa.

Para la versión CASE/ COALESCEcuando @dateStarty @dateEndson el mismo valor, entonces simplifica OK a la misma expresión de igualdad y proporciona el mismo plan, pero cuando @dateStart = '2013-06-01'y @dateEnd IS NULLsolo va tan lejos como

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

que también se aplica como un predicado implícito en ColleagueList. El número estimado de filas esta vez es 79.8filas.

La próxima unión es

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

colleagueTimees una 3,249,590tabla de filas que (de nuevo) aparentemente es un montón sin índices útiles.

Esta discrepancia en las estimaciones afecta la elección de combinación utilizada. El ISNULLplan elige una combinación hash que solo escanea la tabla una vez. El COALESCEplan elige una unión de bucles anidados y estima que solo tendrá que escanear la tabla una vez y poder poner en cola el resultado y reproducirlo 78 veces. es decir, estima que los parámetros correlacionados no cambiarán.

Por el hecho de que el plan de bucles anidados seguía funcionando después de dos horas, esta suposición de un solo escaneo en contra colleagueTimeparece ser muy inexacta.

En cuanto a por qué el número estimado de filas entre las dos uniones es mucho menor, no estoy seguro sin poder ver las estadísticas en las tablas. La única forma en que logré sesgar los recuentos de filas estimados tanto en mis pruebas fue agregando una carga de NULLfilas (esto redujo el recuento de filas estimado a pesar de que el número real de filas devueltas permaneció igual).

El recuento de filas estimado en el COALESCEplan con mis datos de prueba fue del orden de

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

O en SQL

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

pero esto no cuadra con su comentario de que la columna no tiene NULLvalores.

Martin Smith
fuente
"¿tiene una proporción muy alta de valores NULL en la columna Fecha en esa tabla?" No tengo NULLvalores para fechas en ninguna de esas tablas.
Kermit
@FreshPrinceOfSO: es una pena. Todavía no tengo idea de por qué hay una gran discrepancia en las dos estimaciones entonces. En las pruebas hice el filtro de mapa de bits y el predicado adicional no pareció alterar las estimaciones de cardinalidad, tal vez sí lo hace aquí.
Martin Smith
@FreshPrinceOfSO: aunque si tuviera ganas de escribir las estadísticas , puedo intentar descifrarlo.
Martin Smith
Estoy en 2008R2; cuando llego a Elegir esquemas , dbono aparece en la lista. Solo otros esquemas que no uso.
Kermit
4

Parece que hubo un problema con los tipos de datos. ISNULLsolucionado el problema (gracias ypercube ). Después de un poco de investigación, COALESCEes el equivalente a la CASEdeclaración que estaba usando:

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

Paul White explica que:

COALESCE( expression [ ,...n ] ) devuelve el tipo de datos de la expresión con la mayor prioridad de tipo de datos.

ISNULL(check_expression, replacement_value) devuelve el mismo tipo que check_expression.

Para evitar problemas de tipo de datos, parece que ISNULLes la función apropiada para tratar solo con dos expresiones.

Extractos del plan XML

El plan XML que usa la CASEexpresión 2 es 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 utilizando CASE, la expresión 2 es una fecha:

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>

El plan XML que usa la ISNULLexpresión 2 es 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 utilizando ISNULL, la expresión 2 es una fecha:

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
fuente
Pero eso no explica por qué funcionó bien SELECT * FROM theFunction ('2013-06-01', '2013-06-01'). La expresión datatype sigue siendo la misma. Y ambos parámetros son de datetipo de datos de todos modos. ¿Puedes ver los planes de ejecución?
Martin Smith
@MartinSmith Aquí está el plan para la consulta que devuelve un resultado. No tengo un plan cuando la segunda expresión es NULL.
Kermit
La conversión de las expresiones dentro de la CASEtambién no tuvo efecto, la consulta aún se cuelga.
Kermit
2
¿Cómo es que no hay plan para el segundo caso? ¿Es solo porque la consulta nunca termina? Si es así, ¿puede obtener un plan estimado? Preguntándose si las diferentes expresiones cambian las estimaciones de cardinalidad y terminas con un plan diferente.
Martin Smith
3
Las ISNULLmiradas del plan como Simplifica mejor. Tiene un predicado de igualdad simple en ColleagueList de [Date]='2013-06-01'mientras que el CASEque tiene un predicado en [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date]). Las filas estimadas que salen de esa unión son 28,393 para la ISNULLversión, pero mucho más bajas 79.8para la CASEversión que afecta la elección de la unión más adelante en el plan. No estoy seguro de por qué habría tanta discrepancia.
Martin Smith