Лучший подход для удаления части времени 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)
Byju
источник
4
Допустимый вариант, да. Предложил не раз в этой теме, хотя.
Андрей М
Честно говоря, это решение легче всего читать. Мое goto
BelgoCanadian
5

Мне действительно нравится:

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

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

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

Супер прост в использовании в dplyr ( R) и pandas ( Python)!

emehex
источник
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).

broslav
источник
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;
    }
tjeuten
источник
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 или другого механизма для извлечения части даты или времени из составного типа дата-время.

CraigTP
источник
Использование 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
) 

Таким образом, эффективно создавая новое поле из поля даты, которое у вас уже есть.

Jabu
источник
2
Почему ты бы так поступил? Считаете ли вы, что извлечь биты из datetimeзначения, преобразовать их в строки, объединить их вместе и, наконец, преобразовать результат обратно, datetimeлучше, чем, например, выполнить прямые вычисления для оригинала datetime( метод DATEADD/ DATEDIFF)?
Андрей М
Кроме того, что есть MMи DD? В SQL Server нет таких функций.
Андрей М