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

StackOverflow https://stackoverflow.com/questions/2775

Вопрос

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

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

Я думаю, что может быть лучший и более элегантный способ.

Требования:

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

Решение

SQL Server 2008 и выше

В SQL Server 2008 и выше, конечно, самый быстрый способ - это Convert(date, @date).Это может быть возвращено к 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;

Какой - то Бессвязный Анализ

Несколько заметок по этому поводу.Прежде всего, если вы просто выполняете ГРУППИРОВКУ ПО или сравнение, нет необходимости преобразовывать обратно в datetime.Таким образом, вы можете сэкономить немного ресурсов процессора, избегая этого, если только вам не нужно конечное значение для целей отображения.Вы даже можете СГРУППИРОВАТЬ По неконвертированному значению и указать преобразование только в предложении 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
  • круглый требуется ли процессорное время для обратного перехода к datetime.

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

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

Еще одна интересная вещь заключается в том, что базовая стоимость почти равна единовременной Convert(date) метод (стоимость которого должна составлять почти 0, поскольку сервер может внутренне извлечь целочисленную дневную часть прямо из первых четырех байтов 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)

Другие советы

В SQL Server 2008 появился новый Дата тип данных и это упрощает эту проблему до:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)

Ицик Бен-Ган в Вычисления ДАТЫ и ВРЕМЕНИ, Часть 1 (Журнал SQL Server, февраль 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)

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

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

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

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

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top