Как удалить временную часть значения datetime (SQL Server)?

85

Вот что я использую:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

Думаю, есть способ получше и элегантнее.

Требования:

  • Это должно быть как можно быстрее (чем меньше заброса, тем лучше).
  • Конечный результат должен быть datetimeтипом, а не строкой.
Натан Бедфорд
источник

Ответы:

116

SQL Server 2008 и выше

В SQL Server 2008 и выше, конечно, самый быстрый способ Convert(date, @date). При необходимости его можно вернуть в a datetimeили datetime2.

Что действительно лучше всего в SQL Server 2005 и более ранних версиях?

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

Преобразования с плавающей запятой неточны

Во-первых, я бы не стал конвертировать datetimeв float, потому что он не конвертируется правильно. Вам может сойти с рук аккуратное удаление времени, но я думаю, что использовать его - плохая идея, потому что он неявно сообщает разработчикам, что это безопасная операция, а это не так . Взглянуть:

declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops

Это не то, чему мы должны учить людей в нашем коде или в наших примерах в Интернете.

Кроме того, это даже не самый быстрый способ!

Доказательство - Тестирование производительности

Если вы хотите самостоятельно выполнить несколько тестов, чтобы увидеть, как на самом деле складываются разные методы, вам понадобится этот установочный скрипт, чтобы выполнить тесты дальше:

create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
   insert AllDay
   select * from (
      select Tm =
         DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
      from AllDay
   ) X
   where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay;  -- 25,920,000 rows

Обратите внимание, что это создает таблицу размером 427,57 МБ в вашей базе данных, и ее выполнение займет примерно 15–30 минут. Если ваша база данных мала и настроена на 10% -ный рост, это займет больше времени, чем если вы сначала установите достаточно большой размер.

Теперь о самом сценарии тестирования производительности. Обратите внимание, что целенаправленно не возвращать строки обратно клиенту, поскольку это безумно дорого для 26 миллионов строк и скроет различия в производительности между методами.

Результаты производительности

set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
    @dd date,
    @d datetime,
    @di int,
    @df float,
    @dv varchar(10);

-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.

-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;

Некоторый анализ азартных игр

Некоторые заметки по этому поводу. Прежде всего, если вы просто выполняете GROUP BY или сравнение, нет необходимости конвертировать обратно в datetime. Таким образом, вы можете сэкономить немного ресурсов процессора, избегая этого, если вам не нужно окончательное значение для отображения. Вы даже можете GROUP BY по непреобразованному значению и поместить преобразование только в предложение SELECT:

select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

Кроме того, посмотрите, как числовые преобразования требуют немного больше времени для обратного преобразования datetime, а varcharпреобразование почти удваивается? Это показывает, какая часть ЦП посвящена вычислению даты в запросах. Есть части использования ЦП, которые не связаны с вычислением даты, и в приведенных выше запросах это похоже на 19875 мс. Затем для преобразования требуется некоторая дополнительная сумма, поэтому при двух преобразованиях эта сумма расходуется примерно вдвое.

Более исследование показывает , что по сравнению Convert(, 112), то Convert(, 101)запрос имеет некоторые дополнительные расходы на процессор (так как она использует больше varchar?), Потому что второе преобразование обратно dateне стоит столько , сколько в качестве начального преобразования в varchar, но Convert(, 112)это ближе к тому же 20000 мс базовая стоимость процессора.

Вот те расчеты процессорного времени, которые я использовал для вышеупомянутого анализа:

     method   round  single   base
-----------  ------  ------  -----
       date   21324   19891  18458
        int   23031   21453  19875
   datediff   23782   23218  22654
      float   36891   29312  21733
varchar-112  102984   64016  25048
varchar-101  123375   65609   7843
  • round - это время ЦП для возврата к datetime.

  • single - это процессорное время для однократного преобразования в альтернативный тип данных (тот, который имеет побочный эффект удаления временной части).

  • база является вычисление вычитания из singleразности между двумя вызовами: single - (round - single). Это приблизительное значение, предполагающее преобразование в этот тип данных и из него, и datetimeоно примерно одинаково в обоих направлениях. Похоже, что это предположение не идеально, но близко, потому что все значения близки к 20000 мс за одним исключением.

Еще одна интересная вещь заключается в том, что базовая стоимость почти равна стоимости одного Convert(date)метода (которая должна быть почти нулевой, поскольку сервер может внутренне извлекать целочисленную дневную часть прямо из первых четырех байтов типа datetimeданных).

Заключение

Таким образом, похоже, что varcharметод однонаправленного преобразования занимает около 1,8 мкс, а метод однонаправленного преобразования - DateDiffоколо 0,18 мкс. Я основываю это на самом консервативном «базовом времени ЦП» в моем тестировании, которое составляет 18458 мс для 25 920 000 строк, поэтому 23218 мс / 25920000 = 0,18 мкс. Кажущееся 10-кратное улучшение кажется большим, но, откровенно говоря, оно довольно мало, пока вы не имеете дело с сотнями тысяч строк (617 тыс. Строк = 1 секунда экономии).

Даже с учетом этого небольшого абсолютного улучшения, на мой взгляд, этот DateAddметод выигрывает, потому что это лучшее сочетание производительности и ясности. Ответ, который требует «магического числа», когда- 0.50000004нибудь кого-нибудь укусит (пять нулей или шесть ???), плюс его труднее понять.

Дополнительные замечания

Когда я получаю какое - то время я собираюсь изменить , 0.50000004чтобы '12:00:00.003'увидеть , как он делает. Он преобразуется в то же datetimeзначение, и мне его гораздо легче запомнить.

Для тех, кто заинтересован, приведенные выше тесты были запущены на сервере, где @@ Version возвращает следующее:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) 9 июля 2008 г. 14:43:34 Авторские права (c) 1988-2008 гг. Microsoft Corporation Standard Edition для Windows NT 5.2 (сборка 3790: пакет обновления 2)

Эрике
источник
1
+1 Кстати, на какой версии SQL Server вы это тестировали?
Мартин Смит,
1
Похоже, у вас в столе есть одиночные и круглые задние части. Кроме того, есть ли разница во времени, если использовать charвместо varchar?
Гейб
1
@Gabe спасибо, исправлено. Char выглядит точно так же, как varchar.
ErikE
В Oracle это есть select round(sysdate) from dualи нам определенно нужно в Sql Server.
Денис Валеев
3
@Roman Если вы работаете с SQL Server 2008 и новее, да, преобразование в dateтип данных происходит быстрее всего, как показано в моих тестах выше.
ErikE
30

SQL Server 2008 имеет новый тип данных даты, и это упрощает решение этой проблемы:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)
Марек Грзенкович
источник
1
Я ошибочно ввел 0218 вместо 2018 в качестве года, и DATEADD(DATEDIFF())метод сокращения временной части вызывает исключение. Когда я возвращаю результат обратно к datetime2вашему методу, он отлично работаетselect cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
Бернхард Доблер
18

Ицик Бен-Ган в DATETIME Calculations, Part 1 (SQL Server Magazine, февраль 2007 г.) показывает три метода выполнения такого преобразования ( от самого медленного к самому быстрому ; разница между вторым и третьим методами небольшая):

SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)

SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)

SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)

Вашу технику (заброс в плавание ) предлагает читатель в апрельском номере журнала. По его словам, по характеристикам он сопоставим со второй методикой, представленной выше.

Марек Грзенкович
источник
1
На мой взгляд, плавать - не лучший вариант. Пожалуйста, посмотрите мой ответ
ErikE
1
@Emtucifor Я согласен с тем, что третий метод очень неясен из-за значения 0,50000004 , но он самый быстрый, и ваши тесты подтверждают это . Таким образом, он как можно быстрее удовлетворяет требованиям.
Marek Grzenkowicz
1
@Emtucifor Кроме того, вот что в статье, на которую я ссылаюсь, говорится о значении 0,50000004 : Хотя это выражение короткое (и эффективное, как я вскоре продемонстрирую), я должен сказать, что мне это неудобно . Я не уверен, что могу точно указать, почему - может быть, потому, что это слишком технически, и вы не можете увидеть в нем логику, связанную с датой и временем.
Marek Grzenkowicz
2
Если мы собираемся использовать этот метод, я бы предпочел его SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime), так как он что-то значит для меня и его гораздо легче запомнить.
ErikE
6
Теперь это самый быстрый в SQL 2008: Convert(date, GetDate()).
ErikE
12

Ваш CAST- FLOOR- CASTуже кажется оптимальным способом, по крайней мере, на MS SQL Server 2005.

В некоторых других решениях, которые я видел, есть преобразование строк, как Select Convert(varchar(11), getdate(),101)в них, которое медленнее в 10 раз.

Майкл Штум
источник
1
Мы используем метод, предложенный Майклом Штумом в одном из наших продуктов, и он работает как шарм.
Крис Робертс,
3
Это далеко не лучший способ. Пожалуйста, посмотрите мой ответ на этой же странице.
ErikE
4

Пожалуйста, попробуй:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]
шрихари
источник
1

SQL2005: я рекомендую cast вместо dateadd. Например,

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

в среднем на 10% быстрее в моем наборе данных, чем

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(и преобразование в smalldatetime было еще быстрее)

user4217069
источник