12/08/2018, 16:36

Sự khác nhau giữa GETDATE(), SYSDATETIME() với GETUTCDATE() và cách so sánh Date trong SQL Server

Một trong các câu hỏi thường thấy về MS SQL Server đó là sự khác nhau giữa 3 hàm GETDATE(), SYSDATETIME() và GETUTCDATE(). Mặc dù cả 3 hàm này đều trả về thời gian và ngày hiện tại của SQL Server, vẫn có một số điểm khác nhau giữa chúng. Điểm khác nhau chính giữa hàm GETDATE() và SYSDATETIME() đó ...

Một trong các câu hỏi thường thấy về MS SQL Server đó là sự khác nhau giữa 3 hàm GETDATE(), SYSDATETIME() và GETUTCDATE(). Mặc dù cả 3 hàm này đều trả về thời gian và ngày hiện tại của SQL Server, vẫn có một số điểm khác nhau giữa chúng. Điểm khác nhau chính giữa hàm GETDATE() và SYSDATETIME() đó là hàm GETDATE trả về ngày tháng và thời gian ở kiểu DATETIME còn hàm SYSDATETIME trả về giá trị kiểu DATETIME2 chính xác hơn. Sự khác nhau giữa hàm GETDATE() và GETUTCDATE() là ở múi giờ, hàm GETDATE() trả về ngày tháng và giờ hiện tại theo múi giờ địa phương, múi giờ nơi mà server chứa cơ sở dữ liệu của bạn đang hoạt động, nhưng hàm GETUTCDATE() lại trả về ngày tháng và thời gian hiện tại theo dạng UTC (Universal Time Coordinate - Giờ phối hợp quốc tế) hoặc theo múi giờ GMT.

Vì cả 2 hàm GETDATE và GETUTCDATE đều trả về kiểu giá trị DATETIME, nên sự khác nhau giữa SYSDATETIME() và GETUTCDATE() cũng giống như SYSDATETIME() và GETDATE(). Hàm trước trả về kiểu giá trị DATETIME2 với thời gian địa phương, trong khi đó hàm sau trả về kiểu giá trị DATETIME và dạng UTC.

Giờ hãy cùng xem các câu query SQL Server để có thể hiểu kĩ hơn về sự khác nhau giữa 3 hàm trên. Nếu bạn chạy các câu query SQL dưới đây trên MS SQL Server management studio, bạn có thể hiểu được sự khác nhau giữa 3 hàm bằng cách xem kết quả trả về của chúng:

SELECT GETDATE() AS LOCAL_TIME;
LOCAL_TIME
2017-12-17 02:57:06.593


SELECT GETUTCDATE() AS UTC_TIME;
UTC_TIME
2017-12-17 19:57:06.597


SELECT SYSDATETIME() AS CURRENT_DATE_TIME;
CURRENT_DATE_TIME
2017-12-17 02:57:06.6151778

Bạn có thể thấy rằng hàm GETDATE trả về giờ địa phương, Hà Nội Việt Nam, nhưng hàm GETUTCDATE lại trả về giờ UTC chậm hơn giờ Việt Nam 7 tiếng (GMT +7). Bạn cũng có thể thấy rằng ngày tháng và giờ trả về bởi hàm SYSDATETIME có giá trị chính xác hơn, tới tận 7 chữ số thập phân sau dấu phảy trong khi đó giá trị trả về bởi hàm GETDATE() và GETUTCDATE() chỉ chứa tới 3 chữ số thập phân.

Việc sử dụng ngày tháng ở trong một câu SQL Server query sẽ rất là phức tạp nhất là nếu bạn không có một kiến thức tốt về cách mà kiểu dữ liệu DateTime hoạt động trong SQL Server. Ví dụ, một trong các câu hỏi thường được hỏi nhất khi phỏng vấn về các câu truy vấn SQL là " Hãy select ra các bản ghi có date là 20151007?" hay một ngày cụ thể nào khác. Vậy bạn sẽ thực hiện nó như nào? Và theo bạn liệu câu truy vấn SQL sau sẽ hoạt động đúng như bạn yêu cầu không?

select * from table where date = '20151007'

Câu trả lời là có thể có hoặc không, nó hoàn toàn phụ thuộc vào dữ liệu trong bảng của bạn. Khi bạn chỉ cung cấp phần ngày tháng trong kiểu dữ liệu DateTime, nó đặt phần giờ thành 00:00:00.000.

Vậy nếu bạn có bất kì bản ghi nào có cùng ngày tháng nhưng khác phần thời gian thì câu truy vấn trên sẽ không hoạt động. Ví dụ, nếu bạn có bảng Order và có 2 đơn đặt hàng, một với order_date = '20151007' và cái còn lại có order_date = '20151007:01:00:00:000', thì câu truy vấn trên sẽ chỉ trả về đơn đặt hàng đầu tiên. Bài viết sẽ giải thích chi tiết hơn về cách tìm các bản ghi giữa các ngày tháng trong SQL Server.

Có rất nhiều các lập trình viên khi sử dụng SQL Server ít khi chú ý đến kiểu dữ liệu của cột ngày tháng như order_date, trade_date, created_date v.v... và rất nhiều người không biết cách mà SQL server sẽ so sánh các điều kiện với dữ liệu trong cột.

1. Câu truy vấn so sánh ngày tháng trong SQL

Để có thể hiểu được cách ngày tháng được so sánh trong SQL, hãy cùng xem 1 ví dụ khi trường DateTime có giá trị ở phần thời gian. Giả sử ta có một bảng sau với cột course_name kiểu varchar và course_date kiểu datetime

IF OBJECT_ID( 'tempdb..#Course' ) IS NOT NULL
DROP TABLE #Course;

CREATE TABLE #Course (course_name varchar(10), course_date datetime);

INSERT INTO #Course VALUES ('Java', '2015-10-06 11:16:10.496');
INSERT INTO #Course VALUES ('MySQL', '2015-10-07 00:00:00.000');
INSERT INTO #Course VALUES ('SQL SERVER', '2015-10-07 11:26:10.193' );
INSERT INTO #Course VALUES ('PostgreSQL', '2015-10-07 12:36:10.393');
INSERT INTO #Course VALUES ('Oracle', '2015-10-08 00:00:00.000');

Bây giờ, bạn cần viết một câu truy vấn lấy tất cả các course vào ngày '2015-10-07'. Một câu truy vấn đúng sẽ trả về 3 bản ghi chứa ngày '2015-10-07' như dưới đây:

'MySQL',      '2015-10-07 00:00:00.000'
'SQL SERVER', '2015-10-07 11:26:10.193'
'PostgreSQL', '2015-10-07 12:36:10.393'

Nếu bạn chỉ so sánh các giá trị thông qua toán tử = và chỉ cung cấp mỗi phần ngày tháng, thì bạn sẽ chỉ được các bản ghi có phần thời gian là 0 vì SQL sẽ chỉ định phần thời gian là 00:00:00.000, như được thấy ở ví dụ sau:

SELECT * FROM #Course WHERE course_date = '2015-10-07'
course_name course_date
MySQL 2015-10-07 00:00:00.000

Như bạn thấy chỉ có 1 bản ghi được trả về, bản ghi có phần thời gian là 0, SQL sẽ không trả về 2 bản ghi còn lại có thời gian khác 0.

2. Giải pháp sử dụng so sánh ngày tháng

Có vẻ như sử dụng BETWEEN là lựa chọn đúng cho việc so sánh ngày tháng mà không tính đến thời gian. Bạn có thể đặt ngày hiện tại và ngày tiếp theo ở mệnh đề này để có thể phủ toàn bộ thời gian trong ngày, nhưng thật không may giải pháp này cũng sẽ không hoạt động đúng. Nó sẽ trả về cả bản ghi bao gồm cả ngày tiếp theo như dưới đây:

SELECT * FROM #Course WHERE course_date between '2015-10-07' and '2015-10-08'
course_name course_date
MySQL       2015-10-07 00:00:00.000
SQL SERVER  2015-10-07 11:26:10.193
PostgreSQL  2015-10-07 12:36:10.393
Oracle      2015-10-08 00:00:00.00

Có thể thấy rằng bản ghi có giá trị ngày tháng là 2015-10-08 00:00:00.000 cũng được lấy về, và đây là điều không được mong muốn. Điều này xảy ra vì mệnh đề BETWEEN sẽ luôn lấy cả các giá trị tới trước nửa đêm của ngày hôm sau.

Giải pháp đúng nhất cho trường hợp này đó là sử dụng các toán tử lớn hơn > và nhỏ hơn <. Và giải pháp này sẽ hoạt động đúng như những gì chúng ta cần. Để sử dụng lựa chọn giải pháp này, chỉ cần đặt ngày cần tìm và ngày tiếp theo nó như trong mệnh đề WHERE như dưới đây:

SELECT * FROM #Course WHERE course_date >= '2015-10-07' and course_date < '2015-10-08'
course_name course_date
MySQL       2015-10-07 00:00:00.000
SQL SERVER  2015-10-07 11:26:10.193
PostgreSQL  2015-10-07 12:36:10.393

Bạn có thể thấy rằng, ta đã có được 3 bản ghi như mong đợi. Chỉ cần nhớ sử dụng toán tử < ở toán hạng thứ 2, điều này sẽ đảm bảo rằng giá trị 2015-10-08 00:00:00.000 sẽ không được lấy.

  • Hàm GETDATE() sẽ trả về giá trị giờ địa phương của server nơi chứa cơ sở dữ liệu của bạn đang chạy. Vậy nếu bạn đang kết nối đến 1 remote SQL server và sử dụng SQL Server Management Studio, thời gian trả về từ phương thức này sẽ khác với thời gian địa phương trên hệ thống của bạn. Và giá trị được trả về là kiểu DATETIME
  • Hàm GETUTCDATE() trả về ngày tháng và thời gian hiện tại theo múi giờ UTC, hay còn được gọi là GMT. Giá trị được trả về cũng là kiểu DATETIME. Bạn có thể sử dụng hàm này để lưu các timestamp không phụ thuộc vào múi giờ.
  • Hàm SYSDATETIME() cũng trả về giá trị giờ địa phương, giioonsg như GETDATE. Nhưng kiểu giá trị trả về là kiểu DATETIME2.

Và cuối cùng bạn có thể thấy rằng sẽ rất dễ nhầm lẫn khi thao tác ngày tháng sử dụng các câu truy vấn SQL. Đôi khi bạn có thể thấy rằng câu truy vấn của mình là tốt rồi nhưng nó sẽ không hoạt động đúng trong môi trường thật. Bởi vì dữ liệu ở 2 môi trường là khác nhau. Khi được Test có thể bạn sẽ không gặp phải các giá trị ngày tháng nửa đêm, lúc này mệnh đề BETWEEN sẽ hoạt động tốt, nhưng khi vào môi trường thật và gặp giá trị trên, nó sẽ sai. Cách so sánh đúng nhất để so sánh chỉ phần giá trị ngày tháng của cột dữ liệu kiểu DateTime đó là sử dụng toán tử điều kiện <= và >. Điều này sẽ đảm bảo rằng bạn sẽ luôn lấy được các giá trị bản ghi có ngày tháng bắt đầu từ nửa đêm và kết thúc trước nửa đêm cùng ngày ví dụ như bắt đầu từ 00:00:00.000 và kết thúc tại 59:59:59.999. Hãy luôn nhớ rằng nếu bạn sử dụng toán tử = ví dụ như date = '2015-10-08' và nếu cột dữ liệu có kiểu DateTime thì SQL Server sẽ sử dụng giá trị 2015-10-08 00:00:00.000 để so sánh.

0