la funzione si blocca con l'operazione null case


9

Ho creato una funzione che accetta una data di inizio e fine, con la data di fine facoltativa. Ho quindi scritto a CASEnel filtro per utilizzare la data di inizio se non viene passata alcuna data di fine.

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

Quando chiamo la funzione per il mese più recente dei dati:

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

... la query si blocca. Se specifico la data di fine:

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

... il risultato viene restituito normalmente. Ho rimosso il codice dalla funzione e l'ho eseguito correttamente all'interno di una finestra di query. Neanche io posso duplicare il problema con il violino. Una query come:

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

... funziona anche bene.

C'è qualcosa nella query (sotto) che potrebbe causare il blocco della funzione quando NULLviene passato un per la data di fine?

SQL Fiddle


Puoi pubblicare più della logica? Quello che hai lì non dovrebbe causare problemi.
Kenneth Fisher,

3
Se si sostituisce CASEcon COALESCE(@dateEnd,@dateStart), il problema persiste?
ypercubeᵀᴹ

2
E con ISNULL()?
ypercubeᵀᴹ

3
È occupato o sta aspettando qualcosa? Mentre è "appeso" che cosa SELECT task_state FROM sys.dm_os_tasks WHERE session_id = x mostra? Se passa molto tempo non nello RUNNINGstato in quali tipi di attesa sta entrando quella sessione sys.dm_os_waiting_tasks?
Martin Smith,

1
@ypercube Nessun miglioramento con COALESCE. ISNULLaggiustato.
Kermit,

Risposte:


7

Parte della query iniziale è la seguente.

  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) 

Quella sezione del piano è mostrata di seguito

inserisci qui la descrizione dell'immagine

La tua query rivista BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart)ha questo per lo stesso join

inserisci qui la descrizione dell'immagine

La differenza sembra essere quella che ISNULLsemplifica ulteriormente e di conseguenza si ottengono statistiche sulla cardinalità più accurate andando al join successivo. Questa è una funzione con valori di tabella incorporata e la stai chiamando con valori letterali in modo che possa fare qualcosa del genere.

 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 poiché esiste un predicato equi join, b.[Date] = a.dil piano mostra anche un predicato di uguaglianza b.[Date] = '2013-06-01'. Di conseguenza, 28,393è probabile che la stima della cardinalità delle righe sia piuttosto accurata.

Per la versione CASE/ COALESCEquando @dateStarte @dateEndhanno lo stesso valore, allora semplifica OK con la stessa espressione di uguaglianza e fornisce lo stesso piano ma quando @dateStart = '2013-06-01'e @dateEnd IS NULLva solo fino a

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

che si applica anche come predicato implicito ColleagueList. Il numero stimato di righe questa volta è 79.8righe.

Il prossimo join è

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

colleagueTimeè una 3,249,590tabella di righe che è (di nuovo) apparentemente un heap senza indici utili.

Questa discrepanza nelle stime influisce sulla scelta del join utilizzata. Il ISNULLpiano sceglie un hash join che scansiona il tavolo una sola volta. Il COALESCEpiano sceglie un'unione di cicli nidificati e stima che sarà ancora necessario scansionare la tabella una volta ed essere in grado di eseguire lo spooling del risultato e riprodurlo 78 volte. cioè stima che i parametri correlati non cambieranno.

Dal fatto che il piano di cicli annidati stava ancora andando avanti dopo due ore, questa ipotesi di una singola scansione contro colleagueTimesembra essere altamente imprecisa.

Per quanto riguarda il motivo per cui il numero stimato di righe tra i due join è molto più basso, non sono sicuro senza poter vedere le statistiche sulle tabelle. L'unico modo in cui sono riuscito a distorcere i conteggi delle righe stimati così tanto nei miei test è stato l'aggiunta di un carico di NULLrighe (questo ha ridotto il conteggio delle righe stimato anche se il numero effettivo di righe restituite è rimasto lo stesso).

Il conteggio delle righe stimato nel COALESCEpiano con i miei dati di test era nell'ordine di

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

O in SQL

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

ma questo non quadra con il tuo commento che la colonna non ha NULLvalori.


"hai una percentuale molto elevata di valori NULL nella colonna Data in quella tabella?" Non ho NULLvalori per le date in nessuna di quelle tabelle.
Kermit,

@FreshPrinceOfSO - È un peccato. Non ho ancora idea del perché vi sia una discrepanza così grande nelle due stime allora. Nei test ho fatto il filtro bitmap e il predicato aggiuntivo non sembrava alterare le stime della cardinalità, forse lo fa qui.
Martin Smith,

@FreshPrinceOfSO - Anche se hai voglia di scrivere le statistiche, posso provare a capirlo.
Martin Smith,

Sono su 2008R2; quando arrivo a Scegli schemi , dbonon è elencato. Solo altri schemi che non utilizzo.
Kermit,

4

Sembra che ci sia stato un problema con i tipi di dati. ISNULLrisolto il problema (grazie ypercube ). Dopo alcune ricerche, COALESCEè l'equivalente alla CASEdichiarazione che stavo usando:

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

Paul White spiega che:

COALESCE( expression [ ,...n ] ) restituisce il tipo di dati dell'espressione con la massima precedenza del tipo di dati.

ISNULL(check_expression, replacement_value) restituisce lo stesso tipo di check_expression.

Per evitare problemi relativi al tipo di dati, sembra ISNULLsia la funzione appropriata da utilizzare per gestire solo due espressioni.

Estratti di piano XML

Utilizzo del piano XMLCASE , espressione 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>

Utilizzo del piano XMLCASE , espressione 2 è una 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>

Utilizzo del piano XMLISNULL , espressione 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>

Utilizzo del piano XMLISNULL , espressione 2 è una 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>

Ma questo non spiega perché ha funzionato bene SELECT * FROM theFunction ('2013-06-01', '2013-06-01'). Il tipo di dati di espressione è sempre lo stesso. E comunque entrambi i parametri sono datetipo di dati. Puoi visualizzare i piani di esecuzione?
Martin Smith,

@MartinSmith Ecco il piano per la query che restituisce un risultato. Non ho un piano quando è la seconda espressione NULL.
Kermit,

Anche il cast delle espressioni all'interno di CASEha avuto alcun effetto, la query si blocca ancora.
Kermit,

2
Come mai nessun piano per il secondo caso? È solo perché la query non finisce mai? In tal caso è possibile ottenere un piano stimato? Chiedendosi se le diverse espressioni cambiano le stime della cardinalità e si finisce con un piano diverso.
Martin Smith,

3
Il ISNULLpiano sembra semplificare meglio. Ha un semplice predicato sull'uguaglianza in ColleagueList di [Date]='2013-06-01'mentre CASEquello ha un predicato su [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date]). Le righe stimate che escono da quel join sono 28.393 per la ISNULLversione ma molto più basse 79.8per la CASEversione che ha effetto sulla scelta del join più avanti nel piano. Non so perché ci sarebbe una tale discrepanza.
Martin Smith,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.