função trava com operação de caso nulo

9

Criei uma função que aceita uma data de início e término, sendo a data final opcional. Em seguida, escrevi um CASEno filtro para usar a data de início se nenhuma data de término for passada.

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

Quando chamo a função para o mês mais recente dos dados:

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

... a consulta trava. Se eu especificar a data final:

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

... o resultado é retornado normalmente. Tirei o código da função e executei-o bem dentro de uma janela de consulta. Também não posso duplicar a questão. Uma consulta como:

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

... também funciona bem.

Existe alguma coisa na consulta (abaixo) que possa estar causando a interrupção da função quando a NULLé passada para a data final?

SQL Fiddle

Sapo
fonte
Você pode postar mais da lógica? O que você tem lá não deve estar causando um problema.
Kenneth Fisher
3
Se você substituir o CASEpor COALESCE(@dateEnd,@dateStart), o problema ainda aparece?
ypercubeᵀᴹ
2
E com ISNULL()?
ypercubeᵀᴹ
3
Está ocupado ou esperando alguma coisa? Enquanto está "pendurado", o que SELECT task_state FROM sys.dm_os_tasks WHERE session_id = x mostra? Se ele passa muito tempo fora do RUNNINGestado, em que tipos de espera está entrando a sessão sys.dm_os_waiting_tasks?
Martin Smith
11
@ypercube Sem melhorias com COALESCE. ISNULLconsertou.
27613 Kermit

Respostas:

7

Parte da sua consulta inicial é a seguinte.

  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) 

Essa seção do plano é mostrada abaixo

insira a descrição da imagem aqui

Sua consulta revisada BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart)tem isso para a mesma associação

insira a descrição da imagem aqui

A diferença parece ser que ISNULLsimplifica ainda mais e, como resultado, você obtém estatísticas de cardinalidade mais precisas na próxima associação. Esta é uma função com valor de tabela embutida e você a está chamando com valores literais para que ela possa fazer algo parecido.

 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'

E como há um predicado de equi join, b.[Date] = a.do plano também mostra um predicado de igualdade b.[Date] = '2013-06-01'. Como resultado, a estimativa de cardinalidade das 28,393linhas provavelmente será bastante precisa.

Para a versão CASE/ COALESCEquando @dateStarte @dateEndtem o mesmo valor, simplifica OK para a mesma expressão de igualdade e fornece o mesmo plano, mas quando @dateStart = '2013-06-01'e @dateEnd IS NULLsó vai até

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

que também se aplica como predicado implícito em ColleagueList. O número estimado de linhas dessa vez é de 79.8linhas.

A próxima junção é:

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

colleagueTimeé uma 3,249,590tabela de linhas que é (novamente) aparentemente uma pilha sem índices úteis.

Essa discrepância nas estimativas afeta a escolha de junção usada. O ISNULLplano escolhe uma junção de hash que apenas varre a tabela uma vez. O COALESCEplano escolhe uma junção de loops aninhados e estima que ainda será necessário varrer a tabela uma vez e poder colocar o resultado em spool e reproduzi-lo 78 vezes. isto é, estima que os parâmetros correlacionados não serão alterados.

Pelo fato de o plano de loops aninhados ainda estar em andamento após duas horas, é colleagueTimeprovável que essa suposição de uma única varredura seja altamente imprecisa.

Quanto ao motivo pelo qual o número estimado de linhas entre as duas junções é muito menor, não tenho certeza sem poder ver as estatísticas nas tabelas. A única maneira de conseguir distorcer as contagens estimadas de linhas que muito em meus testes foi adicionar uma carga de NULLlinhas (isso reduziu a contagem estimada de linhas, mesmo que o número real de linhas retornadas permanecesse o mesmo).

A contagem estimada de linhas no COALESCEplano com meus dados de teste foi da ordem de

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

Ou no SQL

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

mas isso não corresponde ao seu comentário de que a coluna não tem NULLvalores.

Martin Smith
fonte
"você tem uma proporção muito alta de valores NULL na coluna Data nessa tabela?" Não tenho NULLvalores para datas em nenhuma dessas tabelas.
27513 Kermit
@FreshPrinceOfSO - Isso é uma pena. Ainda não tenho idéia de por que há uma discrepância tão grande nas duas estimativas da época. Nos testes, fiz o filtro de bitmap e um predicado adicional não pareceu alterar as estimativas de cardinalidade, talvez isso ocorra aqui.
Martin Smith
@FreshPrinceOfSO - Embora, se você quiser escrever as estatísticas, eu possa tentar descobrir.
Martin Smith
Estou no 2008R2; quando eu escolho Esquemas , dbonão está listado. Apenas outros esquemas que eu não uso.
27513 Kermit
4

Parece que houve um problema com os tipos de dados. ISNULLCorrigido o problema (obrigado ypercube ). Após algumas pesquisas, COALESCEé o equivalente à CASEafirmação que eu estava 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 ] ) retorna o tipo de dados da expressão com a maior precedência de tipo de dados.

ISNULL(check_expression, replacement_value) retorna o mesmo tipo que check_expression.

Para evitar problemas de tipo de dados, parece ISNULLser a função apropriada a ser usada para lidar com apenas duas expressões.

Trechos do plano XML

O plano XML usando CASE, expressão 2 é 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>

Plano XML usando CASE, expressão 2 é uma data:

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>

O plano XML usando ISNULL, expressão 2 é 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>

Plano XML usando ISNULL, expressão 2 é uma data:

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>
Sapo
fonte
Mas isso não explica por que funcionou bem SELECT * FROM theFunction ('2013-06-01', '2013-06-01'). O tipo de dados da expressão ainda é o mesmo. E ambos os parâmetros são do datetipo de dados de qualquer maneira. Você pode visualizar os planos de execução?
Martin Smith
@ MartinSmith Aqui está o plano para a consulta que retorna um resultado. Não tenho um plano quando a segunda expressão é NULL.
27513 Kermit
A transmissão das expressões dentro do CASEtambém não teve efeito, a consulta ainda trava.
21913 Kermit
2
Como é que não existe plano para o segundo caso? É apenas porque a consulta nunca termina? Se sim, você pode obter um plano estimado? Pensando se as expressões diferentes alteram as estimativas de cardinalidade e você acaba com um plano diferente.
Martin Smith
3
O ISNULLplano parece simplificar melhor. Ele tem um predicado de igualdade simples no ColleagueList, [Date]='2013-06-01'enquanto que CASEaquele tem um predicado [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date]). As linhas estimadas que saem dessa junção são 28.393 para a ISNULLversão, mas muito mais baixas 79.8para a CASEversão que afeta a opção de junção posteriormente no plano. Não sei por que haveria tal discrepância.
Martin Smith