функция зависает с нулевым регистром

9

Я создал функцию, которая принимает дату начала и окончания, причем дата окончания является необязательной. Затем я написал CASEв фильтре, чтобы использовать дату начала, если дата окончания не передана.

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

Когда я вызываю функцию для самого последнего месяца данных:

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

... запрос зависает. Если я укажу дату окончания:

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

... результат возвращается нормально. Я вынул код из функции и запустил его в окне запроса. Я не могу дублировать проблему на скрипке. Запрос как:

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

... тоже отлично работает.

Есть ли в запросе (ниже) что-нибудь, что может привести к зависанию функции при NULLпередаче значения a на дату окончания?

SQL Fiddle

Кермит
источник
Можете ли вы опубликовать больше логики? То, что у вас есть, не должно вызывать проблем.
Кеннет Фишер
3
Если заменить CASEс COALESCE(@dateEnd,@dateStart), это все еще появляется проблема?
ypercubeᵀᴹ
2
А с ISNULL()?
ypercubeᵀᴹ
3
Это занято или что-то ждет? Пока это "повешено", что SELECT task_state FROM sys.dm_os_tasks WHERE session_id = x показывает? Если он проводит много времени не в RUNNINGсостоянии, какие типы ожидания входят в этот сеанс sys.dm_os_waiting_tasks?
Мартин Смит
1
@ypercube Нет улучшений с COALESCE. ISNULLпочинил это.
Кермит

Ответы:

7

Часть вашего первоначального запроса выглядит следующим образом.

  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) 

Этот раздел плана показан ниже

введите описание изображения здесь

Ваш пересмотренный запрос BETWEEN @dateStart AND ISNULL(@dateEnd,@dateStart)имеет это для того же соединения

введите описание изображения здесь

Разница, кажется, в том, что она ISNULLеще больше упрощается, и в результате вы получаете более точные статистические данные о количестве элементов в следующем соединении. Это встроенная табличная функция, и вы вызываете ее с литеральными значениями, чтобы она могла что-то вроде.

 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'

И поскольку есть предикат b.[Date] = a.dравного соединения, план также показывает предикат равенства b.[Date] = '2013-06-01'. В результате оценка количества 28,393строк, скорее всего, будет довольно точной.

Для CASE/ COALESCEверсии, когда @dateStartи @dateEndявляются одним и тем же значением, тогда он упрощает ОК до того же выражения равенства и дает тот же план, но когда @dateStart = '2013-06-01'и @dateEnd IS NULLон идет только до

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

что он также применяется в качестве подразумеваемого предиката ColleagueList. Расчетное количество строк на этот раз составляет 79.8ряды.

Следующее соединение

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

colleagueTimeявляется 3,249,590строка таблицы , которая представляет собой (снова) по- видимому , куча без каких - либо полезных индексов.

Это расхождение в оценках влияет на используемый выбор соединения. ISNULLПлан выбирает хеширования , который просто просматривает таблицу один раз. COALESCEПлан выбирает вложенные циклы и оценок , что он все равно будет просто необходим сканировать таблицу один раз и иметь возможность намотать результат и повторить его 78 раз. т.е. он оценивает, что коррелированные параметры не изменятся.

Исходя из того, что план с вложенными циклами все еще продолжался после двух часов, это предположение об одном сканировании colleagueTimeкажется весьма неточным.

Что касается того, почему предполагаемое количество строк между двумя объединениями намного меньше, я не уверен, не увидев статистику по таблицам. Единственный способ, которым мне удалось исказить приблизительное количество строк, в моем тестировании было добавление загрузки NULLстрок (это уменьшило приблизительное количество строк, даже если фактическое количество возвращенных строк осталось прежним).

Расчетное количество строк в COALESCEплане с моими данными испытаний было в порядке

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

Или в SQL

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

но это не согласуется с вашим комментарием, что столбец не имеет NULLзначений.

Мартин Смит
источник
msgstr "у вас есть очень большая доля значений NULL в столбце Date в этой таблице?" У меня нет NULLзначений для дат ни в одной из этих таблиц.
Кермит
@FreshPrinceOfSO - Жаль. Я до сих пор не знаю, почему в этих двух оценках существует такое большое расхождение. В тестах я использовал фильтр растровых изображений, и дополнительный предикат, по-видимому, не изменял оценки мощности, возможно, здесь.
Мартин Смит
@FreshPrinceOfSO - Хотя, если вам захочется написать статистику, я могу попытаться выяснить это.
Мартин Смит
Я на 2008R2; когда я получу выбор схемы , dboнет в списке. Просто другие схемы, которые я не использую.
Кермит
4

Кажется, что есть проблема с типами данных. ISNULLисправил проблему (спасибо ypercube ). После некоторых исследований, COALESCEявляется эквивалентом к CASEутверждению , что я использовал:

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

Пол Уайт объясняет, что:

COALESCE( expression [ ,...n ] ) возвращает тип данных выражения с наивысшим приоритетом типа данных.

ISNULL(check_expression, replacement_value) возвращает тот же тип, что и check_expression.

Похоже, что для избежания проблем с типом данных ISNULLцелесообразно использовать только два выражения.

Выдержки из плана XML

Использование плана XMLCASE , выражение 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>

Использование плана XMLCASE , выражение 2 - это дата:

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>

Использование плана XMLISNULL , выражение 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>

Использование плана XMLISNULL , выражение 2 - это дата:

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>
Кермит
источник
Но это не объясняет, почему это сработало SELECT * FROM theFunction ('2013-06-01', '2013-06-01'). Тип данных выражения остается прежним. И оба параметра в dateлюбом случае имеют тип данных. Можете ли вы просмотреть планы выполнения?
Мартин Смит
@MartinSmith Вот план для запроса, который возвращает результат. У меня нет плана, когда второе выражение NULL.
Кермит
Приведение выражений внутри CASEтоже не дало эффекта, запрос все еще зависает.
Кермит
2
Почему нет плана для второго случая? Это только потому, что запрос никогда не заканчивается? Если да, можете ли вы получить примерный план? Хотите знать, если различные выражения меняют оценки количества элементов, и в итоге вы получаете другой план.
Мартин Смит
3
В ISNULLвыглядит план , как это упрощает лучше. У него есть простой предикат равенства для ColleagueList, [Date]='2013-06-01'тогда как у CASEодного - предикат [Date]>='2013-06-01' AND [Date]<=CASE WHEN (1) THEN '2013-06-01' ELSE NULL END AND PROBE([Bitmap1067],[Date]). Предполагаемые строки, выходящие из этого объединения, составляют 28 393 для ISNULLверсии, но намного ниже 79.8для CASEверсии, что влияет на выбор объединения позднее в плане. Не уверен, почему будет такое несоответствие.
Мартин Смит