Nếu bạn nhận được một số kết quả thực sự kỳ lạ khi sử dụng DATEDIFF()
chức năng trong SQL Server và bạn tin rằng chức năng này có một lỗi, đừng làm phiền bạn. Nó có thể không phải là một lỗi.
Có những tình huống mà kết quả được tạo ra bởi chức năng này có thể khá khó khăn. Và nếu bạn không hiểu cách thực sự hoạt động của hàm, kết quả sẽ hoàn toàn sai.
Hy vọng rằng bài viết này có thể giúp làm rõ cách DATEDIFF()
chức năng được thiết kế để hoạt động và cung cấp một số tình huống ví dụ về nơi kết quả của bạn có thể không như bạn mong đợi.
Ví dụ 1 - 365 ngày không phải luôn là một năm
Câu hỏi: 365 ngày là khi nào không một năm?
Trả lời: Khi sử dụng DATEDIFF()
tất nhiên!
Đây là một ví dụ mà tôi sử dụng DATEDIFF()
để trả về số ngày giữa hai ngày và sau đó là số năm giữa hai ngày giống nhau.
DECLARE @startdate datetime2 = '2016-01-01 00:00:00.0000000', @enddate datetime2 = '2016-12-31 23:59:59.9999999'; SELECT DATEDIFF(day, @startdate, @enddate) Days, DATEDIFF(year, @startdate, @enddate) Years;
Kết quả:
+--------+---------+ | Days | Years | |--------+---------| | 365 | 0 | +--------+---------+
Nếu bạn cho rằng kết quả này sai và DATEDIFF()
đó rõ ràng là có lỗi, hãy đọc tiếp - không phải mọi thứ đều như vẻ ngoài.
Tin hay không thì tùy, đây thực sự là kết quả đáng mong đợi. Kết quả này hoàn toàn phù hợp với cách DATEDIFF()
được thiết kế để hoạt động.
Ví dụ 2 - 100 Nano giây =1 Năm?
Hãy làm theo cách khác.
DECLARE @startdate datetime2 = '2016-12-31 23:59:59.9999999', @enddate datetime2 = '2017-01-01 00:00:00.0000000'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Kết quả (hiển thị với đầu ra dọc):
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 1 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Chỉ chênh lệch một trăm nano giây (.0000001 giây) giữa hai ngày / thời gian, nhưng chúng tôi nhận được kết quả chính xác như nhau cho mọi thời điểm ngày, ngoại trừ nano giây.
Làm thế nào điều này có thể xảy ra? Làm thế nào nó có thể chênh lệch 1 micro giây và chênh lệch 1 năm cả hai cùng một lúc? Không đề cập đến tất cả các ngày ở giữa?
Nó có vẻ điên rồ, nhưng đây cũng không phải là một lỗi. Các kết quả này hoàn toàn phù hợp với cách DATEDIFF()
được cho là hoạt động.
Và để làm cho mọi thứ trở nên khó hiểu hơn, chúng tôi có thể nhận được các kết quả khác nhau tùy thuộc vào loại dữ liệu. Nhưng chúng ta sẽ sớm đạt được điều đó. Trước tiên, hãy xem cách DATEDIFF()
chức năng thực sự hoạt động.
Định nghĩa thực tế của DATEDIFF ()
Lý do chúng tôi nhận được kết quả mà chúng tôi làm được là vì DATEDIFF()
chức năng được định nghĩa như sau:
Hàm này trả về số lượng (dưới dạng giá trị số nguyên có dấu) của các ranh giới ngày tháng cụ thể được vượt qua giữa ngày bắt đầu được chỉ định và enddate .
Đặc biệt chú ý đến các từ “vượt qua ranh giới thời gian hẹn hò”. Đây là lý do tại sao chúng tôi nhận được kết quả mà chúng tôi thực hiện trong các ví dụ trước. Dễ dàng cho rằng DATEDIFF()
sử dụng thời gian đã trôi qua cho các tính toán của nó, nhưng không. Nó sử dụng số lượng ranh giới ngày đã vượt qua.
Trong ví dụ đầu tiên, ngày tháng không vượt qua bất kỳ ranh giới phần nào trong năm. Năm của ngày đầu tiên giống hệt như năm của ngày thứ hai. Không có ranh giới nào bị vượt qua.
Trong ví dụ thứ hai, chúng ta có một kịch bản ngược lại. Các ngày đã vượt qua mọi ranh giới thời gian ít nhất một lần (100 lần cho nano giây).
Ví dụ 3 - Kết quả khác trong tuần
Bây giờ, hãy giả sử một năm đã trôi qua. Và ở đây chính xác là một năm sau với các giá trị ngày / giờ, ngoại trừ việc các giá trị năm đã tăng lên một.
Chúng ta sẽ nhận được kết quả tương tự, phải không?
DECLARE @startdate datetime2 = '2017-12-31 23:59:59.9999999', @enddate datetime2 = '2018-01-01 00:00:00.0000000'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Kết quả:
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 0 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Sai.
Hầu hết chúng đều giống nhau, nhưng lần này trong tuần trả về 0
.
Hả?
Điều này xảy ra vì các ngày nhập có cùng lịch tuần các giá trị. Điều xảy ra là các ngày được chọn, ví dụ 2 có các giá trị tuần theo lịch khác nhau.
Để cụ thể hơn, ví dụ 2 ranh giới phần tuần vượt qua từ ‘2016-12-31’ thành ‘2017-01-01’. Điều này là do tuần cuối cùng của năm 2016 đã kết thúc vào ngày 12 tháng 12 năm 2016 và tuần đầu tiên của năm 2017 bắt đầu vào ngày 1 tháng 3 năm 2017 (Chủ nhật).
Nhưng trong ví dụ 3, tuần đầu tiên của năm 2018 thực sự bắt đầu vào ngày bắt đầu của chúng tôi là 2017-12-31 (Chủ nhật). Ngày kết thúc của chúng tôi, là ngày hôm sau, rơi vào cùng một tuần. Do đó, không có ranh giới phần tuần nào bị vượt qua.
Điều này rõ ràng giả định rằng Chủ nhật là ngày đầu tiên của mỗi tuần. Hóa ra, DATEDIFF()
chức năng hiện giả sử rằng Chủ nhật là ngày đầu tiên trong tuần. Nó thậm chí bỏ qua SET DATEFIRST
của bạn (cài đặt này cho phép bạn chỉ định rõ ràng ngày nào được coi là ngày đầu tiên trong tuần). Lý do của Microsoft để bỏ qua SET DATEFIRST
là nó đảm bảo DATEDIFF()
chức năng là xác định. Đây là một cách giải quyết nếu đây là vấn đề với bạn.
Vì vậy, tóm lại, kết quả của bạn có thể trông “sai” đối với bất kỳ thời điểm nào tùy thuộc vào ngày / giờ. Kết quả của bạn có thể sai nhiều khi sử dụng phần tuần. Và chúng có thể trông sai hơn nữa nếu bạn sử dụng SET DATEFIRST
giá trị khác 7 (cho Chủ nhật) và bạn đang mong đợi DATEDIFF()
để tôn vinh điều đó.
Nhưng kết quả không sai, và nó không phải là một lỗi. Nó chỉ là một "gotcha" cho những người không biết về cách chức năng thực sự hoạt động.
Tất cả các nhận thức này cũng áp dụng cho DATEDIFF_BIG()
hàm số. Nó hoạt động giống như DATEDIFF()
ngoại trừ trường hợp nó trả về kết quả là bigint đã ký (trái ngược với int cho DATEDIFF()
).
Ví dụ 4 - Kết quả phụ thuộc vào loại dữ liệu
Bạn cũng có thể nhận được kết quả không mong muốn do loại dữ liệu bạn sử dụng cho ngày nhập của mình. Kết quả thường sẽ khác nhau tùy thuộc vào loại dữ liệu của ngày nhập. Nhưng bạn không thể đổ lỗi cho DATEDIFF()
về điều này, vì đó hoàn toàn là do khả năng và giới hạn của các loại dữ liệu khác nhau. Bạn không thể mong đợi nhận được kết quả có độ chính xác cao từ giá trị đầu vào có độ chính xác thấp.
Ví dụ:bất cứ khi nào ngày bắt đầu hoặc ngày kết thúc có smalldatetime giá trị, giây và mili giây sẽ luôn trả về 0. Điều này là do smalldatetime kiểu dữ liệu chỉ chính xác đến từng phút.
Đây là những gì sẽ xảy ra nếu chúng ta chuyển ví dụ 2 sang sử dụng smalldatetime thay vì datetime2 :
DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', @enddate smalldatetime = '2017-01-01 00:00:00'; SELECT DATEDIFF(year, @startdate, @enddate) Year, DATEDIFF(quarter, @startdate, @enddate) Quarter, DATEDIFF(month, @startdate, @enddate) Month, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Day, DATEDIFF(week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond, @startdate, @enddate) Millisecond, DATEDIFF(microsecond, @startdate, @enddate) Microsecond, DATEDIFF(nanosecond, @startdate, @enddate) Nanosecond;
Kết quả:
Year | 0 Quarter | 0 Month | 0 DOY | 0 Day | 0 Week | 0 Hour | 0 Minute | 0 Second | 0 Millisecond | 0 Microsecond | 0 Nanosecond | 0
Lý do tất cả đều bằng 0 là vì cả hai ngày nhập thực sự giống hệt nhau:
DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', @enddate smalldatetime = '2017-01-01 00:00:00'; SELECT @startdate 'Start Date', @enddate 'End Date';
Kết quả:
+---------------------+---------------------+ | Start Date | End Date | |---------------------+---------------------| | 2017-01-01 00:00:00 | 2017-01-01 00:00:00 | +---------------------+---------------------+
Các hạn chế của smalldatetime kiểu dữ liệu làm cho các giây được làm tròn, sau đó gây ra hiệu ứng luồng và mọi thứ được làm tròn. Ngay cả khi bạn không kết thúc với các giá trị đầu vào giống hệt nhau, bạn vẫn có thể nhận được kết quả không mong muốn do loại dữ liệu không cung cấp độ chính xác bạn cần.