Data Warehouse-Design für die Berichterstellung anhand von Daten für viele Zeitzonen

10

Wir versuchen, ein Data Warehouse-Design zu optimieren, das die Berichterstellung für Daten für viele Zeitzonen unterstützt. Beispielsweise haben wir möglicherweise einen Bericht über die Aktivität eines Monats (Millionen von Zeilen), in dem die Aktivität nach Tagesstunden gruppiert angezeigt werden muss. Und natürlich muss diese Stunde des Tages die "lokale" Stunde für die gegebene Zeitzone sein.

Wir hatten ein Design, das gut funktionierte, als wir nur UTC und eine Ortszeit unterstützten. Das Standarddesign der Datums- und Zeitdimensionen für UTC und Ortszeit finden Sie in den Faktentabellen. Dieser Ansatz scheint jedoch nicht skalierbar zu sein, wenn wir die Berichterstellung für mehr als 100 Zeitzonen unterstützen müssen.

Unsere Faktentabellen würden sehr breit werden. Außerdem müssten wir das Syntaxproblem in SQL lösen, indem wir angeben, welche Datums- und Uhrzeit-IDs für die Gruppierung in einem bestimmten Lauf des Berichts verwendet werden sollen. Vielleicht eine sehr große CASE-Anweisung?

Ich habe einige Vorschläge gesehen, um alle Daten nach dem von Ihnen abgedeckten UTC-Zeitbereich abzurufen und sie dann an die Präsentationsschicht zurückzugeben, um sie dort in lokale und aggregierte Daten zu konvertieren. Eingeschränkte Tests mit SSRS deuten jedoch darauf hin, dass dies extrem langsam sein wird.

Ich habe auch einige Bücher zu diesem Thema konsultiert, und alle scheinen zu sagen, dass nur UTC und Konvertierung angezeigt werden oder dass UTC und ein Lokal vorhanden sind. Würde mich über Gedanken und Vorschläge freuen.

Hinweis: Diese Frage ähnelt der Behandlung von Zeitzonen in Data Mart / Warehouse , aber ich kann diese Frage nicht kommentieren, daher war dies eine eigene Frage verdient.

Update: Ich habe Aarons Antwort ausgewählt, nachdem er einige wichtige Updates vorgenommen und Beispielcode und Diagramme veröffentlicht hatte. Meine früheren Kommentare zu seiner Antwort machen keinen Sinn mehr, da sie sich auf die ursprüngliche Bearbeitung der Antwort beziehen. Ich werde versuchen, dies erneut zu aktualisieren, wenn dies gerechtfertigt ist

Peter M.
quelle
Wie weit gehen Ihre Daten im Zusammenhang mit meiner Antwort (und den Updates, die ich später veröffentlichen werde) zurück? Wird ein monatlicher Bericht 28-31 Sätze von 24-Stunden-Blöcken anzeigen? Wird es immer "ein Kalendermonat" sein oder könnte es wirklich irgendein Bereich sein? Was sollte angezeigt werden, wenn eines der Daten ein DST-Vorwärts- / Rücklaufdatum für die ausgewählte Zeitzone ist? Was genau ist die Eingabe für den Bericht? Konvertieren Sie die Ortszeit des Benutzers basierend auf seinem aktuellen Gebietsschema automatisch in UTC, haben sie Einstellungen, wählen sie manuell aus oder schließen Sie auf andere Weise oder möchten Sie, dass die Abfrage dies herausfindet?
Aaron Bertrand
Um Ihre Fragen zu beantworten: Die Daten können bis zu 2 Jahre zurückreichen. Wir haben einige Berichte, die nur einen Satz von 24-Stunden-Blöcken anzeigen, und andere Berichte, die einen 24-Stunden-Block pro Tag im Berichtsdatumsbereich enthalten. Der Datumsbereich kann wirklich alles sein, was der Benutzer möchte. Der Benutzer wählt das Start- und Enddatum (und die Uhrzeit) aus und wählt dann die gewünschte Zeitzone aus einer Dropdown-Liste aus
Peter M
Mögliches Duplikat der Bearbeitungszeitzonen in Data Mart / Warehouse
Jon of All Trades

Antworten:

18

Ich habe dieses Problem durch eine sehr einfache Kalendertabelle gelöst - jedes Jahr gibt es eine Zeile pro unterstützter Zeitzone mit dem Standardversatz und der Start- / Endzeit / Endzeit der Sommerzeit und deren Versatz (sofern diese Zeitzone dies unterstützt). Dann eine Inline-Funktion mit Schema-Bindung und Tabellenwert, die die Quellzeit (natürlich in UTC) benötigt und den Offset addiert / subtrahiert.

Dies wird offensichtlich nie besonders gut funktionieren, wenn Sie über einen großen Teil der Daten berichten. Die Partitionierung scheint zu helfen, aber es gibt immer noch Fälle, in denen die letzten Stunden in einem Jahr oder die ersten Stunden im nächsten Jahr tatsächlich zu einem anderen Jahr gehören, wenn sie in eine bestimmte Zeitzone konvertiert werden - sodass Sie niemals eine echte Partition erhalten können Isolation, außer wenn Ihr Berichtsbereich den 31. Dezember oder den 1. Januar nicht umfasst.

Es gibt ein paar seltsame Randfälle, die Sie berücksichtigen müssen:

  • 2014-11-02 05:30 UTC und 2014-11-02 06:30 UTC konvertieren beide zum Beispiel in der östlichen Zeitzone auf 01:30 Uhr (eine zum ersten Mal wurde 01:30 lokal getroffen und dann eine zum zweiten Mal, als die Uhren von 2:00 Uhr auf 1:00 Uhr zurückgingen und eine weitere halbe Stunde verstrichen war). Sie müssen also entscheiden, wie mit dieser Stunde der Berichterstellung umgegangen werden soll. Laut UTC sollten Sie den doppelten Datenverkehr oder das doppelte Volumen von allem, was Sie messen, sehen, sobald diese zwei Stunden einer einzelnen Stunde in einer Zeitzone zugeordnet sind, in der die Sommerzeit eingehalten wird. Dies kann auch lustige Spiele mit Sequenzierung von Ereignissen spielen, da etwas, das logischerweise passieren musste, nachdem etwas anderes erscheinen konntebevor es passiert, sobald das Timing auf eine Stunde anstatt auf zwei eingestellt ist. Ein extremes Beispiel ist eine Seitenansicht, die um 05:59 UTC erfolgte, und ein Klick, der um 06:00 UTC erfolgte. In der UTC-Zeit passierten diese im Abstand von einer Minute, aber bei der Umrechnung in die östliche Zeit erfolgte die Ansicht um 01:59 Uhr, und das Klicken erfolgte eine Stunde zuvor.

  • 2014-03-09 02:30 kommt in den USA nie vor. Dies liegt daran, dass wir um 2:00 Uhr die Uhr auf 3:00 Uhr vorwärts rollen. Daher möchten Sie wahrscheinlich einen Fehler auslösen, wenn der Benutzer eine solche Zeit eingibt und Sie auffordert, diese in UTC zu konvertieren, oder Ihr Formular so zu gestalten, dass Benutzer eine solche Zeit nicht auswählen können.

Selbst unter Berücksichtigung dieser Randfälle denke ich immer noch, dass Sie den richtigen Ansatz haben: Speichern Sie die Daten in UTC. Es ist viel einfacher, Daten von UTC auf andere Zeitzonen abzubilden als von einer Zeitzone auf eine andere Zeitzone, insbesondere wenn verschiedene Zeitzonen die Sommerzeit an verschiedenen Daten beginnen / beenden und sogar dieselbe Zeitzone in verschiedenen Jahren nach unterschiedlichen Regeln wechseln kann ( Zum Beispiel haben die USA die Regeln vor ungefähr 6 Jahren geändert.

Sie werden für all dies eine Kalendertabelle verwenden wollen, keinen gigantischen CASE Ausdruck (keine Aussage ). Ich habe gerade eine dreiteilige Serie für MSSQLTips.com darüber geschrieben. Ich denke, der 3. Teil wird für Sie am nützlichsten sein:

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/


In der Zwischenzeit ein echtes Live-Beispiel

Angenommen, Sie haben eine sehr einfache Faktentabelle. Die einzige Tatsache, die mir in diesem Fall wichtig ist, ist die Ereigniszeit, aber ich werde eine bedeutungslose GUID hinzufügen, um die Tabelle breit genug zu machen, um mich darum zu kümmern. Um genau zu sein, speichert die Faktentabelle Ereignisse nur in UTC-Zeit und UTC-Zeit. Ich habe die Spalte sogar mit einem Suffix versehen, _UTCdamit keine Verwirrung entsteht.

CREATE TABLE dbo.Fact
(
  EventTime_UTC DATETIME NOT NULL,
  Filler UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID()
);
GO

CREATE CLUSTERED INDEX x ON dbo.Fact(EventTime_UTC);
GO

Laden wir nun unsere Faktentabelle mit 10.000.000 Zeilen, die alle 3 Sekunden (1.200 Zeilen pro Stunde) vom 30.12.2013 um Mitternacht UTC bis kurz nach 5 Uhr UTC am 12.12.2014 darstellen. Dies stellt sicher, dass die Daten eine Jahresgrenze überschreiten sowie die Sommerzeit für mehrere Zeitzonen vorwärts und rückwärts. Das sieht wirklich beängstigend aus, hat aber auf meinem System ~ 9 Sekunden gedauert. Die Tabelle sollte ungefähr 325 MB groß sein.

;WITH x(c) AS 
(
  SELECT TOP (10000000) DATEADD(SECOND, 
    3*(ROW_NUMBER() OVER (ORDER BY s1.[object_id])-1),
    '20131230')
  FROM sys.all_columns AS s1
  CROSS JOIN sys.all_columns AS s2
  ORDER BY s1.[object_id]
)
INSERT dbo.Fact WITH (TABLOCKX) (EventTime_UTC) 
  SELECT c FROM x;

Und nur um zu zeigen, wie eine typische Suchabfrage für diese 10-MM-Zeilentabelle aussehen wird, wenn ich diese Abfrage ausführe:

SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, EventTime_UTC), 0),
  COUNT(*)
FROM dbo.Fact 
WHERE EventTime_UTC >= '20140308'
AND EventTime_UTC < '20140311'
GROUP BY DATEADD(HOUR, DATEDIFF(HOUR, 0, EventTime_UTC), 0);

Ich erhalte diesen Plan und er kehrt in 25 Millisekunden * mit 358 Lesevorgängen zurück, um 72 Stundensummen zurückzugeben:

Geben Sie hier die Bildbeschreibung ein

* Dauer gemessen mit unserem kostenlosen SQL Sentry Plan Explorer , der die Ergebnisse verwirft, sodass die Netzwerkübertragungszeit der Daten, das Rendern usw. nicht enthalten sind. Als zusätzlichen Haftungsausschluss arbeite ich für SQL Sentry.

Es dauert natürlich etwas länger, wenn ich meine Reichweite zu groß mache - ein Monat Daten dauert 258 ms, zwei Monate mehr als 500 ms und so weiter. Parallelität kann eintreten:

Geben Sie hier die Bildbeschreibung ein

Hier fangen Sie an, über andere, bessere Lösungen nachzudenken, um Berichtsanfragen zu beantworten, und es hat nichts damit zu tun, in welcher Zeitzone Ihre Ausgabe angezeigt wird. Ich werde nicht darauf eingehen, ich möchte nur zeigen, dass die Zeitzonenkonvertierung Ihre Berichtsabfragen nicht wirklich viel mehr zum Kotzen bringt, und sie können bereits zum Kotzen werden, wenn Sie große Bereiche erhalten, die nicht von den richtigen unterstützt werden Indizes. Ich werde mich an kleine Datumsbereiche halten, um zu zeigen, dass die Logik korrekt ist, und Sie müssen sich darum kümmern, dass Ihre bereichsbezogenen Berichtsabfragen mit oder ohne Zeitzonenkonvertierungen eine angemessene Leistung erbringen.

Okay, jetzt brauchen wir Tabellen zum Speichern unserer Zeitzonen (mit Offsets in Minuten, da nicht jeder sogar Stunden von UTC entfernt ist) und DST-Änderungsdaten für jedes unterstützte Jahr. Der Einfachheit halber werde ich nur einige Zeitzonen und ein einziges Jahr eingeben, um den obigen Daten zu entsprechen.

CREATE TABLE dbo.TimeZones
(
  TimeZoneID TINYINT    NOT NULL PRIMARY KEY,
  Name       VARCHAR(9) NOT NULL,
  Offset     SMALLINT   NOT NULL, -- minutes
  DSTName    VARCHAR(9) NOT NULL,
  DSTOffset  SMALLINT   NOT NULL  -- minutes
);

Enthält einige Zeitzonen für Abwechslung, einige mit halbstündigen Offsets, andere ohne Sommerzeit. Beachten Sie, dass Australien, in der südlichen Hemisphäre beobachtet DST während unseres Winters, so dass ihre Uhren gehen zurück im April und vorwärts im Oktober. (In der obigen Tabelle werden die Namen umgedreht, aber ich bin mir nicht sicher, wie ich dies für Zeitzonen der südlichen Hemisphäre weniger verwirrend machen soll.)

INSERT dbo.TimeZones VALUES
(1, 'UTC',     0, 'UTC',     0),
(2, 'GMT',     0, 'BST',    60), 
     -- London = UTC in winter, +1 in summer
(3, 'EST',  -300, 'EDT',  -240), 
     -- East coast US (-5 h in winter, -4 in summer)
(4, 'ACDT',  630, 'ACST',  570), 
     -- Adelaide (Australia) +10.5 h Oct - Apr, +9.5 Apr - Oct
(5, 'ACST',  570, 'ACST',  570); 
     -- Darwin (Australia) +9.5 h year round

Nun eine Kalendertabelle, um zu wissen, wann sich TZs ändern. Ich werde nur interessierende Zeilen einfügen (jede Zeitzone oben und nur Sommerzeitänderungen für 2014). Zur Vereinfachung der Berechnungen speichere ich sowohl den Moment in UTC, in dem sich eine Zeitzone ändert, als auch den gleichen Moment in der Ortszeit. Für Zeitzonen, in denen die Sommerzeit nicht eingehalten wird, ist sie das ganze Jahr über Standard, und die Sommerzeit "beginnt" am 1. Januar.

CREATE TABLE dbo.Calendar
(
  TimeZoneID    TINYINT NOT NULL FOREIGN KEY
                REFERENCES dbo.TimeZones(TimeZoneID),
  [Year]        SMALLDATETIME NOT NULL,
  UTCDSTStart   SMALLDATETIME NOT NULL,
  UTCDSTEnd     SMALLDATETIME NOT NULL,
  LocalDSTStart SMALLDATETIME NOT NULL,
  LocalDSTEnd   SMALLDATETIME NOT NULL,
  PRIMARY KEY (TimeZoneID, [Year])
);

Sie können dies definitiv mit Algorithmen füllen (und die kommende Tipp-Serie verwendet einige clevere satzbasierte Techniken, wenn ich es selbst sage), anstatt manuell zu schleifen, was Sie haben. Für diese Antwort habe ich beschlossen, nur ein Jahr für die fünf Zeitzonen manuell auszufüllen, und ich werde mich nicht um ausgefallene Tricks kümmern.

INSERT dbo.Calendar VALUES
(1, '20140101', '20140101 00:00','20150101 00:00','20140101 00:00','20150101 00:00'),
(2, '20140101', '20140330 01:00','20141026 00:00','20140330 02:00','20141026 01:00'),
(3, '20140101', '20140309 07:00','20141102 06:00','20140309 03:00','20141102 01:00'),
(4, '20140101', '20140405 16:30','20141004 16:30','20140406 03:00','20141005 02:00'),
(5, '20140101', '20140101 00:00','20150101 00:00','20140101 00:00','20150101 00:00');

Okay, wir haben also unsere Faktendaten und unsere "Dimension" -Tabellen (ich erschrecke, wenn ich das sage). Was ist also die Logik? Nun, ich gehe davon aus, dass Benutzer ihre Zeitzone auswählen und den Datumsbereich für die Abfrage eingeben müssen. Ich gehe auch davon aus, dass der Datumsbereich volle Tage in ihrer eigenen Zeitzone sein wird. Keine Teiltage, egal Teilstunden. Sie übergeben also ein Startdatum, ein Enddatum und eine TimeZoneID. Von dort aus werden wir eine Skalarfunktion verwenden, um das Start- / Enddatum von dieser Zeitzone in UTC umzuwandeln, wodurch wir die Daten basierend auf dem UTC-Bereich filtern können. Sobald wir dies getan und unsere Aggregationen daran durchgeführt haben, können wir die Konvertierung der gruppierten Zeiten zurück in die Quellzeitzone anwenden, bevor sie dem Benutzer angezeigt werden.

Die skalare UDF:

CREATE FUNCTION dbo.ConvertToUTC
(
  @Source   SMALLDATETIME,
  @SourceTZ TINYINT
)
RETURNS SMALLDATETIME
WITH SCHEMABINDING
AS
BEGIN
  RETURN 
  (
    SELECT DATEADD(MINUTE, -CASE 
        WHEN @Source >= src.LocalDSTStart 
         AND @Source < src.LocalDSTEnd THEN t.DSTOffset 
        WHEN @Source >= DATEADD(HOUR,-1,src.LocalDSTStart) 
         AND @Source < src.LocalDSTStart THEN NULL
        ELSE t.Offset END, @Source)
    FROM dbo.Calendar AS src
    INNER JOIN dbo.TimeZones AS t 
    ON src.TimeZoneID = t.TimeZoneID
    WHERE src.TimeZoneID = @SourceTZ 
      AND t.TimeZoneID = @SourceTZ
      AND DATEADD(MINUTE,t.Offset,@Source) >= src.[Year]
      AND DATEADD(MINUTE,t.Offset,@Source) < DATEADD(YEAR, 1, src.[Year])
  );
END
GO

Und die tabellenwertige Funktion:

CREATE FUNCTION dbo.ConvertFromUTC
(
  @Source   SMALLDATETIME,
  @SourceTZ TINYINT
)
RETURNS TABLE
WITH SCHEMABINDING
AS
 RETURN 
 (
  SELECT 
     [Target] = DATEADD(MINUTE, CASE 
       WHEN @Source >= trg.UTCDSTStart 
        AND @Source < trg.UTCDSTEnd THEN tz.DSTOffset 
       ELSE tz.Offset END, @Source)
  FROM dbo.Calendar AS trg
  INNER JOIN dbo.TimeZones AS tz
  ON trg.TimeZoneID = tz.TimeZoneID
  WHERE trg.TimeZoneID = @SourceTZ 
  AND tz.TimeZoneID = @SourceTZ
  AND @Source >= trg.[Year] 
  AND @Source < DATEADD(YEAR, 1, trg.[Year])
);

Und eine Prozedur, die es verwendet ( bearbeiten : aktualisiert, um die 30-Minuten-Offset-Gruppierung zu handhaben):

CREATE PROCEDURE dbo.ReportOnDateRange
  @Start      SMALLDATETIME, -- whole dates only please! 
  @End        SMALLDATETIME, -- whole dates only please!
  @TimeZoneID TINYINT
AS 
BEGIN
  SET NOCOUNT ON;

  SELECT @Start = dbo.ConvertToUTC(@Start, @TimeZoneID),
         @End   = dbo.ConvertToUTC(@End,   @TimeZoneID);

  ;WITH x(t,c) AS
  (
    SELECT DATEDIFF(MINUTE, @Start, EventTime_UTC)/60, 
      COUNT(*) 
    FROM dbo.Fact 
    WHERE EventTime_UTC >= @Start
      AND EventTime_UTC <  DATEADD(DAY, 1, @End)
    GROUP BY DATEDIFF(MINUTE, @Start, EventTime_UTC)/60
  )
  SELECT 
    UTC = DATEADD(MINUTE, x.t*60, @Start), 
    [Local] = y.[Target], 
    [RowCount] = x.c 
  FROM x OUTER APPLY 
    dbo.ConvertFromUTC(DATEADD(MINUTE, x.t*60, @Start), @TimeZoneID) AS y
  ORDER BY UTC;
END
GO

(Möglicherweise möchten Sie dort einen Kurzschluss oder eine separate gespeicherte Prozedur ausprobieren, falls der Benutzer in UTC Bericht erstatten möchte. Die Übersetzung von und nach UTC ist offensichtlich eine verschwenderische Arbeit.)

Beispielanruf:

EXEC dbo.ReportOnDateRange 
  @Start      = '20140308', 
  @End        = '20140311', 
  @TimeZoneID = 3;

Gibt in 41 ms * zurück und generiert diesen Plan:

Geben Sie hier die Bildbeschreibung ein

* Wieder mit verworfenen Ergebnissen.

Für 2 Monate kehrt es in 507 ms zurück, und der Plan ist bis auf die Anzahl der Zeilen identisch:

Geben Sie hier die Bildbeschreibung ein

Obwohl dies etwas komplexer ist und die Laufzeit ein wenig erhöht, bin ich ziemlich zuversichtlich, dass diese Art von Ansatz viel, viel besser funktionieren wird als der Bridge-Table-Ansatz. Und dies ist ein Beispiel für eine dba.se-Antwort. Ich bin sicher, meine Logik und Effizienz könnten von Leuten verbessert werden, die viel schlauer sind als ich.

Sie können die Daten lesen, um die Randfälle zu sehen, über die ich spreche - keine Ausgabezeile für die Stunde, in der die Uhren vorwärts rollen, zwei Zeilen für die Stunde, in der sie zurückgerollt sind (und diese Stunde zweimal passiert ist). Sie können auch mit schlechten Werten spielen. Wenn Sie beispielsweise 20140309 02:30 Eastern Time vergehen, wird es nicht so gut funktionieren.

Ich habe möglicherweise nicht alle Annahmen richtig darüber, wie Ihre Berichterstattung funktionieren wird, daher müssen Sie möglicherweise einige Anpassungen vornehmen. Aber ich denke, das deckt die Grundlagen ab.

Aaron Bertrand
quelle
0

Können Sie die Transformation in einem gespeicherten Prozess oder einer parametrisierten Ansicht anstelle einer Präsentationsebene durchführen? Eine andere Möglichkeit besteht darin, einen Würfel zu erstellen und die Berechnungen im Würfel zu haben.

Erklärung aus den Kommentaren:

OP stieß bei seinen begrenzten Tests auf Leistungsprobleme, indem er die Berechnungen in der Präsentationsschicht durchführte. Mein Vorschlag ist, das in die Datenbank zu verschieben. In SQL können Sie eine parametrisierte Ansicht mithilfe einer Tabellenwertfunktion erstellen. Basierend auf der Zeitzone, die an diese Funktion übergeben wird, können Daten berechnet und aus der UTC-Tabelle zurückgegeben werden. Hoffe das klärt meine ursprüngliche Antwort.

KNI
quelle
Eine Ansicht mit mehr als 100 zusätzlichen Spalten, in der jede Zeile die Quellzeit in UTC in alle über 100 Zeitzonen übersetzt hat? Ich kann nicht einmal verstehen, wie eine solche Ansicht geschrieben werden würde. Beachten Sie auch, dass SQL Server keine "parametrisierte Ansicht" hat ...
Aaron Bertrand
hmm .. also das ist was du denkst. und das habe ich nicht gemeint.
KNI
1
Also lass mich anders denken. Ich war übrigens nicht die Abwertung, sondern habe nur versucht, Ihre Antwort klarer zu gestalten.
Aaron Bertrand
op stieß bei seinen begrenzten Tests auf Leistungsprobleme, indem er die Berechnungen in der Präsentationsebene durchführte. Mein Vorschlag ist, das in die Datenbank zu verschieben. In SQL können Sie eine parametrisierte Ansicht mithilfe einer Tabellenwertfunktion erstellen. Basierend auf der Zeitzone, die an diese Funktion übergeben wird, können Daten berechnet und aus der utc-Tabelle zurückgegeben werden. Hoffe das klärt meine ursprüngliche Antwort.
KNI
Wie kann dies funktionieren, wenn die Daten aggregiert werden? Wenn eine Zeitzone 30 Minuten versetzt ist, fallen die Daten in eine andere Gruppe. Sie können nicht einfach die Beschriftungen ändern, die in der Präsentationsebene angezeigt werden.
Colin 't Hart