Staramy się zoptymalizować projekt hurtowni danych, który będzie obsługiwał raportowanie danych w wielu strefach czasowych. Na przykład, możemy mieć raport dotyczący aktywności z miesiąca (miliony wierszy), która musi pokazywać aktywność pogrupowaną według pory dnia. I oczywiście ta godzina dnia musi być godziną „lokalną” dla danej strefy czasowej.
Mieliśmy projekt, który działał dobrze, kiedy tylko wspieraliśmy UTC i jeden czas lokalny. Standardowy projekt wymiarów daty i godziny dla czasu UTC i czasu lokalnego, id w tabelach faktów. Jednak podejście to nie wydaje się skalowane, jeśli musimy wspierać raportowanie dla ponad 100 stref czasowych.
Nasze tabele faktów stałyby się bardzo szerokie. Musielibyśmy również rozwiązać problem składni w SQL polegający na określeniu, który identyfikator daty i godziny ma zostać użyty do grupowania w dowolnym przebiegu raportu. Być może bardzo duże oświadczenie CASE?
Widziałem pewne sugestie, aby uzyskać wszystkie dane według zakresu czasu UTC, który obejmujesz, a następnie wróć do warstwy prezentacji, aby przekonwertować ją na lokalną i agregować tam, ale ograniczone testy z SSRS sugerują, że będzie to bardzo powolne.
Zapoznałem się również z kilkoma książkami na ten temat i wszystkie wydają się mówić, że mają tylko UTC i dokonują konwersji na wystawie lub mają UTC i jedną lokalną. Byłbym wdzięczny za wszelkie myśli i sugestie.
Uwaga: To pytanie jest podobne do: Obsługa stref czasowych w magazynie danych / hurtowni , ale nie mogę skomentować tego pytania, więc czułem, że zasługuje ono na własne pytanie.
Aktualizacja: wybrałem odpowiedź Aarona po tym, jak dokonał istotnych aktualizacji i opublikował przykładowy kod i diagramy. Moje wcześniejsze komentarze do jego odpowiedzi nie będą miały większego sensu, ponieważ odnosiły się do oryginalnej edycji odpowiedzi. Spróbuję wrócić i zaktualizować to ponownie, jeśli będzie to uzasadnione
Odpowiedzi:
Rozwiązałem ten problem, mając bardzo prostą tabelę kalendarza - każdy rok ma jeden wiersz na obsługiwaną strefę czasową , ze standardowym przesunięciem i początkową datą / końcową datą czasu DST i jego przesunięciem (jeśli ta strefa czasowa to obsługuje). Następnie wbudowana, powiązana ze schematem funkcja o wartościach przechowywanych w tabeli, która zajmuje czas źródłowy (oczywiście w UTC) i dodaje / odejmuje przesunięcie.
To oczywiście nigdy nie zadziała wyjątkowo dobrze, jeśli raportujesz w stosunku do dużej części danych; partycjonowanie może wydawać się pomocne, ale nadal będziesz mieć przypadki, w których ostatnie kilka godzin w ciągu jednego roku lub kilka pierwszych godzin w następnym roku faktycznie należy do innego roku po konwersji do określonej strefy czasowej - więc nigdy nie możesz uzyskać prawdziwej partycji izolacja, z wyjątkiem sytuacji, gdy zakres raportowania nie obejmuje 31 grudnia lub 1 stycznia.
Jest kilka dziwnych przypadków, które należy wziąć pod uwagę:
2014-11-02 05:30 UTC i 2014-11-02 06:30 UTC oba przeliczają się na 01:30 AM we wschodniej strefie czasowej, na przykład (jeden po raz pierwszy 01:30 został trafiony lokalnie, a następnie jeden po raz drugi zegary cofały się od 2:00 do 1:00 i upłynęły kolejne pół godziny). Musisz więc zdecydować, jak poradzić sobie z tą godziną raportowania - zgodnie z UTC, powinieneś zobaczyć podwójny ruch lub wielkość tego, co mierzysz, gdy te dwie godziny zostaną zmapowane do jednej godziny w strefie czasowej, w której obserwuje się czas letni. Może to również grać w fajne gry z sekwencjonowaniem wydarzeń, ponieważ coś, co logicznie musiało się wydarzyć po tym, co mogło się pojawićnastąpi przed tym, gdy czas zostanie ustawiony na jedną godzinę zamiast dwóch. Skrajnym przykładem jest wyświetlenie strony, które nastąpiło o 05:59 UTC, a następnie kliknięcie, które nastąpiło o 06:00 UTC. W czasie UTC nastąpiło to w odstępie minuty, ale po przeliczeniu na czas wschodni widok nastąpił o 01:59, a kliknięcie nastąpiło godzinę wcześniej.
2014-03-09 02:30 nigdy nie zdarza się w USA. Jest tak, ponieważ o 2:00 rano przesuwamy zegary do 3:00 rano. Prawdopodobnie będziesz chciał zgłosić błąd, jeśli użytkownik wprowadzi taki czas i poprosi o konwersję na UTC lub zaprojektowanie formularza, aby użytkownicy nie mogli wybrać takiego czasu.
Nawet biorąc pod uwagę te skrajne przypadki, nadal uważam, że masz właściwe podejście: przechowuj dane w UTC. Znacznie łatwiej jest mapować dane do innych stref czasowych z UTC niż z jednej strefy czasowej do innej strefy czasowej, szczególnie gdy różne strefy czasowe rozpoczynają / kończą czas letni w różnych datach, a nawet ta sama strefa czasowa może przełączać się przy użyciu różnych reguł w różnych latach ( na przykład USA zmieniły zasady około 6 lat temu).
Do tego wszystkiego będziesz chciał użyć tabeli kalendarza, a nie jakiegoś gigantycznego
CASE
wyrażenia (nie instrukcji ). Właśnie napisałem na ten temat trzyczęściową serię dla MSSQLTips.com ; Myślę, że trzecia część będzie dla Ciebie najbardziej użyteczna:http://www.mssqltips.com/sqlservertip/3173/handle-conversion-between-time-zones-in-sql-server--part-1/
http://www.mssqltips.com/sqlservertip/3174/handle-conversion-between-time-zones-in-sql-server--part-2/
http://www.mssqltips.com/sqlservertip/3175/handle-conversion-between-time-zones-in-sql-server--part-3/
Tymczasem prawdziwy przykład na żywo
Powiedzmy, że masz bardzo prostą tabelę faktów. Jedyny fakt, na którym mi zależy w tym przypadku, to czas wydarzenia, ale dodam bezsensowny identyfikator GUID, aby stół był wystarczająco szeroki, aby o niego dbać. Ponownie, mówiąc wprost, tabela faktów przechowuje zdarzenia tylko w czasie UTC i UTC. Dodałem nawet kolumnę,
_UTC
aby nie było zamieszania.Teraz załadujmy do naszej tabeli faktów 10 000 000 wierszy - co 3 sekundy (1200 wierszy na godzinę) od 30.12.2013 o północy do czasu UTC do pewnego czasu po 5 rano UTC w dniu 12.12.2014. Zapewnia to, że dane przekraczają granicę roku, a także czas letni do przodu i do tyłu dla wielu stref czasowych. To wygląda naprawdę przerażająco, ale zajęło mi ~ 9 sekund w moim systemie. Tabela powinna zakończyć się na około 325 MB.
I tylko, aby pokazać, jak będzie wyglądać typowe zapytanie wyszukiwania w przypadku tabeli wierszy 10 mm, jeśli uruchomię to zapytanie:
Dostaję ten plan, który zwraca za 25 milisekund *, wykonując 358 odczytów, aby zwrócić 72 godzinne sumy:
* Czas trwania mierzony przez nasz darmowy SQL Sentry Plan Explorer , który odrzuca wyniki, więc nie obejmuje czasu przesyłania danych w sieci, renderowania itp. Jako dodatkowe zastrzeżenie, pracuję dla SQL Sentry.
Oczywiście zajmie to trochę więcej czasu, jeśli zwiększę zasięg - miesiąc danych zajmuje 258 ms, dwa miesiące - 500 ms itd. Równoległość może się uruchomić:
W tym miejscu zaczynasz myśleć o innych, lepszych rozwiązaniach w celu zaspokojenia zapytań dotyczących raportowania, i nie ma to nic wspólnego z tym, jaką strefę czasową wyświetli twój wynik. Nie będę się w to angażował, chcę tylko wykazać, że konwersja strefy czasowej tak naprawdę nie spowoduje, że zapytania związane z raportowaniem będą do niczego więcej ssące, a mogą już być do bani, jeśli otrzymujesz duże zakresy, które nie są obsługiwane przez odpowiednie indeksy. Będę trzymać się małych zakresów dat, aby pokazać, że logika jest poprawna, i pozwolę ci się martwić o to, czy Twoje zapytania raportowe oparte na zakresie będą działać poprawnie, niezależnie od konwersji strefy czasowej.
Okej, teraz potrzebujemy tabel do przechowywania naszych stref czasowych (z przesunięciami, w minutach, ponieważ nie wszyscy są nawet godziny wolne od UTC) i dat zmian czasu letniego dla każdego obsługiwanego roku. Dla uproszczenia wprowadzę tylko kilka stref czasowych i jeden rok, aby dopasować powyższe dane.
Uwzględniono kilka stref czasowych dla odmiany, niektóre z przesunięciem o pół godziny, niektóre nie przestrzegają czasu letniego. Zauważ, że Australia na półkuli południowej obserwuje DST podczas naszej zimy, więc ich zegary cofają się w kwietniu i przesuwają w październiku. (Powyższa tabela zmienia nazwy, ale nie jestem pewien, jak sprawić, by było to mniej skomplikowane w strefach czasowych półkuli południowej).
Teraz tabela kalendarza, aby wiedzieć, kiedy zmieniają się TZ. Zamierzam tylko wstawić wiersze zainteresowania (każda strefa czasowa powyżej i tylko zmiany czasu letniego w 2014 r.). Dla ułatwienia obliczeń tam i z powrotem przechowuję zarówno moment w UTC, w którym zmienia się strefa czasowa, jak i ten sam moment w czasie lokalnym. W strefach czasowych, w których nie obserwuje się czasu letniego, jest to standard przez cały rok, a czas letni rozpoczyna się 1 stycznia.
Zdecydowanie możesz wypełnić to algorytmami (a nadchodząca seria końcówek używa sprytnych technik opartych na zestawie, jeśli sam to powiem), zamiast zapętlać, ręcznie wypełniać, co masz. W przypadku tej odpowiedzi postanowiłem ręcznie wypełnić jeden rok dla pięciu stref czasowych i nie zamierzam zawracać sobie głowy żadnymi fantazyjnymi sztuczkami.
Okej, więc mamy nasze dane faktów i nasze tabele „wymiarów” (przewracam się, kiedy to mówię), więc jaka jest logika? Zakładam, że użytkownicy wybiorą strefę czasową i wprowadzą zakres dat dla zapytania. Zakładam również, że zakres dat będzie obejmował pełne dni we własnej strefie czasowej; bez częściowych dni, nie wspominając o niepełnych godzinach. Przekażą więc datę początkową, końcową i TimeZoneID. Następnie użyjemy funkcji skalarnej do konwersji daty początkowej / końcowej z tej strefy czasowej na UTC, co pozwoli nam filtrować dane na podstawie zakresu UTC. Po wykonaniu tej czynności i przeprowadzeniu na niej naszych agregacji, możemy zastosować konwersję zgrupowanych czasów z powrotem do źródłowej strefy czasowej, przed wyświetleniem ich użytkownikowi.
Skalarny UDF:
I funkcja wyceniana w tabeli:
I procedura, która go używa ( edycja : zaktualizowano, aby obsługiwał grupowanie przesunięć o 30 minut):
(Możesz spróbować przejść do zwarcia lub osobnej procedury składowanej w przypadku, gdy użytkownik chce zgłaszać się w UTC - oczywiście tłumaczenie na i z UTC będzie marnowaniem czasu.)
Przykładowe połączenie:
Zwraca za 41 ms * i generuje ten plan:
* Ponownie, z odrzuconymi wynikami.
Przez 2 miesiące zwraca za 507 ms, a plan jest identyczny z wyjątkiem liczby wierszy:
Chociaż nieco bardziej skomplikowany i nieco wydłużający się czas działania, jestem całkiem pewien, że tego rodzaju podejście zadziała o wiele, wiele lepiej niż podejście oparte na tabeli brydża. Jest to przykładowy przykład odpowiedzi dba.se; Jestem pewien, że moją logikę i wydajność mogliby poprawić ludzie mądrzejsi ode mnie.
Możesz przejrzeć dane, aby zobaczyć przypadki brzegowe, o których mówię - brak wiersza wyników dla godziny, w której zegary przesuwają się do przodu, dwa rzędy dla godziny, w której cofały się (i ta godzina zdarzyła się dwa razy). Możesz także grać ze złymi wartościami; na przykład, jeśli zdasz 20140309 02:30 czasu wschodniego, nie zadziała to zbyt dobrze.
Mogę nie mieć wszystkich założeń dotyczących tego, jak będzie działało Twoje raportowanie, więc może być konieczne wprowadzenie pewnych korekt. Ale myślę, że obejmuje to podstawy.
źródło
Czy możesz wykonać transformację w zapisanym proc lub sparametryzowanym widoku zamiast warstwy prezentacji? Inną opcją jest utworzenie kostki i wykonanie obliczeń w kostce.
Wyjaśnienie z komentarzy:
źródło