Найкращий підхід до видалення часової частини дати в SQL Server


514

Який метод забезпечує найкращу ефективність при видаленні частини часу з поля дати в SQL Server?

a) select DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)

або

b) select cast(convert(char(11), getdate(), 113) as datetime)

Другий метод дійсно надсилає ще кілька байтів, але це може бути не так важливо, як швидкість конверсії.

І те й інше здається дуже швидким, але може бути різниця у швидкості при роботі з сотнями тисяч і більше рядків?

Крім того, чи можливо, що існують ще кращі методи позбавлення від часової частини дати в SQL?


1
Я спробував це на одному мільйоні записів в одній з моїх виробничих таблиць, і в жодному разі не міг отримати точне читання про продуктивність. Обидва способи повернули точно однаковий обсяг даних.
Стівен Перелсон

9
На 18 000 000 рядків це те, що я знайшов (SQL Server 2008): метод b приблизно на 24% повільніше, ніж метод a. КАСТ (ПІДНЯТЬ (CAST (getdate () ЯК ПЛАТ)) ЯК ДАТЕТИМ) на 3,5% повільніше, ніж метод a. Спосіб a, здається, є переможцем щодо продуктивності. Дякую всім за чудові відповіді.
Стівен Перелсон

46
Чому чорт не має вбудованої функції SQL, щоб це зробити? !!
Гері Макгілл

10
З цим впорається новий тип даних DATE SQL 2008.
Філіп Келлі

Відповіді:


557

Суворо, метод aє найменш ресурсоємним:

a) select DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)

Доведено, що інтенсивність процесора менша, за однакову загальну тривалість мільйон рядків, якийсь один із занадто великим часом на руках: Найефективніший спосіб у SQL Server отримати дату + час?

Я бачив подібний тест і в інших місцях з подібними результатами.

Я віддаю перевагу DATEADD / DATEDIFF тому, що:

  • varchar підлягає проблемам мови / дати Формат
    Приклад: Чому мій вираз CASE не детермінований?
  • float покладається на внутрішнє зберігання
  • він продовжує працювати перший день місяця, завтра тощо, змінюючи базу "0"

Редагувати, жовтень 2011 р

Для SQL Server 2008+ ви можете CAST dateтобто CAST(getdate() AS date). Або просто використовувати dateтип даних, щоб не було часу для видалення.

Редагувати, січень 2012 р

Працюючий приклад того, наскільки це гнучко: Потрібно обчислити закруглену цифру часу або дати на сервері sql

Редагувати, травень 2012 року

Не використовуйте це в пунктах WHERE тощо, не замислюючись: додавання функції або CAST до стовпця недійсне використання індексу. Дивіться номер 2 тут: http://www.simple-talk.com/sql/t-sql-programming/ten-common-sql-programming-mistakes/

Тепер у цьому є приклад пізніших версій оптимізатора SQL Server, які керують CAST на сьогодні правильно, але, як правило, це буде поганою ідеєю ...

Редагувати, вересень 2018 року, для datetime2

DECLARE @datetime2value datetime2 = '02180912 11:45' --this is deliberately within datetime2, year 0218
DECLARE @datetime2epoch datetime2 = '19000101'

select DATEADD(dd, DATEDIFF(dd, @datetime2epoch, @datetime2value), @datetime2epoch)

3
@David Sopko для жовтня 2011 редагувати, тоді код буде таким чином: вибрати дату (GETDATE () як дата)
Choco Smith

1
Для новіших версій SQL використання дати замість дати не дозволяє працювати з годинами. Використовуйте наступний зразок: оголосити noTime date = getdate (), withTime datetime = getdate () вибрати @ noTime, @ withTime
ozkary

1
акторський склад як дата чудовий, якщо вам просто потрібна дата. Однак часто вам потрібна поточна дата о півночі, щоб потім зробити деякі подальші маніпуляції з датою. час DATEданих є відверто обмежуючим, що він дозволить вам робити стосовно таких речей, як dateadd, dateiff та взаємодія з іншими типами даних дата / час. У цих випадках DATEADD()підхід панує король.
Xedni

Це працює не для кожної дати. Я помилково вписав 0218замість 2018року, і DATEDIFFчастина вашої заяви кидає виняток. The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range datetime valueСпробуйте:select DATEDIFF(dd, 0, convert(datetime2(0), '0218-09-12', 120))
Bernhard Döbler,

1
@ BernhardDöbler у липні 2009 р., Коли я відповів, "0218" була б дійсною датою, щоб у вас не було цього далеко. Також "0" не перетворюється на 19000101 для datetime2. Спробуйте цей вибірSELECT DATEDIFF(dd, '19000101', convert(datetime2(0), '0218-09-12', 120))
gnn

69

У SQL Server 2008 ви можете використовувати:

CONVERT(DATE, getdate(), 101)

13
Третій аргумент абсолютно не має ніякого відношення до результату при перетворенні з a datetimeв a date, і тому ваше рішення ефективно зводиться до справедливого CONVERT(DATE,getdate()), що вже було запропоновано не раз.
Андрій М

Просто використовуйте CAST(GETDATE() AS DATE)або строго ANSI, CAST(CURRENT_TIMESTAMP AS DATE)що я вважаю марним. Залишайтеся з першим.
Іванзіньо

52

Звичайно, це стара нитка, але для її завершення.

У SQL 2008 ви можете використовувати тип даних DATE, щоб ви могли просто зробити:

SELECT CONVERT(DATE,GETDATE())

21
SELECT CAST(FLOOR(CAST(getdate() AS FLOAT)) AS DATETIME)

... не є вдалим рішенням, згідно коментарів нижче.

Я б видалив цю відповідь, але я залишу це як приклад, оскільки вважаю, що пояснення коментарів, чому це не гарна ідея, все ще корисне.


Дивіться відповідь GBN, багато хто досліджував це. DATETIME НЕ зберігаються як поплавці, і тому використання DATEADD / DATEDIFF дозволяє уникнути математичних маніпуляцій, необхідних для КАСТУВАННЯ між типами.
MatBailie

Я можу погодитись, що ви можете уникнути трансляції з DATETIME в FLOAT з причини, яку ви описали, але в цьому випадку чи не неявна конверсія з нуля в опції OP (a) також не є проблемою? Гммм ... Я гадаю, що в цьому випадку це не ПЛОТ і що сервер, мабуть, досить розумний, щоб відкинути інформацію про час. Гаразд, поступаюсь :-)
Гері Макгілл

0 дійсно є неявним перетворенням з числового типу (INT я б здогадався) в DATETIME. Оскільки це постійне вираження, однак оптимізатор може це зробити під час компіляції для збережених процедур і йому потрібно зробити це лише один раз для динамічного виконання SQL. Коротше кажучи, для цього є разовий наклад, запит на основі FLOAT має рівноцінні накладні витрати для кожного рядка.
MatBailie

Кидати плавати - жахливо не точно. Цю відповідь слід видалити. Ніхто не повинен використовувати цей код.
usr

3
Не кажучи вже про те, що кинути плавати і повертатись до дату не можна безпечно - float не має достатньої точності. Тому я думаю, що це взагалі не можна рекомендувати. Дивіться цю публікацію для більш детальної інформації .
ErikE

17

У SQL Server 2008 існує тип дати DATE (також тип даних TIME).

CAST(GetDate() as DATE)

або

declare @Dt as DATE = GetDate()

Це те, що я використав, і це добре працювало. Здається, найпростіша відповідь. Будь-які недоліки щодо використання спільно з W / CONVERT?
joelmdev

1
CAST та CONVERT є еквівалентними за функцією. Різниця полягає в тому, що CAST є частиною стандарту ANSI, тоді як CONVERT характерний для T-SQL. Тож використовуйте CAST, де це можливо.
тройка

@troy Я використовую CAST, тому що я можу зберегти 3 набрані букви, і синтаксис зрозуміліший за CONVERT, стандартна частина ANSI - нічого не варті
Ivanzinho

8

Ось ще одна відповідь з іншого дублюючого запитання:

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

Цей метод магічного числення працює трохи швидше, ніж метод DATEADD. (Схоже, ~ 10%)

Час процесора на кілька мільйонів записів на мільйон:

DATEADD   MAGIC FLOAT
500       453
453       360
375       375
406       360

Але зауважте, що ці цифри, можливо, не мають значення, оскільки вони ДУЖЕ швидкі. Якщо у мене не було записів на 100 000 або більше, я не міг навіть отримати час процесора для читання вище нуля.

Враховуючи той факт, що DateAdd призначений для цієї мети і є більш надійним, я б сказав, використовуйте DateAdd.


1
Це жахливо. Я ніколи не піддавав би свої дані таким чином. Хто знає, чи правильно це для всіх дат, а не лише тих, які ви перевірили.
usr

@usr О, це правильно, це просто магічне число, і його не слід використовувати з цієї причини. Якщо ви хочете перевірити його правильність, просто запишіть усі можливі дати на один день у таблицю та перевірте результати! Також дивіться цю публікацію для отримання додаткової інформації.
ErikE

@ErikE хороший момент. Ваша відповідь передбачає можливість використання '12:00:00.003', хоча я вважаю, що набагато краще.
usr

6
SELECT CAST(CAST(GETDATE() AS DATE) AS DATETIME)

4
Дійсний варіант, так. Однак пропонується не раз у цій темі.
Андрій М

Чесно кажучи, це рішення є найпростішим для читання. Мій goto
BelgoCanadian

5

Мені дійсно подобається:

[date] = CONVERT(VARCHAR(10), GETDATE(), 120)

Код 120формату приводить дату до стандарту ISO 8601:

'YYYY-MM-DD' or '2017-01-09'

Супер простий у використанні в dplyr ( R) та pandas ( Python)!


3

ПОДЕРЖАЙТЕ!

Метод a) і b) НЕ завжди має однаковий вихід!

select DATEADD(dd, DATEDIFF(dd, 0, '2013-12-31 23:59:59.999'), 0)

Вихід: 2014-01-01 00:00:00.000

select cast(convert(char(11), '2013-12-31 23:59:59.999', 113) as datetime)

Вихід: 2013-12-31 00:00:00.000

(Тестовано на MS SQL Server 2005 та 2008 R2)

EDIT: Відповідно до коментаря Адама, це не може статися, якщо ви читаєте значення дати з таблиці, але це може статися, якщо ви надаєте значення дати у прямому значенні (наприклад: як параметр збереженої процедури, що називається через ADO.NET).


1
.999 не можна зберігати на SQL Server у DATETIMEстовпці. Найвищий доступний - .997 З: msdn.microsoft.com/en-us/library/ms187819.aspx ви побачите, що значення округлені, щоб тисячне місце було 0, 3 або 7. ОП не побачить значення вашого тесту в їх таблицях.
Адам Венгер

Ви праві. Я не хотів розміщувати це як відповідь на питання ОП, а як коментар, який бачать інші, але у мене було лише 11 балів репутації, і 15 потрібно для коментарів.
broslav

У першому фрагменті константа рядка неявно перетворюється на дату, у другому - рядок залишається рядком (а 113 просто ігнорується).
Андрій М

2

Спершу час вставки / оновлень. Що стосується перетворення на ходу, ніщо не може перемогти визначену користувачем функцію ремонтопридатності:

select date_only(dd)

Реалізація date_onlyможе бути будь-якою, що вам подобається - тепер вона абстрагується і виклик коду набагато чистіше.


Я колись придумав спусковий механізм для скрабування разів із вибраних стовпців. Якщо дані не можуть бути поганими, вам не доведеться їх очищати.
Філіп Келлі

2
Існує і зворотний бік підходу до АДС, вони не є SARGable. Якщо використовується в JOINs або WHERE, оптимізатор не може використовувати INDEXes для підвищення продуктивності. Однак використання підходу DATEADD / DATEDIFF є SARGable і зможе отримати вигоду від INDEXes. (Мабуть, метод FLOAT теж SARGable)
MatBailie

1
@MatBailie Я прошу відрізнятися! UDF, безумовно, не є SARGable, але жодна дата не додається, і не конвертувати плавати! WHERE DateAdd(DateDiff(Column)) = @DateValueне використовуватиме індекс. З іншого боку, WHERE Column >= dbo.UDF(@DateValue) AND Column < dbo.UDF(@DateValue + 1) це SARGable. Тож будьте уважні, як це ставите.
ErikE

2

Дивіться це питання:
Як я можу обрізати дату в SQL Server?

Що б ви не робили, не використовуйте струнний метод . Ось про найгірший спосіб, коли ви могли це зробити.


Дякую, я зрозумів, що про це потрібно було попросити раніше. Дивно, що в моїх експериментах вказувалося, що метод float насправді повільніше на 3,5% на SQL Server 2008, ніж метод dateadd (dd, 0, dateiff (dd, 0, getDate ())). Я виконував свої тести багато разів для кожного методу, і сервер бази даних на той час не використовувався ні для чого іншого.
Стівен Перелсон

Скажімо, що я скептично ставлюсь до показників, зроблених будь-ким, хто не продемонстрував, що вони регулярно та дуже науково працюють як частина своєї роботи. Навіть орієнтир Томаса у посиланні від gbn має деякі очевидні проблеми, коли ви дивитесь на це. Це не робить помилку не обов'язково, просто не остаточно. Метод лиття / підлога / лиття був прийнятим найшвидшим способом протягом дуже тривалого часу, і я підозрюю, що колись це було безперечно правдою. Це сказало: я починаю переглядати це; особливо для sql-сервера 2008 року, де це все одно зовсім непотрібно.
Joel Coehoorn

1
Метод струн надзвичайно простий у використанні, для читання та запам'ятовування. Це дуже важливі фактори, які, я думаю, ви недооцінюєте!
Бен

1
@JoelCoehoorn, стиль перетворення 121 називається "ODBC Canonical". Він не змінюється в залежності від місцеположення та місцевості. Струнний трюк також легко узагальнити до року, року + місяця, дня, години чи хвилини.
Бен

1
@Ben Строковий трюк вчить розробників використовувати перетворення рядків. Вони працюють , але математика дат далеко, набагато перевершує, з багатьох причин, не меншою мірою швидкість - але навіть більше, те, що вчитися працювати з датами як номери, надає розробнику і його розумовим здібностям бути флюїдом з маніпуляцією з цифрами в коді.
ErikE

2

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

 CAST(
FLOOR( CAST( GETDATE() AS FLOAT ) )
AS DATETIME
)

вдруге я знайшов це рішення ... я схопив цей код


1
Перетворення в плавати не є безпечним .
ErikE

2
CAST(round(cast(getdate()as real),0,1) AS datetime)

Цей метод не використовує функцію string. Dateв основному є реальним типом даних з цифрами до десяткових - це частка дня.

це я думаю, буде швидше, ніж багато.


1
Кастинг як поплавок не є безпечним .
ErikE


2

виберіть CONVERT (char (10), GetDate (), 126)


У чому полягає принципова відмінність вашої пропозиції від методу, згаданого у відповіді @ broslav, або від методу, який визначався як найповільніший у цій темі (те саме посилання, що і у прийнятій відповіді)?
Андрій М

1

Я думаю, ти маєш на увазі cast(floor(cast(getdate()as float))as datetime)

Real - це лише 32 біти і може втратити частину інформації

Це найшвидше cast(cast(getdate()+x-0.5 as int)as datetime)

... хоча швидше лише на 10%(about 0.49 microseconds CPU vs. 0.58)

Це було рекомендовано та займає те саме час у моєму тесті: DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)

У SQL 2008 функції SQL CLR приблизно в 5 разів швидше, ніж використання функції SQL було б у 1,35 мікросекунди проти 6,5 мікросекцій, що вказує на значно менші накладні витрати на виклик функції для функції CLQL SQL порівняно з простим UQL SQL SQL.

У SQL 2005, функція CLR SQL в моєму тестуванні в 16 разів швидша порівняно з цією повільною функцією:

create function dateonly (  @dt datetime )
returns datetime
as
begin
return cast(floor(cast(@dt as float))as int)
end

1

Як щодо select cast(cast my_datetime_field as date) as datetime)? Це призводить до тієї ж дати з часом, встановленим на 00:00, але уникає будь-якого перетворення тексту, а також уникає явного округлення чисел.



Вони не однакові. Інші відповіді пропонували передати його на дату без часової складової та залишити її так. Моя публікація встановлює його на час побачення з часом о півночі. Є велика різниця; спробуйте експортувати в MS Excel, і ви побачите, що він обробляє дату набагато краще, ніж дата.
Доктор Дрю

Перший точно такий же.
Мікаель Ерікссон

Гаразд, так, я це бачу зараз. Я буду радий видалити свою відповідь як дублікат, якщо потрібно.
Доктор Дрю

1

Я думаю, що якщо чітко дотримуватися TSQLцього, це найшвидший спосіб скоротити час:

 select convert(datetime,convert(int,convert(float,[Modified])))

Я знайшов цей метод усічення приблизно на 5% швидшим, ніж DateAddметод. І це можна легко змінити, щоб округлити найближчий день таким чином:

select convert(datetime,ROUND(convert(float,[Modified]),0))

Перетворення в плавати не є безпечним .
ErikE

1

Тут я створив функцію для видалення деяких частин часу для SQL Server. Використання:

  • Перший парам - це час, який потрібно зняти.
  • Другий парам - це знак:
    • s: обходи в секунди; видаляє мілісекунди
    • m: обходи до хвилин; видаляє секунди і мілісекунди
    • h: обходи до годин; видаляє хвилини, секунди і мілісекунди.
    • d: обходи до днів; видаляє години, хвилини, секунди і мілісекунди.
  • Повертає новий час

create function dbo.uf_RoundDateTime(@dt as datetime, @part as char) returns datetime as begin if CHARINDEX( @part, 'smhd',0) = 0 return @dt; return cast( Case @part when 's' then convert(varchar(19), @dt, 126) when 'm' then convert(varchar(17), @dt, 126) + '00' when 'h' then convert(varchar(14), @dt, 126) + '00:00' when 'd' then convert(varchar(14), @dt, 112) end as datetime ) end


Дякую Андрію! Я не знав, що моя рекомендація не була такою ефективною. Принаймні це працює, але ви праві.
Макс Варгас

1

На всякий випадок, якщо хтось шукає тут для Sybase версії, оскільки кілька версій вище не працювали

CAST(CONVERT(DATE,GETDATE(),103) AS DATETIME)
  • Тестовано в I SQL v11, що працює на адаптивному сервері 15.7

Це краще підходить як редакція прийнятої відповіді. З 20 іншими відповідями це буде закопано і майже не завершиться. Також у прийнятій відповіді згадується використання cast: Для SQL Server 2008+ ви можете CAST на сьогоднішній день. Або просто скористайтеся датою, щоб не знімати часу.
EWit

Найкраще було б розмістити це як відповідь на еквівалентне питання Sybase. Якщо такого питання немає, ви можете створити його (і відповісти на нього самостійно).
Андрій М

Крім того, він не має сенсу вказувати третій параметр CONVERT , коли ви перетворюєте datetimeв date: ні один з тих , хто має властивий формат.
Андрій М

0

Якщо можливо, для таких спеціальних речей я люблю використовувати функції CLR.

В цьому випадку:

[Microsoft.SqlServer.Server.SqlFunction]
    public static SqlDateTime DateOnly(SqlDateTime input)
    {
        if (!input.IsNull)
        {
            SqlDateTime dt = new SqlDateTime(input.Value.Year, input.Value.Month, input.Value.Day, 0, 0, 0);

            return dt;
        }
        else
            return SqlDateTime.Null;
    }

0

Я, особисто, майже завжди використовую для цього функції, визначені користувачем, якщо мати справу з SQL Server 2005 (або нижчою версією), однак, слід зазначити, що для використання UDF є специфічні недоліки, особливо якщо застосовувати їх до пунктів WHERE (див. Нижче та коментарі до цієї відповіді для отримання детальної інформації). Якщо ви використовуєте SQL Server 2008 (або вище) - див. Нижче.

Насправді для більшості баз даних, які я створюю, я додаю ці UDF безпосередньо перед початком, оскільки я знаю, що є 99% шансів, що мені рано чи пізно знадобляться.

Я створюю його для "тільки дати" та "лише часу" (хоча один із "дат" - це найбільше з двох).

Ось декілька посилань на різні UDF, пов’язані з датою:

Основні функції дати, часу та дати SQL-сервера
Отримують лише дату

Останнє посилання показує не менше 3-х різних способів отримання дати лише частиною поля дати та згадує деякі плюси та мінуси кожного підходу.

Якщо ви використовуєте UDF, слід зазначити, що вам слід намагатися уникати використання UDF як частини пункту WHERE у запиті, оскільки це сильно утруднятиме виконання запиту. Основна причина цього полягає в тому, що використання UDF у пункті WHERE робить цей пункт як непридатний для збору , а це означає, що SQL Server більше не може використовувати індекс з цим пунктом для підвищення швидкості виконання запитів. Посилаючись на моє власне використання UDF, я часто буду використовувати "необроблений" стовпець дати в рамках WHERE, але застосовуватиму UDF до стовпця SELECTed. Таким чином, UDF застосовується лише до відфільтрованого набору результатів, а не до кожного рядка таблиці як частини фільтра.

Звичайно, абсолютним найкращим підходом для цього є використання SQL Server 2008 (або вище) та розділення ваших дат і часу , оскільки двигун бази даних SQL Server надає вихідні компоненти окремі компоненти дати та часу і може ефективно їх запитувати незалежно. без необхідності використання УДС або іншого механізму для вилучення частини дати або часу з складеного типу дати.


Використання UDF може бути корисним у деяких ситуаціях (наприклад, при вичистці параметрів). Але в більшості ситуацій це жахливе рішення - запустити UDF один раз для кожного ряду - це спосіб просто знищити ефективність запиту, не маючи потреби в цьому!
ErikE

@ErikE - Я не погоджуюся, Ерік, UDF - це вбивці продуктивності, тому я кажу, що, якщо ви можете використовувати SQL Server 2008 або вище, і використовувати вбудований тип даних, який робить це для вас, це буде найкращим рішенням (як з точки зору досягнення необхідного, так і з точки зору продуктивності). Якщо ви зупинилися на старішій версії SQL Server, яка не підтримує цього, ви збираєтесь відмовитися від чогось , щоб досягти своїх потреб.
CraigTP

Правда. Було б добре, якби двигун бази даних дав нам щось, що було SARGable, але простіше виразити. У той же час, якщо ви шукаєте унікальне значення, в будь-який час в протягом усього дня, це ще краще рішення (по крайней мере , старіших версій SQL): WHERE DateColumn >= {TimeTruncatingExpression}(@DateValue) AND DateColumn < {TimeTruncatingExpression}(@DateValue + 1). Мені здалося, що мені потрібно щось сказати, оскільки ти сказав, що "я майже завжди використовую UDF", не пояснив жодних недоліків, а також способу зробити запит, призначений лише для дати SARGable.
ErikE

@ErikE - Не хвилюйся, Еріку. Коли я використовував UDF, я або працював з невеликими наборами даних, де продуктивність не є першорядною, або, швидше за все, я фільтрував запит у "необроблене" поле дати (щоб забезпечити зручність роботи), але вибирав стовпчик із застосованим АДС. Оскільки це, як правило, невеликі набори даних, щойно відфільтровані, запуск UDF над такою невеликою кількістю записів не є таким ефективним показником. Це означає, що ви піднімаєте дуже хороший пункт, і я оновив свою відповідь, щоб це відобразити.
CraigTP

-4

Я б використав:

CAST
(
CAST(YEAR(DATEFIELD) as varchar(4)) + '/' CAST(MM(DATEFIELD) as varchar(2)) + '/' CAST(DD(DATEFIELD) as varchar(2)) as datetime
) 

Таким чином, ефективно створюється нове поле з поля, яке ви вже маєте.


2
Навіщо ти це робив? Чи вважаєте ви, що витяг бітів із datetimeзначення, перетворення їх у рядки, об'єднання цих об'єднань і, нарешті, перетворення результату назад datetime, краще, ніж, наприклад, виконання прямих обчислень на оригінал datetime( метод DATEADD/ DATEDIFF)?
Андрій М

Також, що таке MMі що DD? У SQL Server таких функцій немає.
Андрій М
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.