Làm cách nào để loại bỏ phần thời gian của giá trị datetime (SQL Server)?


84

Đây là những gì tôi sử dụng:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

Tôi nghĩ có thể có một cách tốt hơn và thanh lịch hơn.

Yêu cầu:

  • Nó phải càng nhanh càng tốt (càng ít đúc càng tốt).
  • Kết quả cuối cùng phải là một datetimekiểu, không phải là một chuỗi.

Câu trả lời:


116

SQL Server 2008 trở lên

Trong SQL Server 2008 trở lên, tất nhiên là cách nhanh nhất Convert(date, @date). Điều này có thể được chuyển trở lại a datetimehoặc datetime2nếu cần.

Điều gì thực sự tốt nhất trong SQL Server 2005 và cũ hơn?

Tôi đã thấy các tuyên bố không nhất quán về việc cắt ngắn thời gian từ một ngày trong SQL Server nhanh nhất và một số người thậm chí còn nói rằng họ đã thử nghiệm, nhưng trải nghiệm của tôi thì khác. Vì vậy, hãy thực hiện một số kiểm tra nghiêm ngặt hơn và để mọi người có kịch bản để nếu tôi mắc bất kỳ lỗi nào, mọi người có thể sửa cho tôi.

Chuyển đổi nổi không chính xác

Đầu tiên, tôi sẽ tránh chuyển đổi datetimesang float, vì nó không chuyển đổi chính xác. Bạn có thể tránh xa việc thực hiện chính xác việc loại bỏ thời gian, nhưng tôi nghĩ không nên sử dụng nó vì nó ngầm thông báo với các nhà phát triển rằng đây là một hoạt động an toàn và không phải vậy . Hãy xem:

declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops

Đây không phải là điều mà chúng ta nên dạy mọi người về mã của chúng ta hoặc trong các ví dụ trực tuyến của chúng ta.

Ngoài ra, nó thậm chí không phải là cách nhanh nhất!

Bằng chứng - Kiểm tra hiệu suất

Nếu bạn muốn tự mình thực hiện một số thử nghiệm để xem các phương pháp khác nhau thực sự xếp chồng lên nhau như thế nào, thì bạn sẽ cần tập lệnh thiết lập này để chạy các thử nghiệm sâu hơn:

create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
   insert AllDay
   select * from (
      select Tm =
         DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
      from AllDay
   ) X
   where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay;  -- 25,920,000 rows

Xin lưu ý rằng điều này tạo ra một bảng 427,57 MB trong cơ sở dữ liệu của bạn và sẽ mất khoảng 15-30 phút để chạy. Nếu cơ sở dữ liệu của bạn nhỏ và được đặt ở mức tăng trưởng 10% thì sẽ mất nhiều thời gian hơn nếu bạn kích thước đủ lớn trước.

Bây giờ là kịch bản kiểm tra hiệu suất thực tế. Xin lưu ý rằng mục đích là không trả lại các hàng cho khách hàng vì điều này rất đắt trên 26 triệu hàng và sẽ che giấu sự khác biệt về hiệu suất giữa các phương pháp.

Kết quả hoạt động

set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
    @dd date,
    @d datetime,
    @di int,
    @df float,
    @dv varchar(10);

-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.

-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;

Một số phân tích lan man

Một số lưu ý về điều này. Trước hết, nếu chỉ thực hiện GROUP BY hoặc so sánh, thì không cần phải chuyển đổi trở lại datetime. Vì vậy, bạn có thể tiết kiệm một số CPU bằng cách tránh điều đó, trừ khi bạn cần giá trị cuối cùng cho mục đích hiển thị. Bạn thậm chí có thể NHÓM THEO giá trị chưa được chuyển đổi và chỉ đặt chuyển đổi trong mệnh đề CHỌN:

select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

Ngoài ra, hãy xem cách chuyển đổi số chỉ mất thêm một chút thời gian để chuyển đổi trở lại datetime, nhưng varcharchuyển đổi gần như tăng gấp đôi? Điều này cho thấy phần của CPU được dành cho tính toán ngày tháng trong các truy vấn. Có một số phần của việc sử dụng CPU không liên quan đến tính toán ngày tháng và điều này dường như gần bằng 19875 ms trong các truy vấn trên. Sau đó, chuyển đổi cần một số tiền bổ sung, vì vậy nếu có hai chuyển đổi, số tiền đó sẽ được sử dụng gần gấp đôi.

Kiểm tra nhiều hơn cho thấy rằng so với Convert(, 112), Convert(, 101)truy vấn có một số chi phí CPU bổ sung (vì nó sử dụng lâu hơn varchar?), Bởi vì chuyển đổi thứ hai trở lại datekhông tốn nhiều như chuyển đổi ban đầu varchar, nhưng Convert(, 112)nó gần với cùng 20000 ms chi phí cơ sở CPU.

Dưới đây là những tính toán về thời gian CPU mà tôi đã sử dụng cho phân tích ở trên:

     method   round  single   base
-----------  ------  ------  -----
       date   21324   19891  18458
        int   23031   21453  19875
   datediff   23782   23218  22654
      float   36891   29312  21733
varchar-112  102984   64016  25048
varchar-101  123375   65609   7843
  • vòng là thời gian CPU cho một vòng quay trở lại datetime.

  • đơn là thời gian CPU cho một lần chuyển đổi sang kiểu dữ liệu thay thế (kiểu dữ liệu có tác dụng phụ là loại bỏ phần thời gian).

  • cơ sở là việc tính trừ từ singlephần chênh lệch giữa hai lời gọi: single - (round - single). Đó là một hình vẽ quả bóng giả định việc chuyển đổi đến và đi từ kiểu dữ liệu đó và datetimegần giống nhau theo cả hai hướng. Có vẻ như giả định này không hoàn hảo nhưng gần đúng vì tất cả các giá trị đều gần 20000 ms với chỉ một ngoại lệ.

Một điều thú vị nữa là chi phí cơ sở gần bằng với Convert(date)phương pháp đơn (phải gần như bằng 0, vì máy chủ có thể trích xuất nội bộ phần ngày số nguyên ngay trong bốn byte đầu tiên của datetimekiểu dữ liệu).

Phần kết luận

Vì vậy, nó trông như thế nào là varcharphương pháp chuyển đổi một hướng mất khoảng 1,8 μs và DateDiffphương pháp một hướng mất khoảng 0,18 μs. Tôi đang dựa trên điều này dựa trên thời gian "CPU cơ bản" thận trọng nhất trong thử nghiệm của tôi là tổng cộng 18458 ms cho 25,920,000 hàng, vì vậy 23218 ms / 25920000 = 0,18 μs. Sự cải thiện 10x rõ ràng có vẻ như rất nhiều, nhưng nói thật là nó khá nhỏ cho đến khi bạn xử lý hàng trăm nghìn hàng (617k hàng = tiết kiệm 1 giây).

Ngay cả với cải tiến tuyệt đối nhỏ này, theo ý kiến ​​của tôi, DateAddphương pháp này vẫn chiến thắng vì nó là sự kết hợp tốt nhất giữa hiệu suất và độ rõ ràng. Câu trả lời đòi hỏi một "con số kỳ diệu" 0.50000004là sẽ cắn ai đó vào một ngày nào đó (năm số 0 hoặc sáu ???), cộng với nó khó hiểu hơn.

Ghi chú bổ sung

Khi tôi có thời gian, tôi sẽ thay đổi 0.50000004để '12:00:00.003'xem nó hoạt động như thế nào. Nó được chuyển đổi thành cùng một datetimegiá trị và tôi thấy nó dễ nhớ hơn nhiều.

Đối với những người quan tâm, các thử nghiệm ở trên đã được chạy trên máy chủ mà @@ Phiên bản trả về như sau:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) ngày 9 tháng 7 năm 2008 14:43:34 Bản quyền (c) 1988-2008 Microsoft Corporation Standard Edition trên Windows NT 5.2 (Bản dựng 3790: Gói dịch vụ 2)


1
+1 Bạn đã kiểm tra phiên bản này bằng cách nào của SQL Server?
Martin Smith

1
Có vẻ như bạn có đơnvòng ngược trong bảng của mình. Ngoài ra, có bất kỳ sự khác biệt nào về thời gian nếu bạn sử dụng charthay vì varchar?
Gabe

1
@Gabe cảm ơn, đã sửa. Char dường như giống hệt như varchar.
ErikE

Trong Oracle có select round(sysdate) from dualvà chúng tôi chắc chắn cần điều đó trong Sql Server.
Denis Valeev

3
@Roman Nếu bạn đang làm việc với SQL Server 2008 trở lên, thì có, chuyển đổi sang datekiểu dữ liệu là nhanh nhất, như thể hiện trong các thử nghiệm của tôi ở trên.
ErikE

30

SQL Server 2008 có kiểu dữ liệu ngày tháng mới và điều này đơn giản hóa vấn đề này thành:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)

1
Tôi đã nhập nhầm 0218 thay vì 2018 là năm và DATEADD(DATEDIFF())phương pháp cắt phần thời gian ném ra một ngoại lệ. Khi tôi đúc kết quả trở lại datetime2phương pháp của bạn hoạt động độc đáoselect cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
Bernhard Döbler

18

Itzik Ben-Gan trong Tính toán DATETIME, Phần 1 (Tạp chí SQL Server, tháng 2 năm 2007) cho thấy ba phương pháp thực hiện chuyển đổi như vậy ( chậm nhất đến nhanh nhất ; sự khác biệt giữa phương pháp thứ hai và thứ ba là nhỏ):

SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)

SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)

SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)

Kỹ thuật của bạn (đúc để nổi ) được gợi ý bởi một độc giả trong số tháng 4 của tạp chí. Theo ông, nó có hiệu suất tương đương với kỹ thuật thứ hai đã trình bày ở trên.


1
Theo tôi casting để nổi không phải là tốt nhất. Vui lòng xem câu trả lời của tôi
ErikE

1
@Emtucifor Tôi đồng ý rằng phương pháp thứ 3 rất khó hiểu vì giá trị 0.50000004 , nhưng nó là phương pháp nhanh nhất và các thử nghiệm của bạn xác nhận điều đó . Do đó, nó đáp ứng yêu cầu nhanh nhất có thể .
Marek Grzenkowicz

1
@Emtucifor Ngoài ra, đây là những gì bài viết tôi đã liên kết nói về giá trị 0.50000004 : Mặc dù biểu thức này ngắn (và hiệu quả, như tôi sẽ trình bày ngay sau đây), tôi phải nói rằng tôi cảm thấy không thoải mái với nó . Tôi không chắc mình có thể tìm hiểu chính xác lý do tại sao — có thể vì nó quá kỹ thuật và bạn không thể thấy logic liên quan đến ngày giờ trong đó.
Marek Grzenkowicz

2
Nếu chúng ta sử dụng phương pháp này, tôi muốn SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime)thay thế hơn, vì nó có ý nghĩa với tôi và imo dễ nhớ hơn nhiều.
ErikE

6
Điều này bây giờ là nhanh nhất trong SQL 2008: Convert(date, GetDate()).
ErikE

12

Của bạn CAST- FLOOR- CASTđã có vẻ là cách tối ưu, ít nhất là trên MS SQL Server 2005.

Một số giải pháp khác mà tôi đã thấy có chuyển đổi chuỗi, như Select Convert(varchar(11), getdate(),101)trong chúng, chậm hơn bởi hệ số 10.


1
Chúng tôi sử dụng phương pháp do Michael Stum đề xuất trong một trong những sản phẩm của chúng tôi và nó hoạt động như một sự quyến rũ.
Chris Roberts

3
Đây không phải là cách tối ưu. Vui lòng xem câu trả lời của tôi trên cùng trang này.
ErikE

4

Vui lòng thử:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]

1

SQL2005: Tôi khuyên bạn nên truyền thay vì dateadd. Ví dụ,

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

trung bình nhanh hơn khoảng 10% trên tập dữ liệu của tôi, hơn

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(và truyền vào smalldatetime vẫn nhanh hơn)

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.