Database
 sql >> Baza danych >  >> RDS >> Database

Używanie AT TIME ZONE do naprawy starego raportu

Gdy tylko zobaczyłem funkcję AT TIME ZONE w SQL 2016, o której pisałem tutaj na sqlperformance.com kilka miesięcy temu przypomniałem sobie raport, który potrzebował tej funkcji. Ten post stanowi studium przypadku, w jaki sposób to zadziałało, które pasuje do wtorku T-SQL prowadzonego w tym miesiącu przez Matta Gordona (@sqlatspeed). (Jest 87. wtorek T-SQL i naprawdę muszę napisać więcej postów na blogu, zwłaszcza o rzeczach, które nie są podpowiadane przez wtorki T-SQL.)

Sytuacja była taka i może to brzmieć znajomo, jeśli przeczytasz mój wcześniejszy post.

Na długo przed pojawieniem się LobsterPot Solutions musiałem sporządzić raport na temat incydentów, które miały miejsce, a w szczególności wykazać, ile razy udzielono odpowiedzi w ramach umowy SLA i ile razy umowa SLA została pominięta. Na przykład, na incydent Sev2, który miał miejsce o 16:30 w dzień powszedni, potrzebna byłaby odpowiedź w ciągu 1 godziny, podczas gdy na incydent Sev2, który miał miejsce o 17:30 w dzień powszedni, należałoby uzyskać odpowiedź w ciągu 3 godzin. Albo coś w tym stylu – zapominam o liczbach, ale pamiętam, że pracownicy helpdesku odetchnęli z ulgą, gdy nadchodziła godzina 17:00, ponieważ nie musieliby tak szybko reagować. 15-minutowe alarmy Sev1 nagle przedłużyłyby się do godziny, a pilność zniknęłaby.

Ale problem pojawiał się, gdy zaczynał się lub kończył czas letni.

Jestem pewien, że jeśli miałeś do czynienia z bazami danych, poznasz ból związany z czasem letnim. Podobno Ben Franklin wpadł na pomysł – i za to powinien uderzyć go piorun czy coś takiego. Australia Zachodnia próbowała go ostatnio przez kilka lat i rozsądnie go porzuciła. Ogólnym konsensusem jest przechowywanie danych o dacie/czasie w UTC.

Jeśli nie przechowujesz danych w UTC, ryzykujesz, że wydarzenie rozpocznie się o 2:45 i zakończy o 2:15 rano po cofnięciu się zegarów. Albo incydent SLA, który zaczyna się o 1:59 tuż przed tym, jak zegary ruszają do przodu. Teraz te czasy są w porządku, jeśli przechowujesz strefę czasową, w której się znajdują, ale w czasie UTC działa zgodnie z oczekiwaniami.

…z wyjątkiem raportowania.

Bo skąd mam wiedzieć, czy dana data była przed rozpoczęciem czasu letniego czy później? Mogę wiedzieć, że incydent miał miejsce o 6:30 w UTC, ale czy to jest 16:30 w Melbourne czy 17:30? Oczywiście mogę zastanowić się, w którym miesiącu się znajduje, ponieważ wiem, że w Melbourne obowiązuje czas letni od pierwszej niedzieli października do pierwszej niedzieli kwietnia, ale jeśli są klienci w Brisbane, Auckland, Los Angeles i Phoenix, i różne miejsca w Indianie, sprawy stają się o wiele bardziej skomplikowane.

Aby to obejść, istniało bardzo niewiele stref czasowych, w których można było zdefiniować umowy SLA dla tej firmy. Po prostu uważano, że za trudno jest zaspokoić więcej niż to. Raport można wtedy dostosować tak, aby mówił „Uważaj, że w określonym dniu strefa czasowa zmieniła się z X na Y”. To było bałaganiarskie, ale zadziałało. Nie było potrzeby sprawdzania rejestru systemu Windows i po prostu działało.

Ale w dzisiejszych czasach zrobiłbym to inaczej.

Teraz użyłbym AT TIME ZONE.

Widzisz, teraz mogę przechowywać informacje o strefie czasowej klienta jako właściwość klienta. Mogłem wtedy przechowywać czas każdego incydentu w UTC, co pozwoliło mi wykonać niezbędne obliczenia dotyczące liczby minut na odpowiedź, rozwiązanie itd., jednocześnie będąc w stanie raportować przy użyciu czasu lokalnego klienta. Zakładając, że mój IncidentTime był faktycznie przechowywany przy użyciu daty i godziny, a nie przesunięcia daty i godziny, byłaby to po prostu kwestia użycia kodu takiego jak:

i.IncidentTime AT TIME ZONE 'UTC' AT TIME ZONE c.tz

…co najpierw umieszcza bezstrefowy i.IncidentTime w UTC, a następnie konwertuje go na strefę czasową klienta. I ta strefa czasowa może być „AUS Eastern Standard Time” lub „Mauritius Standard Time” lub cokolwiek innego. A silnik SQL pozostaje, aby dowiedzieć się, jakiego przesunięcia użyć do tego.

W tym momencie mogę bardzo łatwo utworzyć raport, który zawiera listę wszystkich incydentów w określonym przedziale czasu i pokazuje je w lokalnej strefie czasowej klienta. Mogę przekonwertować wartość na typ danych czasu, a następnie zgłosić, ile incydentów miało miejsce w godzinach pracy, czy nie.

A wszystko to jest bardzo przydatne, ale co z indeksowaniem, aby dobrze sobie z tym poradzić? W końcu AT TIME ZONE to funkcja. Ale zmiana strefy czasowej nie zmienia kolejności, w jakiej incydenty faktycznie miały miejsce, więc powinno być w porządku.

Aby to przetestować, stworzyłem tabelę o nazwie dbo.Incidents i zindeksowałem kolumnę IncidentTime. Następnie uruchomiłem to zapytanie i potwierdziłem, że użyto wyszukiwania indeksu.

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';

Ale chcę filtrować według 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';

Brak szczęścia. Nie podobał mu się indeks.

Ostrzeżenia wynikają z tego, że trzeba przejrzeć znacznie więcej niż dane, które mnie interesują.

Próbowałem nawet użyć tabeli z polem datetimeoffset. W końcu AT TIME ZONE może zmienić kolejność przy przechodzeniu z datetime do datetimeoffset, nawet jeśli kolejność nie jest zmieniana przy przechodzeniu z datetimeoffset na inną datetimeoffset. Próbowałem nawet upewnić się, że to, do czego go porównywałem, znajdowało się w strefie czasowej.

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';

Nadal nie ma szczęścia!

Więc teraz miałem dwie opcje. Jednym z nich było przechowywanie przekonwertowanej wersji obok wersji UTC i indeksowanie jej. Myślę, że to ból. Z pewnością jest to o wiele większa zmiana bazy danych, niż bym chciał.

Inną opcją było użycie tego, co nazywam predykatami pomocniczymi. Są to rzeczy, które widzisz, gdy używasz LIKE. Są to predykaty, które mogą być używane jako predykaty wyszukiwania, ale nie są dokładnie tym, o co prosisz.

Domyślam się, że bez względu na strefę czasową, którą się interesuję, IncidentTimes, na których mi zależy, mieszczą się w bardzo konkretnym zakresie. Ten zakres jest nie więcej niż jeden dzień większy niż mój preferowany zakres po obu stronach.

Dlatego dołączę dwa dodatkowe predykaty.

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');

Teraz mój indeks może być używany. Musi przejrzeć 30 wierszy, zanim przefiltruje je do 28, na których mu zależy – ale to o wiele lepsze niż skanowanie całości.

I wiecie – jest to zachowanie, które widzę cały czas ze zwykłych zapytań, na przykład kiedy robię CAST(myDateTimeColumns AS DATE) =@SomeDate lub używam LIKE.

Nie przeszkadza mi to. AT TIME ZONE doskonale nadaje się do obsługi konwersji w strefie czasowej, a biorąc pod uwagę, co dzieje się z moimi zapytaniami, nie muszę też rezygnować z wydajności.

@rob_farley


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Przyrostowe maskowanie i mapowanie danych:wykrywanie zmian i aktualizowanie…

  2. Dzielenie strun:kontynuacja

  3. Model danych ważnych dat

  4. Jak zainstalować Nextcloud 15 na Ubuntu 18.04

  5. Liczniki Zombie PerfMon, które nigdy nie umierają!