Использование DATEDIFF в T-SQL - PullRequest
6 голосов
/ 15 мая 2009

Я использую DATEDIFF в операторе SQL. Я выбираю его, и мне нужно использовать его также в предложении WHERE. Это утверждение не работает ...

SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable
WHERE InitialSave <= 10

выдает сообщение: Неверное имя столбца "InitialSave"

Но это утверждение прекрасно работает ...

SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable
WHERE DATEDIFF(ss, BegTime, EndTime) <= 10

Программист во мне говорит, что это неэффективно (кажется, что я вызываю функцию дважды).

Итак, два вопроса. Почему первое утверждение не работает? Неэффективно ли это делать, используя второе утверждение?

Ответы [ 5 ]

7 голосов
/ 15 мая 2009

Примечание: Когда я первоначально написал этот ответ, я сказал, что индекс по одному из столбцов может создать запрос, который работает лучше, чем другие ответы (и упомянул ответ Дэна Фуллера). Однако я не думал на 100% правильно. Дело в том, что без вычисляемого столбца или индексированного (материализованного) представления полное сканирование таблицы будет обязательным , потому что сравниваются два столбца даты из той же таблицы !

Я полагаю, что в приведенной ниже информации все еще есть ценность, а именно: 1) возможность повышения производительности в правильной ситуации, например, при сравнении столбцов из разных таблиц, и 2) продвижение привычки разработчиков SQL следовать лучшим практикуйте и изменяйте их мышление в правильном направлении.

Условия изготовления Sargable

Лучшая практика, о которой я говорю, это перемещение одного столбца в одиночку с одной стороны от оператора сравнения, например:

SELECT InitialSave = DateDiff(second, T.BegTime, T.EndTime)
FROM dbo.MyTable T
WHERE T.EndTime <= T.BegTime + '00:00:10'

Как я уже сказал, это не избежит сканирования одной таблицы, однако в такой ситуации это может иметь огромное значение:

SELECT InitialSave = DateDiff(second, T.BegTime, T.EndTime)
FROM
   dbo.BeginTime B
   INNER JOIN dbo.EndTime E
      ON B.BeginTime <= E.EndTime
      AND B.BeginTime + '00:00:10' > E.EndTime

EndTime в обоих условиях теперь один на одной стороне сравнения. Предполагая, что таблица BeginTime имеет намного меньше строк, а таблица EndTime имеет индекс для столбца EndTime, это будет работать намного лучше, чем что-либо, использующее DateDiff(second, B.BeginTime, E.EndTime). Теперь это sargable , что означает, что существует действительный «аргумент поиска» - так как механизм сканирует таблицу BeginTime, он может искать в EndTime таблица. Требуется тщательный выбор того, какой столбец находится на одной стороне от оператора - может быть стоит поэкспериментировать, поставив BeginTime отдельно, выполнив некоторую алгебру для переключения на AND B.BeginTime > E.EndTime - '00:00:10'

Точность DateDiff

Я должен также указать, что DateDiff не возвращает истекшего времени, а вместо этого подсчитывает количество пересеченных границ . Если вызов DateDiff с использованием секунд вернет 1, это может означать 3 ms истекшее время или 1997 ms! По сути, это точность + - 1 единиц времени. Для большей точности + - 1/2 единицы времени вам понадобится следующий запрос, сравнивающий 0 с EndTime - BegTime:

SELECT DateDiff(second, 0, EndTime - BegTime) AS InitialSave
FROM MyTable
WHERE EndTime <= BegTime + '00:00:10'

Теперь максимальная ошибка округления составляет всего одну секунду, а не две (по сути, операция floor ()). Обратите внимание, что вы можете только вычесть тип данных datetime - чтобы вычесть значение date или time, которое вам придется преобразовать в datetime или использовать другие методы, чтобы получить более высокую точность (в целом *) 1060 *, DateDiff и, возможно, другой мусор, или, возможно, с использованием более высокой точности и деления).

Этот принцип особенно важен при подсчете больших единиц, таких как часы, дни или месяцы. DateDiff из 1 month может быть на расстоянии 62 дня (подумайте 1 июля 2013 г. - 31 августа 2013 г.)!

5 голосов
/ 15 мая 2009

Вы не можете получить доступ к столбцам, определенным в операторе select в операторе where, потому что они не генерируются до тех пор, пока не выполнится where.

Вы можете сделать это, однако

select InitialSave from 
(SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable) aTable
WHERE InitialSave <= 10

Как sidenote - это по существу перемещает DATEDIFF в оператор where с точки зрения того, где он был впервые определен. Использование функций в столбцах, где операторы приводят к тому, что индексы используются не так эффективно, и их следует избегать, если это возможно, однако, если вам нужно использовать datediff, вам нужно это сделать!

3 голосов
/ 15 мая 2009

помимо того, что это "работает", вам нужно использовать индекс

используйте вычисляемый столбец с индексом или представление с индексом, иначе вы будете сканировать таблицу. когда вы наберете достаточно строк, вы почувствуете PAIN медленного сканирования!

вычисляемый столбец и индекс:

ALTER TABLE MyTable ADD
    ComputedDate  AS DATEDIFF(ss,BegTime, EndTime)
GO
CREATE NONCLUSTERED INDEX IX_MyTable_ComputedDate  ON MyTable 
    (
    ComputedDate
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

создать представление и индекс:

CREATE VIEW YourNewView
AS
SELECT
    KeyValues
        ,DATEDIFF(ss, BegTime, EndTime) AS InitialSave
    FROM MyTable
GO
CREATE CLUSTERED INDEX IX_YourNewView
    ON YourNewView(InitialSave)
GO
2 голосов
/ 15 мая 2009

Вы должны использовать функцию вместо псевдонима столбца - то же самое с count (*) и т. Д. PITA.

1 голос
/ 15 мая 2009

В качестве альтернативы вы можете использовать вычисляемые столбцы .

...