Лучший подход для удаления части времени datetime в SQL Server


514

Какой метод обеспечивает наилучшую производительность при удалении части времени из поля даты и времени в SQL Server?

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

или

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

Второй способ отправляет еще несколько байтов в любом случае, но это может быть не так важно, как скорость преобразования.

Оба также кажутся очень быстрыми, но может быть разница в скорости при работе с сотнями тысяч или более строк?

Кроме того, возможно ли, что есть еще лучшие методы, чтобы избавиться от части времени datetime в SQL?


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

9
Вот что я нашел на 18 000 000 строк (SQL Server 2008): метод b примерно на 24% медленнее, чем метод a. CAST (FLOOR (CAST (getdate () AS FLOAT)) AS DATETIME) на 3,5% медленнее, чем метод a. Метод А кажется победителем в отношении производительности. Спасибо всем за отличные ответы.
— Стивен Перельсон

46
Почему, черт возьми, в SQL нет встроенной функции для этого? !!
— Гэри МакГилл

10
Новый тип данных SQL 2008 DATE справится с этим.
— Филипп Келли

Ответы:


558

Строго говоря, метод aявляется наименее ресурсоемким:

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

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

Я видел аналогичный тест в другом месте с аналогичными результатами тоже.

Я предпочитаю DATEADD / DATEDIFF, потому что:

  • varchar подвержен проблемам с языком / форматированием даты.
    Пример: почему мое выражение CASE недетерминировано?
  • float полагается на внутреннюю память
  • он распространяется на первый день месяца, завтра и т. д., изменяя базу на 0

Редактировать, октябрь 2011

Для SQL Server 2008+, можно заливать в dateто есть CAST(getdate() AS date). Или просто используйте dateтип данных, поэтому нет времени, чтобы удалить.

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

Работающий пример того, насколько это гибко: нужно рассчитать по округленному времени или дате в SQL Server

Редактировать, май 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
@ Дэвид Сопко для редактирования в октябре 2011 года, тогда код будет таким: выберите актерский состав (GETDATE () в качестве даты)
— Choco Smith

1
Для более поздних версий SQL использование даты вместо datetime избавляет от необходимости работать с часами. Используйте следующий пример: объявляйте noTime date = getdate (), withTime datetime = getdate () select @ noTime, @ withTime
— ozkary

1
бросок как дата - это здорово, если вам просто нужна дата. Тем не менее, часто вам нужна текущая дата в полночь, чтобы потом можно было проводить дальнейшие манипуляции с датой. время DATEданных неприятно ограничивает то, что оно позволяет вам делать в отношении таких вещей, как dateadd, datediff и взаимодействие с другими типами данных даты / времени. Для этих случаев 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))
— Бернхард Доблер,

1
@ BernhardDöbler в июле 2009 года, когда я ответил, «0218» было бы допустимой датой, поэтому вы бы не зашли так далеко. Также «0» не конвертируется в 19000101 для datetime2. Попробуйте это выбратьSELECT DATEDIFF(dd, '19000101', convert(datetime2(0), '0218-09-12', 120))
— ГБН

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 позволяет избежать математических манипуляций CAST между типами.
— MatBailie

Я могу согласиться с тем, что вы, возможно, захотите избежать приведения типа DATETIME к FLOAT по той причине, которую вы описываете, но в этом случае не является ли неявное преобразование из нуля в опции OP (a) также проблемой? Хммм ... Полагаю, в этом случае это не FLOAT и сервер, вероятно, достаточно умен, чтобы отбрасывать информацию о времени. Хорошо, я уступаю :-)
— Гари МакГилл

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

Кастинг, чтобы плавать, ужасно неточен. Этот ответ должен быть удален. Никто не должен использовать этот код.
— USR

3
Не говоря уже о том, что небезопасно приводить к типу float и обратно ко времени - у float недостаточно точности. Поэтому я думаю, что это не может быть рекомендовано вообще. Смотрите этот пост для более подробной информации .
— Эрик

17

В SQL Server 2008 существует тип даты DATE (также тип данных TIME).

CAST(GetDate() as DATE)

или

declare @Dt as DATE = GetDate()

Это то, что я использовал, и это работало хорошо. Похоже, самый простой ответ. Есть ли недостатки в использовании вместе с 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 хорошая мысль. Ваш ответ дает возможность использовать то, '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

BEWARE!

Метод а) и б) НЕ всегда имеют одинаковый результат!

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)

РЕДАКТИРОВАТЬ: Согласно комментарию Адама, это не может произойти, если вы читаете значение даты из таблицы, но это может произойти, если вы укажите свое значение даты в виде литерала (пример: в качестве параметра хранимой процедуры, вызываемой через ADO.NET).


1
.999 нельзя хранить в SQL Server в DATETIMEстолбце. Максимально возможное значение - 0,999. От: msdn.microsoft.com/en-us/library/ms187819.aspx вы увидите, что значения округлены до тысячного места до 0, 3 или 7. OP не увидит значение из вашего теста в их таблицах.
— Адам Венгер

Ты прав. Я не хотел публиковать это как ответ на вопрос ОП, но как комментарий для других, но у меня было только 11 очков репутации, и 15 нужно для комментариев.
— Брослав

В вашем первом фрагменте строковая константа неявно преобразуется в дату и время, во втором она остается строкой (а 113 просто игнорируется).
— Андрей М

2

Обрезать время на вставках / обновлениях в первую очередь. Что касается преобразования на лету, ничто не может превзойти определяемую пользователем функцию:

select date_only(dd)

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


Однажды я разработал триггер для очистки времени от выбранных столбцов. Если данные не могут быть плохими, вам не нужно их чистить.
— Филипп Келли

2
У подхода UDF есть и обратная сторона, они не изнемогают. При использовании в предложениях JOIN или WHERE оптимизатор не может использовать индексы для повышения производительности. Использование подхода DATEADD / DATEDIFF, однако, оправдано и может извлечь выгоду из индексов. (Видимо метод FLOAT тоже SARGable)
— MatBailie

1
@ MatBailie, прошу отличаться! UDF определенно не SARGable, но ни Dateadd, ни Convert to float! WHERE DateAdd(DateDiff(Column)) = @DateValueне будет использовать индекс. С другой стороны, WHERE Column >= dbo.UDF(@DateValue) AND Column < dbo.UDF(@DateValue + 1) это SARGable. Так что будьте осторожны, как вы это выразите.
— Эрик

2

Смотрите этот вопрос:
Как я могу усечь дату и время в SQL Server?

Что бы вы ни делали, не используйте строковый метод . Это о худшем способе, которым ты мог сделать это.


Спасибо, я подумал, что об этом нужно было спрашивать раньше. Странно, однако, что мои эксперименты показали, что метод float на SQL Server 2008 на 3,5% медленнее, чем метод dateadd (dd, 0, datediff (dd, 0, getDate ())). Я проводил свои тесты много раз для каждого метода, и сервер базы данных в то время не использовался ни для чего другого.
— Стивен Перельсон

Скажем так, я скептически отношусь к тестам, проводимым теми, кто не продемонстрировал, что они регулярно и по-научному проводят тесты в рамках своей работы. Даже у теста Томаса в ссылке на gbn есть некоторые очевидные проблемы, когда вы смотрите на него. Это не обязательно делает это неправильно, просто не является окончательным. Метод литья / пол / литой был принят самый быстрый путь в течение очень долгого времени, и я подозреваю , что это было когда - то бесспорная истина. Тем не менее, я начинаю пересматривать это; особенно для SQL Server 2008, где это все равно совершенно не нужно.
— Джоэл Коухорн

1
Строковый метод чрезвычайно прост в использовании, чтении и запоминании. Это очень важные факторы, которые, я думаю, вы недооцениваете!
— Бен

1
@JoelCoehoorn, конвертировать стиль 121 называется «ODBC Canonical». Это не зависит от сопоставления или локали. Струнный трюк также легко обобщить на год, год + месяц, день, час или минуту.
— Бен

1
@Ben Строковый трюк учит разработчиков использовать преобразования строк. Они работают , но математика дат намного, намного лучше, по многим причинам, не в последнюю очередь из-за скорости - но даже больше, потому что то, что обучение работе с числами даты даёт разработчику и его умственным способностям быть подвижным с числовыми манипуляциями в коде.
— Эрик

2

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

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

во второй раз я нашел это решение ... я взял этот код


1
Преобразование в плавающее не является безопасным .
— Эрик

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

Этот метод не использует строковую функцию. Dateв основном настоящий тип данных с цифрами перед десятичной дробью являются частью дня.

Я думаю, это будет быстрее, чем много.


1
Кастинг как поплавок небезопасен .
— Эрик


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 микросекциями, что указывает на гораздо меньшую нагрузку при вызове функции для функции SQL CLR по сравнению с простой UDF SQL.

В моем тесте SQL 2005 функция SQL CLR была в 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))

Преобразование в плавающее не является безопасным .
— Эрик

1

Здесь я сделал функцию для удаления некоторых частей datetime для SQL Server. Применение:

  • Первый параметр - это дата и время, которое нужно удалить.
  • Второй параметр - это символ:
    • s: округляет до секунд; удаляет миллисекунды
    • m: округляет до минут; удаляет секунды и миллисекунды
    • ч: округляет до часов; удаляет минуты, секунды и миллисекунды.
    • 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, работающем на Adaptive Server 15.7

Это лучше подходит для редактирования принятого ответа. С 20 другими ответами это будет похоронено и почти невозможно выяснить. Также в принятом ответе упоминается использование cast: для SQL Server 2008+ вы можете использовать CAST на сегодняшний день. Или просто используйте дату, поэтому нет времени, чтобы удалить.
— EWit

Было бы лучше опубликовать это как ответ на эквивалентный вопрос Sybase. Если такого вопроса нет, вы можете его создать (и ответить на него самостоятельно).
— Андрей М

Кроме того, бессмысленно указывать третий параметр для преобразования при преобразовании 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 Server Date, Time и DateTime Функция
Get Only Only

Эта последняя ссылка показывает не менее 3 различных способов получения только даты в поле даты и времени и упоминает некоторые плюсы и минусы каждого подхода.

При использовании UDF следует отметить, что вы должны стараться избегать использования UDF как части предложения WHERE в запросе, так как это сильно снизит производительность запроса. Основная причина этого заключается в том, что использование UDF в предложении WHERE делает это предложение несортируемым , что означает, что SQL Server больше не может использовать индекс с этим предложением для повышения скорости выполнения запроса. Что касается моего собственного использования UDF, я часто буду использовать «необработанный» столбец даты в предложении WHERE, но применяю UDF к столбцу SELECTed. Таким образом, UDF применяется только к отфильтрованному набору результатов, а не к каждой строке таблицы как части фильтра.

Конечно, абсолютно лучший подход для этого - использовать SQL Server 2008 (или выше) и разделять ваши даты и время , так как ядро ​​базы данных SQL Server изначально предоставляет отдельные компоненты даты и времени и может эффективно запрашивать их независимо без необходимости использования UDF или другого механизма для извлечения части даты или времени из составного типа дата-время.


Использование 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. Поскольку после фильтрации это обычно небольшие наборы данных, работа 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.