Ngay sau khi tôi nhìn thấy tính năng SQL 2016 AT TIME ZONE, mà tôi đã viết ở đây tại sqlperformance.com a vài tháng trước, tôi nhớ một báo cáo cần tính năng này. Bài đăng này tạo thành một nghiên cứu điển hình về cách tôi thấy nó hoạt động như thế nào, phù hợp với ngày Thứ Ba T-SQL của tháng này do Matt Gordon (@sqlatspeed) tổ chức. (Đó là Thứ Ba T-SQL thứ 87 và tôi thực sự cần viết thêm các bài đăng trên blog, đặc biệt là về những điều mà các Thứ Ba T-SQL không nhắc nhở.)
Tình huống là thế này, và điều này nghe có vẻ quen thuộc nếu bạn đọc bài viết trước đó của tôi.
Rất lâu trước khi Giải pháp LobsterPot tồn tại, tôi cần tạo báo cáo về các sự cố đã xảy ra và đặc biệt là hiển thị số lần phản hồi được thực hiện trong SLA và số lần SLA bị bỏ qua. Ví dụ:sự cố Sev2 xảy ra lúc 4:30 chiều một ngày trong tuần sẽ cần có phản hồi trong vòng 1 giờ, trong khi sự cố Sev2 xảy ra lúc 5:30 chiều một ngày trong tuần sẽ cần có phản hồi trong vòng 3 giờ. Hoặc đại loại như vậy - tôi quên những con số liên quan, nhưng tôi nhớ rằng các nhân viên bộ phận trợ giúp sẽ thở phào nhẹ nhõm khi 5 giờ chiều sẽ quay lại, bởi vì họ sẽ không cần phải phản hồi mọi thứ quá nhanh. Cảnh báo Sev1 kéo dài 15 phút sẽ đột ngột kéo dài đến một giờ và sự khẩn cấp sẽ biến mất.
Nhưng một vấn đề sẽ đến bất cứ khi nào thời gian tiết kiệm ánh sáng ban ngày bắt đầu hoặc kết thúc.
Tôi chắc rằng nếu bạn đã xử lý cơ sở dữ liệu, bạn sẽ biết nỗi đau mà thời gian tiết kiệm ánh sáng ban ngày là như thế nào. Được cho là Ben Franklin đã nảy ra ý tưởng - và cho rằng anh ta nên bị sét đánh hoặc thứ gì đó. Tây Úc đã thử nó trong một vài năm gần đây, và đã từ bỏ nó một cách hợp lý. Và sự đồng thuận chung là lưu trữ dữ liệu ngày / giờ là phải làm như vậy trong UTC.
Nếu bạn không lưu trữ dữ liệu theo giờ UTC, bạn có nguy cơ gặp phải sự kiện bắt đầu lúc 2:45 sáng và kết thúc lúc 2:15 sáng sau khi đồng hồ hoạt động trở lại. Hoặc có một sự cố SLA bắt đầu lúc 1:59 sáng ngay trước khi đồng hồ chạy tiếp. Giờ đây, những khoảng thời gian này sẽ ổn nếu bạn lưu trữ múi giờ mà chúng đang ở, nhưng theo giờ UTC chỉ hoạt động như mong đợi.
… Ngoại trừ việc báo cáo.
Bởi vì làm thế nào tôi có thể biết liệu một ngày cụ thể là trước khi bắt đầu tiết kiệm ánh sáng ban ngày hay sau đó? Tôi có thể biết rằng một sự cố xảy ra lúc 6:30 sáng theo giờ UTC, nhưng đó là 4:30 chiều ở Melbourne hay 5:30 chiều? Rõ ràng là tôi có thể cân nhắc đó là tháng nào, bởi vì tôi biết rằng Melbourne quan sát thời gian tiết kiệm ánh sáng ban ngày từ Chủ nhật đầu tiên của tháng 10 đến Chủ nhật đầu tiên của tháng 4, nhưng nếu có khách hàng ở Brisbane, Auckland, Los Angeles và Phoenix, và nhiều nơi khác nhau trong Indiana, mọi thứ trở nên phức tạp hơn rất nhiều.
Để giải quyết vấn đề này, có rất ít múi giờ mà SLA có thể được xác định cho công ty đó. Nó chỉ được coi là quá khó để đáp ứng nhiều hơn thế. Sau đó, một báo cáo có thể được tùy chỉnh để nói "Hãy xem xét rằng vào một ngày cụ thể, múi giờ đã thay đổi từ X thành Y". Nó cảm thấy lộn xộn, nhưng nó đã hoạt động. Không cần bất kỳ thứ gì để tra cứu sổ đăng ký Windows và về cơ bản nó chỉ hoạt động.
Nhưng ngày nay, tôi đã làm điều đó theo cách khác.
Bây giờ, tôi đã sử dụng TẠI TIME ZONE.
Bạn thấy đấy, bây giờ tôi có thể lưu trữ thông tin múi giờ của khách hàng dưới dạng tài sản của khách hàng. Sau đó, tôi có thể lưu trữ từng thời gian sự cố theo giờ UTC, cho phép tôi thực hiện các phép tính cần thiết về số phút để phản hồi, giải quyết, v.v., đồng thời có thể báo cáo bằng giờ địa phương của khách hàng. Giả sử IncidentTime của tôi thực sự đã được lưu trữ bằng datetime, chứ không phải datetimeoffset, thì vấn đề đơn giản là sử dụng mã như:
i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE c.tz
… Lần đầu tiên đặt i.IncidentTime không có múi giờ thành UTC, trước khi chuyển đổi nó thành múi giờ của khách hàng. Và múi giờ này có thể là 'Giờ chuẩn miền Đông AUS' hoặc 'Giờ chuẩn Mauritius', hoặc bất cứ điều gì. Và SQL Engine được để lại để tìm ra những gì bù đắp để sử dụng cho điều đó.
Tại thời điểm này, tôi có thể rất dễ dàng tạo một báo cáo liệt kê từng sự cố trong một khoảng thời gian và hiển thị báo cáo đó theo múi giờ địa phương của khách hàng. Tôi có thể chuyển đổi giá trị thành kiểu dữ liệu thời gian, sau đó báo cáo có bao nhiêu sự cố xảy ra trong giờ làm việc hay không.
Và tất cả những điều này đều rất hữu ích, nhưng còn việc lập chỉ mục để xử lý điều này một cách độc đáo thì sao? Rốt cuộc, AT TIME ZONE là một chức năng. Nhưng việc thay đổi múi giờ không thay đổi thứ tự thực sự xảy ra sự cố, vì vậy sẽ ổn thôi.
Để kiểm tra điều này, tôi đã tạo một bảng có tên dbo.Incident, và lập chỉ mục cột IncidentTime. Sau đó, tôi chạy truy vấn này và xác nhận rằng tìm kiếm chỉ mục đã được sử dụng.
select i.IncidentTime, itz.LocalTime from dbo.Incidents i cross apply (select i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where i.IncidentTime >= '20170201' and i.IncidentTime < '20170301';
Nhưng tôi muốn lọc trên itz.LocalTime…
select i.IncidentTime, itz.LocalTime from dbo.Incidents i cross apply (select i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where itz.LocalTime >= '20170201' and itz.LocalTime < '20170301';
Không may mắn. Nó không thích chỉ mục.
Các cảnh báo là do tôi phải xem qua nhiều thứ hơn là dữ liệu mà tôi quan tâm.
Tôi thậm chí đã thử sử dụng một bảng có trường datetimeoffset. Rốt cuộc, TẠI TIME ZONE có thể thay đổi thứ tự khi chuyển từ datetime sang datetimeoffset, mặc dù thứ tự không thay đổi khi chuyển từ datetimeoffset sang datetimeoffset khác. Tôi thậm chí còn cố gắng đảm bảo rằng thứ tôi đang so sánh với nó nằm trong múi giờ.
select i.IncidentTime, itz.LocalTime from dbo.IncidentsOffset i cross apply (select i.IncidentTime AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where itz.LocalTime >= cast('20170201' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time' and itz.LocalTime < cast('20170301' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time';
Vẫn không có may mắn!
Vì vậy, bây giờ tôi có hai lựa chọn. Một là lưu trữ phiên bản đã chuyển đổi cùng với phiên bản UTC và lập chỉ mục đó. Tôi nghĩ đó là một nỗi đau. Chắc chắn đó là một sự thay đổi cơ sở dữ liệu nhiều hơn tôi muốn.
Tùy chọn khác là sử dụng những gì tôi gọi là vị từ trợ giúp. Đây là những thứ mà bạn thấy khi bạn sử dụng LIKE. Chúng là những vị từ có thể được sử dụng như Tìm kiếm Dự đoán, nhưng không chính xác là những gì bạn đang yêu cầu.
Tôi nghĩ rằng bất kể tôi quan tâm đến múi giờ nào, thì Thời gian sự cố mà tôi quan tâm đều nằm trong một phạm vi rất cụ thể. Phạm vi đó không lớn hơn một ngày so với phạm vi ưa thích của tôi, ở cả hai bên.
Vì vậy, tôi sẽ bao gồm hai vị từ bổ sung.
select i.IncidentTime, itz.LocalTime from dbo.IncidentsOffset i cross apply (select i.IncidentTime AT TIME ZONE 'Cen. Australia Standard Time') itz (LocalTime) where itz.LocalTime >= cast('20170201' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time' and itz.LocalTime < cast('20170301' as datetimeoffset) AT TIME ZONE 'Cen. Australia Standard Time and i.IncidentTime >= dateadd(day,-1,'20170201') and i.IncidentTime < dateadd(day, 1,'20170301');
Bây giờ, chỉ mục của tôi có thể được sử dụng. Nó phải xem qua 30 hàng trước khi lọc nó thành 28 hàng mà nó quan tâm - nhưng điều đó tốt hơn rất nhiều so với việc quét toàn bộ.
Và bạn biết đấy - đây là loại hành vi mà tôi thấy mọi lúc từ các truy vấn thông thường, như khi tôi thực hiện CAST (myDateTimeColumns AS DATE) =@SomeDate hoặc sử dụng LIKE.
Tôi ổn với điều này. AT TIME ZONE thật tuyệt khi cho phép tôi xử lý các chuyển đổi múi giờ của mình và bằng cách xem xét những gì đang xảy ra với các truy vấn của tôi, tôi cũng không cần phải hy sinh hiệu suất.
@rob_farley