Тип данных SQL Server datetype
- это два 32-битных слова (целые числа). Старшее слово - это смещение в днях, начиная с эпохи (нулевой точки) внутреннего календаря, используемого SQL Server: этой эпохой является 1 января 1900 00: 00: 00.000. Слово младшего разряда - это смещение в миллисекундах с начала дня (00: 00: 00.000 / полночь).
По историческим причинам, в то время как точность младшего слова составляет 1 миллисекунду; точность составляет 1/300 секунды (!?). Это означает, что любой данный момент времени округляется с шагом 0, 3 или 7 миллисекунд.
Чтобы выполнить преобразование так, как это делает SQL Server, выполните следующие действия: возьмите долю в миллисекундах рассматриваемого фактического времени, значение от 0 до 999, по модулю 100. Это даст вам младшую цифру, значение от 0 до 9. Таким образом, если текущее время составляет 23: 57: 23.559, компонент в миллисекундах равен 559. По модулю 100 вы получите 9.
- Значения 0 и 1 округляются до 0.
- Значения 2, 3 и 4 «округлены» до 3.
- Значения 5, 6, 7 и 8 «округлены» до 7.
- Значение 9 округляется UP до 0. Это имеет довольно неприятный и неприятный побочный эффект: если доля времени в миллисекундах равна 999, она увеличивается на 1 миллисекунду. Это означает, что время 23: 59: 59,999 округляется до СЛЕДУЮЩИЙ ДЕНЬ . Таким образом, преобразование '31 Dec 2010 23: 59: 59.999 'дает значение даты и времени ... 1 января 2011 00: 00: 00.000.
Brilliant! Или что-то.
См. Раздел «Замечания» в разделе SQL Server 2005 BOL здесь: http://msdn.microsoft.com/en-us/library/ms187819(v=SQL.90).aspx
Результатом всего этого является то, что вы не можете выполнить очевидную проверку для диапазона дат / периода времени ... что-то вроде
where myDateColumn between '1 September 2011 00:00:00.000'
and '30 September 2011 23:59:59.999'
, поскольку это потенциально может принести [небольшой] кусок данных за следующий период. И вы не можете сказать
where myDateColumn between '1 September 2011 00:00'
and '30 September 2011 23:59:59'
, поскольку это потенциально исключает данные, относящиеся к периоду. Вместо этого вы должны сказать что-то вроде
where myDateColumn >= '1 September 2011 00:00:00.000' and myDateColumn < '1 October 2011 00:00:00.000'
или
where myDateColumn >= '1 September 2011 00:00:00.000' and myDateColumn <= '30 September 2011 23:59:59.997'
Следует отметить, что smalldatetime
с точностью до 1 минуты демонстрирует то же поддельное поведение: если составляющая секунд рассматриваемого времени составляет 29,998 секунд или меньше, она округляется до ближайшей минуты; если 29,999 или выше, округляется UP до следующей минуты, поэтому значение 31 Dec 2010 23:59:30.000' winds up as a
smalldatetime value of
1 Jan 2011 00: 00: 00`.
Это имеет всевозможные последствия, особенно WRT для биллинговых систем и тому подобного.
Я бы сказал, что если точность важна для вас, сохраните значения даты / времени в SQL Server в виде строк в формате ISO 8601 , что-то вроде 2011-10-30T23:59:55.1234
(или эквивалентную «компактную» форму ( 20111030T235955.1234
). Стандарт ISO 8601 сопоставляет и сравнивает должным образом, он легко преобразуется и удобочитаем для человека. Еще лучше разбить его на два столбца - один для даты (2011-10-30
) и один для времени (23:59:55.1234
). Затем добавьте третий вычисляемый столбец, чтобы сложить все вместе:
create table foo
(
...
transaction_date char(10) not null ,
transaction_time char(12) not null ,
...
iso8601_transaction_datetime as transaction_date + 'T' + transaction_time ,
...
)
Довольно хорошее резюме ISO 8601 в http://www.cl.cam.ac.uk/~mgk25/iso-time.html. В Википедии также есть довольно хорошая информация: http://en.wikipedia.org/wiki/ISO_8601.