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


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.


"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>

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.