Миграция на SQL Server 2017: один и тот же запрос иногда не возвращает строк

Я только что обновился до SQL Server 2017 с SQL Server 2014 (пока только для среды разработки) и заметил очень странное поведение: один и тот же запрос имеет два разных влияния на оба сервера.

Запрос был переписан и сведен к минимуму для примера:

SELECT * FROM eth_Mandate M WHERE customerId = 1 AND M.isActive = 1

Поле isActive это вычисляемый столбец (возвращает 1, если мандат активен, 0 в противном случае), определенный как:

[isActive]  AS (CASE WHEN GETDATE() >= ISNULL([man_start],GETDATE()) AND GETDATE() <= isnull(dateadd(day,(1),[man_stop]),GETDATE()) THEN 1 ELSE 0 END)

Для SQL Server 2014 это всегда работает. Для 2017 года, один раз в 4-5 раз, он не возвращает строк, как иногда isActive вычисляется как 0.

Зачем? Я глубоко искал и хотел поделиться им, чтобы вы могли найти мой собственный ответ ниже объяснения, которое я понял, основываясь на моих тестах.

1 ответ

Проблема в том, что в ISNULL у нас есть datetime2 (поле man_stop) и getdate как резервный (возвращает datetime), и, соответственно, ISNULL приводит второй параметр на основе первого параметра.... Следовательно, мы сравниваем getdate () как datetime с помощью getdate () в качестве datetime2. Обратите внимание, что man_stop (конец мандата клиента) в большинстве случаев не является нулевым, поскольку мандат выполняется.

Затем вы должны взглянуть на эту статью и, в частности, на предложение:

Что касается временной части, SQL Server начинается с 0 и увеличивает числовое значение для каждого 0,003 секунды после полуночи. Что означает? если время 12:15:00:002 фактически сохраняется как 12:15:00:003, но сравнивается с 12:15:00.002, то есть не равно!!

РЕШЕНИЕ 1

Итак, чтобы решить проблему с SQL Server 2017 (хотя с SQL Server 2014 проблем нет), я просто преобразовал свои поля datetime2 в datetime, чтобы получить правильное сравнение с getdate () как datetime:

[isActive]  AS (case when getdate()>=isnull(CONVERT([datetime],[man_start]),getdate()) AND getdate()<=isnull(dateadd(day,(1),CONVERT([datetime],[man_stop])),getdate()) then (1) else (0) end)

РЕШЕНИЕ 2

Или я мог бы также преобразовать все datetime в datetime2. Вместо GETDATE() используйте SYSDATETIME(), возвращая datetime2(7). Поскольку мои два поля просто datetime2 (точность не определена), сравнение было хуже... но при преобразовании datetime2 в datetime2 (7) оно работало как чудо. Так и становится:

(case when SYSDATETIME () >= isnull(convert(datetime2(7), [man_start]),SYSDATETIME()) AND SYSDATETIME() <= isnull(dateadd(day,(1),convert(datetime2(7), [man_stop])),SYSDATETIME()) then (1) else (0) end)

РЕШЕНИЕ 3

Измените уровень совместимости базы данных на 120 или ниже, чтобы сохранить устаревшее поведение при сравнении типов datetime и datetime 2. Это критическое изменение было введено в SQL Server 2016.

РЕШЕНИЕ 4 (раствор применяется)

Используйте тот же тип данных и избегайте преобразования типов. Изменить тип полей man_start а также man_stop как datetime2(7) (вместо datetime2, который является datetime2(3)), чтобы соответствовать возвращаемому типу функции SYSDATETIME() (datetime2(7)).

ALTER TABLE myTable ALTER COLUMN [man_start] datetime2(7) NULL
ALTER TABLE myTable ALTER COLUMN [man_stop] datetime2(7) NULL  

Таким образом, мой вычисляемый столбец становится следующим: [isActive] AS (СЛУЧАЙ, КОГДА SYSDATETIME() >= ISNULL([man_start],SYSDATETIME()) И SYSDATETIME() <= isnull(dateadd(день,(1),[man_stop]),SYSDATETIME()) ТОГДА 1 ИЛИ 0 КОНЕЦ)

Преимущества: сохраняется уровень совместимости, избегайте преобразования типов (лучше производительность)

Другие вопросы по тегам