Як видалити часову частину значення дати та часу (SQL Server)?


84

Ось що я використовую:

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

Я думаю, що може бути кращий і елегантніший спосіб.

Вимоги:

  • Це має бути якомога швидше (чим менше кастингу, тим краще).
  • Кінцевим результатом має бути datetimeтип, а не рядок.

Відповіді:


116

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;

Деякі аналізи

Деякі нотатки з цього приводу. Перш за все, якщо ви просто виконуєте 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)методу (який повинен бути майже 0 витрат, оскільки сервер може внутрішньо витягти цілочисельну частину дня прямо з перших чотирьох байтів типу datetimeданих).

Висновок

Отже, це виглядає так, що varcharметод однонаправленого перетворення займає близько 1,8 мкс, а DateDiffметод однонаправленого - близько 0,18 мкс. Я базую це на найбільш консервативному "базовому процесорі" часу в моєму тестуванні 18458 мс загалом для 25 920 000 рядків, тобто 23218 мс / 25920000 = 0,18 мкс. Очевидне покращення в 10 разів здається багато, але, відверто кажучи, досить невелике, поки ви не матимете справу з сотнями тисяч рядків (617 000 рядків = економія за 1 секунду).

Навіть з огляду на це невелике абсолютне вдосконалення, на мій погляд, DateAddметод виграє, оскільки це найкраще поєднання продуктивності та чіткості. Відповідь, яка вимагає "магічного числа", - 0.50000004це колись когось вкусить (п’ять нулів чи шість ???), плюс це важче зрозуміти.

додаткові нотатки

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

Для зацікавлених вищезазначені тести проводились на сервері, де @@ Версія повертає наступне:

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 (Build 3790: Service Pack 2)


1
+1 У якій версії SQL Server ви, до речі, перевіряли це?
Martin Smith

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

Іцік Бен-Ган у частині 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)

Вашу техніку (кастинг для плавання ) пропонує читач у квітневому номері журналу. За його словами, він має продуктивність, порівнянну з ефективністю другої техніки, представленої вище.


1
На мій погляд, кастинг плавати не найкраще. Будь ласка, перегляньте мою відповідь
ErikE

1
@Emtucifor Я згоден, що 3-й метод дуже неясний через значення 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


1

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

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

в середньому приблизно на 10% швидше на моєму наборі даних, ніж

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

(а кастинг на невеликий час був ще швидшим)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.