Tại sao thời gian tìm kiếm truy vấn của tôi không khớp?


20
select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

Nhưng kết quả có chứa một bản ghi đã được đăng ngày hôm nay: 2015-07-28. Máy chủ cơ sở dữ liệu của tôi không ở trong nước tôi. Vấn đề là gì?

Câu trả lời:


16

Vì bạn đang sử dụng datetimekiểu dữ liệu, bạn cần hiểu cách máy chủ sql làm tròn dữ liệu datetime.

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
   Name     sn          Minimum value                Maximum value         Accuracy   Storage  
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
 datetime   dt   1753-01-01 00:00:00.000      9999-12-31 23:59:59.997      3.33 ms   8 bytes   
 datetime2  dt2  0001-01-01 00:00:00.0000000  9999-12-31 23:59:59.9999999  100ns     6-8 bytes 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

nhập mô tả hình ảnh ở đây

Sử dụng truy vấn bên dưới, bạn có thể dễ dàng thấy vấn đề làm tròn máy chủ sql khi bạn sử dụng DATETIMEkiểu dữ liệu.

select  '2015-07-27 00:00:00.000'                       as Original_startDateTime,
        convert(datetime ,'2015-07-27 00:00:00.000')    as startDateTime,
        '2015-07-27 23:59:59.999'                       as Original_endDateTime,
        convert(datetime ,'2015-07-27 23:59:59.999')    as endDateTime,
        '2015-07-27 00:00:00.000'                       as Original_startDateTime2,
        convert(datetime2 ,'2015-07-27 00:00:00.000')   as startDateTime2,  -- default precision is 7
        '2015-07-27 23:59:59.999'                       as Original_endDateTime2,
        convert(datetime2 ,'2015-07-27 23:59:59.999')   as endDateTime2     -- default precision is 7

nhập mô tả hình ảnh ở đây bấm vào để phóng to

DATETIME2đã xuất hiện từ SQL Server 2008, vì vậy hãy bắt đầu sử dụng nó thay vì DATETIME. Đối với tình huống của bạn, bạn có thể sử dụng datetime2với độ chính xác là 3 số thập phân, vd datetime2(3).

Lợi ích của việc sử dụng datetime2:

  • Hỗ trợ lên đến 7 chữ số thập phân cho phần thời gian vs datetimehỗ trợ chỉ có 3 chữ số thập phân .. và do đó bạn sẽ thấy vấn đề làm tròn kể từ khi theo mặc định datetimevòng gần nhất .003 secondsvới gia số của .000, .003hoặc .007giây.
  • datetime2là chính xác hơn nhiều datetimedatetime2cung cấp cho bạn quyền kiểm soát DATETIMEtrái ngược với datetime.

Tài liệu tham khảo :


1
gives you control of DATE and TIME as opposed to datetime.điều đó nghĩa là gì?
Nurettin

Re. sử dụng DateTime2so với DateTime: a. Đối với - trường hợp - rộng lớn - đa số - thực tế - thế giới - sử dụng - lợi ích của phần DateTime2lớn <chi phí. Xem: stackoverflow.com/questions/1334143/ b. Đó không phải là vấn đề gốc ở đây. Xem bình luận tiếp theo.
Tom

Vấn đề gốc ở đây (như tôi cá là hầu hết các nhà phát triển cấp cao sẽ đồng ý) là không đủ độ chính xác trong thời gian kết thúc bao gồm so sánh phạm vi thời gian, mà là sử dụng một khoảng thời gian bao gồm (so với độc quyền). Giống như kiểm tra đẳng thức với Pi, luôn có khả năng một trong số # có> hoặc <độ chính xác (nghĩa là gì nếu datetime3có thêm 70 (so với 7) chữ số chính xác được thêm vào?). Thực hành tốt nhất là sử dụng một giá trị trong đó độ chính xác không thành vấn đề, tức là < bắt đầu của giây tiếp theo, phút, giờ hoặc ngày so với <= kết thúc của giây trước, phút, giờ hoặc ngày.
Tom

18

Như một số người khác đã đề cập trong các bình luận và các câu trả lời khác cho câu hỏi của bạn, vấn đề cốt lõi 2015-07-27 23:59:59.999đang được 2015-07-28 00:00:00.000SQL Server làm tròn . Theo tài liệu cho DATETIME:

Phạm vi thời gian - 00:00:00 đến 23: 59: 59.997

Lưu ý rằng phạm vi thời gian không bao giờ có thể được .999. Hơn nữa trong tài liệu này, nó chỉ định các quy tắc làm tròn mà SQL Server sử dụng cho chữ số có nghĩa ít nhất.

Bảng hiển thị quy tắc làm tròn

Lưu ý rằng chữ số có nghĩa nhỏ nhất chỉ có thể có một trong ba giá trị tiềm năng: "0", "3" hoặc "7".

Có một số giải pháp / giải pháp cho việc này mà bạn có thể sử dụng.

-- Option 1
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <  '2015-07-28 00:00:00.000' --Round up and remove equality

-- Option 2
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <=  '2015-07-27 23:59:59.997' --Round down and keep equality

-- Option 3
SELECT 
    * 
FROM A 
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type

-- Option 4
SELECT 
    * 
FROM A 
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time

-- Option 5
SELECT 
    * 
FROM A 
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000' 
  AND '2015-07-27 23:59:59.997' --Use between

Trong số năm tùy chọn tôi đã trình bày ở trên, tôi sẽ xem xét các lựa chọn 1 và 3 là các tùy chọn khả thi duy nhất. Chúng truyền đạt ý định của bạn một cách rõ ràng và sẽ không bị phá vỡ nếu bạn cập nhật các loại dữ liệu. Nếu bạn đang sử dụng SQL Server 2008 hoặc mới hơn, tôi nghĩ tùy chọn 3 nên là cách tiếp cận ưa thích của bạn. Điều đó đặc biệt đúng nếu bạn có thể thay đổi từ việc sử dụng DATETIMEkiểu dữ liệu thành kiểu DATEdữ liệu cho posted_datecột của mình .

Liên quan đến phương án 3, một lời giải thích rất hay về một số vấn đề có thể được tìm thấy ở đây: Diễn viên cho đến nay là rất lớn nhưng đó có phải là một ý tưởng tốt?

Tôi không thích các tùy chọn 2 và 5 vì các .997giây phân số sẽ chỉ là một số ma thuật khác mà mọi người sẽ muốn "sửa chữa". Đối với một số lý do tại sao BETWEENkhông được chấp nhận rộng rãi, bạn có thể muốn kiểm tra bài đăng này .

Tôi không thích tùy chọn 4 vì việc chuyển đổi các loại dữ liệu thành một chuỗi cho mục đích so sánh cảm thấy bẩn đối với tôi. Một lý do chất lượng hơn để tránh nó trong SQL Server là nó ảnh hưởng đến tính khả dụng hay còn gọi là bạn không thể thực hiện tìm kiếm chỉ mục và điều đó thường dẫn đến hiệu suất kém hơn.

Để biết thêm thông tin về cách đúng và sai cách xử lý các truy vấn phạm vi ngày, hãy kiểm tra bài đăng này của Aaron Bertrand .

Khi chia tay, bạn sẽ có thể giữ truy vấn ban đầu của mình và nó sẽ hoạt động như mong muốn nếu bạn thay đổi posted_datecột của mình từ a DATETIMEsang a DATETIME2(3). Điều đó sẽ tiết kiệm không gian lưu trữ trên máy chủ, cung cấp cho bạn độ chính xác cao hơn với cùng độ chính xác, tuân thủ tiêu chuẩn / di động hơn và cho phép bạn dễ dàng điều chỉnh độ chính xác / độ chính xác nếu nhu cầu của bạn thay đổi trong tương lai. Tuy nhiên, đây chỉ là một tùy chọn nếu bạn đang sử dụng SQL Server 2008 hoặc mới hơn.

Như một câu đố nhỏ, 1/300độ chính xác thứ hai DATETIMEdường như bị giữ lại từ UNIX theo câu trả lời StackOverflow này . Sybase có di sản chung1/300độ chính xác thứ hai về kiểu dữ liệu DATETIMETIME dữ liệu của chúng nhưng các chữ số có ý nghĩa nhỏ nhất của chúng là một điểm khác nhau ở "0", "3" và "6". Theo tôi 1/300, độ chính xác thứ hai và / hoặc 3,33ms là một quyết định kiến ​​trúc không may vì khối 4 byte trong thời gian trong DATETIMEkiểu dữ liệu của SQL Server có thể dễ dàng hỗ trợ độ chính xác 1ms.


Có, nhưng cốt lõi "cốt lõi vấn đề" không được sử dụng tùy chọn 1 (ví dụ như sử dụng bất kỳ (so với độc quyền) phạm vi giá trị cuối bao gồm nơi chính xác của các kiểu dữ liệu tương lai trong quá khứ hay tiềm năng có thể ảnh hưởng đến kết quả). Giống như kiểm tra đẳng thức với Pi, luôn luôn có thể một # có> hoặc <độ chính xác (trừ khi cả hai được làm tròn trước với độ chính xác chung thấp nhất). Điều gì xảy ra nếu datetime3với 70 (so với 7) chữ số chính xác được thêm vào? Thực hành tốt nhất là sử dụng một giá trị trong đó độ chính xác không thành vấn đề, tức là <bắt đầu của giây tiếp theo, phút, giờ hoặc ngày so với <= kết thúc của giây trước, phút, giờ hoặc ngày.
Tom

9

Chuyển đổi ngầm định

Tôi cho rằng kiểu dữ liệu post_date là Datetime. Tuy nhiên, không có vấn đề gì nếu loại ở phía bên kia là Datetime, Datetime2 hay chỉ là Time vì chuỗi (Varchar) sẽ được chuyển đổi hoàn toàn thành Datetime.

Với post_date được khai báo là Datetime2 (hoặc Time), posted_date <= '2015-07-27 23:59:59.99999'mệnh đề where không thành công vì altough 23:59:59.99999là giá trị Datetime2 hợp lệ, đây không phải là giá trị Datetime hợp lệ:

 Conversion failed when converting date and/or time from character string.

Phạm vi thời gian cho ngày

Phạm vi thời gian của Datetime là 00:00:00 đến 23: 59: 59.997. Do đó, 23: 59: 59.999 nằm ngoài phạm vi và phải được làm tròn lên hoặc xuống đến giá trị gần nhất.

Độ chính xác

Bên cạnh các giá trị Datetime được làm tròn theo số gia của .000, .003 hoặc .007 giây. (ví dụ: 000, 003, 007, 010, 013, 017, 020, ..., 997)

Đây không phải là trường hợp với giá trị 2015-07-27 23:59:59.999nằm trong phạm vi này: 2015-07-27 23:59:59.9972015-07-28 0:00:00.000.

Phạm vi này tương ứng với các tùy chọn trước và sau gần nhất, cả hai đều kết thúc bằng .000, .003 hoặc .007.

Làm tròn lên hay xuống ?

Vì nó gần với 2015-07-28 0:00:00.000(+1 so với -2) hơn 2015-07-27 23:59:59.997, nên chuỗi được làm tròn và trở thành giá trị Ngày tháng này : 2015-07-28 0:00:00.000.

Với giới hạn trên như 2015-07-27 23:59:59.998(hoặc .995, .996, .997, .998), nó sẽ được làm tròn xuống 2015-07-27 23:59:59.997và truy vấn của bạn sẽ hoạt động như mong đợi. Tuy nhiên, nó không phải là một giải pháp mà chỉ là một giá trị may mắn.

Datetime2 hoặc các loại thời gian

Datetime2 và Thời gian thời gian dao động là 00:00:00.0000000thông qua 23:59:59.9999999với độ chính xác 100ns (chữ số cuối cùng khi được sử dụng với một độ chính xác 7 chữ số).

Tuy nhiên, phạm vi Datetime (3) không giống với phạm vi Datetime:

  • Thời gian 0:0:00.000để23:59:59.997
  • Datetime2 0:0:00.000000000để23:59:59.999

Dung dịch

Cuối cùng, an toàn hơn là tìm kiếm ngày bên dưới vào ngày hôm sau so với ngày bên dưới hoặc bằng với những gì bạn nghĩ đó là mảnh thời gian cuối cùng trong ngày. Điều này chủ yếu là vì bạn biết rằng ngày hôm sau luôn bắt đầu từ 0: 00: 00.000 nhưng các loại dữ liệu khác nhau có thể không có cùng thời gian vào cuối ngày:

Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
  • < 2015-07-28 0:00:00.000sẽ cho bạn một kết quả chính xác và là lựa chọn tốt nhất
  • <= 2015-07-27 23:59:59.xxx có thể trả về các giá trị bất ngờ khi nó không được làm tròn đến mức bạn nghĩ nó phải như vậy.
  • Nên tránh chuyển đổi sang ngày và sử dụng chức năng vì nó giới hạn việc sử dụng các chỉ mục

Chúng tôi có thể nghĩ rằng việc thay đổi [post_date] thành Datetime2 và độ chính xác cao hơn của nó có thể khắc phục vấn đề này nhưng nó sẽ không giúp ích gì vì chuỗi vẫn được chuyển đổi thành Datetime. Tuy nhiên, nếu một diễn viên được thêm vào cast(2015-07-27 23:59:59.999' as datetime2), điều này hoạt động tốt

Diễn viên và chuyển đổi

Truyền có thể chuyển đổi một giá trị có tối đa 3 chữ số thành Datetime hoặc tối đa 9 chữ số thành Datetime2 hoặc Time và làm tròn nó với độ chính xác chính xác.

Cần lưu ý rằng Cast of Datetime2 và Time2 có thể cho các kết quả khác nhau:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) được làm tròn 2015-05-03 00: 00: 00.0000000 (đối với giá trị lớn hơn 999999949)
  • select cast('23:59:59.999999999' as time(7)) => 23: 59: 59.9999999

Cách khắc phục sự cố mà datetime đang gặp phải với các mức tăng 0, 3 và 7 mặc dù vẫn luôn tốt hơn để tìm ngày trước nano thứ 1 của ngày tiếp theo (luôn là 0: 00: 00.000).

Nguồn MSDN: datetime (Transact-SQL)


6

Nó đang làm tròn

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998, .997, .996, .995 tất cả các diễn viên / vòng đến .997

Nên dùng

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

hoặc là

where cast(posted_date as date) = '2015-07-27'

Xem độ chính xác trong liên kết này
Luôn được báo cáo là .000, .003, .007


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.