функція зависає при нульовій справі


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передачі а на кінцеву дату?

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значень.


"у вас дуже висока частка значень NULL у стовпці" Дата "в цій таблиці?" Я не маю 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

План XML з використанням CASE, вираз 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>

План XML з використанням CASE, вираз 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>

План XML з використанням ISNULL, вираз 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>

План XML з використанням ISNULL, вираз 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 of [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версії, що впливає на вибір приєднання пізніше в плані. Не впевнений, чому існувала б така невідповідність.
Мартін Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.