Как объединить дату и время с datetime2 в SQL Server?

48

Учитывая следующие компоненты

DECLARE @D DATE = '2013-10-13'
DECLARE @T TIME(7) = '23:59:59.9999999'

Как лучше всего объединить их, чтобы получить DATETIME2(7)результат со стоимостью '2013-10-13 23:59:59.9999999'?

Некоторые вещи, которые не работают, перечислены ниже.


SELECT @D + @T 

Дата типа данных операнда недопустима для оператора добавления.


SELECT CAST(@D AS DATETIME2(7)) + @T 

Тип данных операнда datetime2 недопустим для оператора добавления.


SELECT DATEADD(NANOSECOND,DATEDIFF(NANOSECOND,CAST('00:00:00.0000000' AS TIME),@T),@D)

Функция datediff привела к переполнению. Число частей даты, разделяющих два экземпляра даты / времени, слишком велико. Попробуйте использовать datediff с менее точной частью даты.

* Переполнения можно избежать в базе данных SQL Azure и SQL Server 2016, используя DATEDIFF_BIG.


SELECT CAST(@D AS DATETIME) + @T 

Типы данных datetime и time несовместимы в операторе добавления.


SELECT CAST(@D AS DATETIME) + CAST(@T AS DATETIME)

Возвращает результат, но теряет точность 2013-10-13 23:59:59.997

Мартин Смит
источник

Ответы:

49

Это, кажется, работает и сохраняет точность:

SELECT DATEADD(day, DATEDIFF(day,'19000101',@D), CAST(@T AS DATETIME2(7)))

CASTК DATETIME2(7)Преобразует TIME(7)значение ( @T) к DATETIME2где дата часть '1900-01-01', которая является значением по умолчанию даты и DateTime типа (см datetime2и комментарий * на CASTиCONVERT страницы в MSDN) .

* ... Когда символьные данные, представляющие только компоненты даты или только времени, приводятся к типам данных datetime или smalldatetime, для неопределенного компонента времени устанавливается значение 00: 00: 00.000, а для неопределенного компонента даты устанавливается значение 1900-01- 01 .

Функция DATEADD()и DATEDIFF()заботится об остальном, т.е. добавляет разницу в днях между 1900-01-01и DATEзначением ( @D).

Тест в: SQL-Fiddle


Как заметил @Quandary , вышеприведенное выражение считается недетерминированным в SQL Server. Если нам нужно детерминированное выражение, скажем, потому что оно должно использоваться для PERSISTEDстолбца, '19000101'** необходимо заменить на 0или CONVERT(DATE, '19000101', 112):

CREATE TABLE date_time
( d DATE NOT NULL,
  t TIME(7) NOT NULL,
  dt AS DATEADD(day, 
                DATEDIFF(day, CONVERT(DATE, '19000101', 112), d), 
                CAST(t AS DATETIME2(7))
               ) PERSISTED
) ;

**: DATEDIFF(day, '19000101', d)не является детерминированным, поскольку неявное преобразование строки в DATETIMEи преобразование из строки в дату и время являются детерминированными только при использовании определенных стилей.

ypercubeᵀᴹ
источник
8

Я опаздываю на вечеринку, но этот подход, хотя и похож на ответ @ ypercube , избавляет от необходимости использовать любое преобразование строк (которое может быть более дорогим, чем преобразования даты), является детерминированным и должен продолжать работать, если MS когда-либо изменит значение даты по умолчанию от 1900-01-01 (даже если это, вероятно, не изменится):

DECLARE @D DATE = SYSUTCDATETIME()
, @T TIME = SYSUTCDATETIME();

SELECT DATEADD(DAY, DATEDIFF(DAY, @T, @D), CONVERT(DATETIME2, @T));

Принцип состоит в том, что, преобразовав значение времени в datetime2, а затем в date, он удаляет время и назначает дату по умолчанию, затем вы вводите это значение со своим значением даты, чтобы получить дни для добавления, приводите время к datetime2 и добавляете дни.

кастет
источник
Вместо «DATEDIFF (ДЕНЬ, @T, @D)» должно быть «DATEDIFF (ДЕНЬ, 0, @D)». Результат тот же, но помогает избежать путаницы. DateDiff (день, ...) приводит аргументы к наименьшему целому числу дней, поэтому @T в любом случае преобразуется в 0.
Деннис Горелик
5

Для SQL Server 2012 и выше есть функция DATETIME2FROMPARTS . Это имеет такую ​​форму:

DATETIME2FROMPARTS(year, month, day, hour, minute, seconds, fractions, precision)

Для данных данного образца это становится

select Answer = DATETIME2FROMPARTS(2013, 10, 13, 23, 59, 59, 9999999, 7);

что приводит к

Answer
---------------------------
2013-10-13 23:59:59.9999999

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

Майкл Грин
источник
0

SQL Server довольно глупо не позволять вашему первому примеру работать, и это тоже будет казаться очень глупым, но…

select convert(datetime2, convert(nvarchar(max), @d) + ' ' + convert(nvarchar(max), @t));
Atario
источник
0
SELECT mydate=CAST(CAST(@D AS nvarchar(max)) + ' ' + 
                   CAST(@T AS nvarchar (max)) 
              AS DATETIME2);
Mihir
источник
5
Вы проверяли это? Это работает? На это влияют языковые настройки?
ypercubeᵀᴹ
0

Я искал что-то еще, когда я приземлился здесь. Вопрос довольно старый, но есть недавние комментарии и активность. Думаю, я бы поделился простым методом, который очень похож на ответ, который дал @Atario, но немного короче, и некоторые из них могут оказаться более легкими для чтения:

declare @d date = '2013-10-13'
declare @t time(7) = '23:59:59.9999999'

select cast(concat(@d, ' ', @t) as datetime2(7))
Брайан Джорден
источник
-3

Вы можете усечь с приведением к DATE для усечения, затем вернуться к DATETIME, чтобы добавить ВРЕМЯ

select CAST( cast(getdate() as date) as DATETIME)  + CAST(getdate() as TIME)
user3448451
источник
Хитрость хороша, но она не отвечает на вопрос сверху.
Петер говорит восстановить Монику