多くのタイムゾーンのデータに対してレポートするためのデータウェアハウスの設計


10

多くのタイムゾーンのデータに対するレポートをサポートするデータウェアハウスの設計を最適化しようとしています。たとえば、アクティビティを1日の時間でグループ化して表示する必要がある、1か月分のアクティビティ(数百万行)のレポートがあるとします。そしてもちろんその日の時間は与えられたタイムゾーンの「ローカル」時間でなければなりません。

UTCと1つの現地時間をサポートしたときにうまく機能するデザインがありました。UTCおよび現地時間の日付と時刻のディメンションの標準設計、ファクトテーブルのID。ただし、100以上のタイムゾーンのレポートをサポートする必要がある場合、そのアプローチは拡張されないようです。

ファクトテーブルは非常に広くなります。また、レポートの特定の実行でグループ化に使用する日付と時刻のIDを指定するSQLの構文の問題を解決する必要があります。おそらく非常に大きなCASEステートメントでしょうか?

カバーしているUTC時間範囲ごとにすべてのデータを取得し、それをプレゼンテーションレイヤーに戻してローカルに変換してそこで集計するといういくつかの提案を見てきましたが、SSRSを使用した限られたテストでは、非常に遅くなることが示唆されています。

私はこの主題についてもいくつかの本を調べましたが、それらはすべて、UTCがあり、ディスプレイで変換するか、UTCと1つのローカルがあると言っているようです。任意の考えや提案をいただければ幸いです。

注:この質問は「データマート/倉庫でのタイムゾーンの処理」に似ていますが、その質問についてはコメントできません。

更新: Aaronが重要な更新を行い、サンプルコードと図を投稿した後、私はAaronの回答を選択しました。彼の回答に対する私の以前のコメントは、回答の元の編集を参照しているため、あまり意味がありません。必要に応じて戻ってきてこれをもう一度更新しようとします


私の回答(および後で投稿する更新)に関連して、データはどのくらい前に戻りますか?月次レポートには、28〜31セットの24時間チャンクが表示されますか?常に「暦月」になるのでしょうか、それとも実際にはどのような範囲になるのでしょうか。日付の1つが、選択したタイムゾーンのDSTスプリングフォワード/ロールバック日付である場合、何を表示する必要がありますか?また、レポートの入力は正確には何ですか?ユーザーのローカル時間を現在のロケールに基づいて自動的にUTCに変換しますか、ユーザー設定がありますか、手動で選択しますか、それとも他の方法で推測しますか、またはクエリでそれを計算しますか?
アーロンバートランド

質問に答えるには:データは最大2年前に戻る可能性があります。24時間チャンクのセットを1つだけ表示するレポートと、レポートの日付範囲で毎日24時間チャンクを含むレポートがあります。日付範囲は、実際にはユーザーが望むものにすることができます。ユーザーは開始日と終了日(および時間)を選択し、ドロップダウンから希望のタイムゾーンを選択します
Peter M

回答:


18

私は非常にシンプルなカレンダーテーブルを使用してこれを解決しました-毎年、サポートされているタイムゾーンごとに1つの行があり、標準オフセット、DSTの開始日時/終了日時、およびそのオフセット(そのタイムゾーンでサポートされている場合)が含まれています。次に、インラインのスキーマバインドのテーブル値関数で、ソース時間(もちろんUTC)を取得し、オフセットを加算または減算します。

データの大部分に対してレポートを作成している場合、これは明らかに非常にうまく機能しません。パーティショニングは役立つように見えるかもしれませんが、特定のタイムゾーンに変換すると、ある年の最後の数時間または翌年の最初の数時間が実際には別の年に属している場合があるため、実際のパーティションを取得することはできません分離。ただし、レポート範囲に12月31日または1月1日が含まれない場合は除きます。

考慮する必要がある奇妙なエッジケースがいくつかあります。

  • たとえば、2014年11月2日05:30 UTCおよび2014年11月2日06:30 UTCはどちらも東部時間帯の午前1時30分に変換されます(最初にローカルで01:30がヒットし、次に1つクロックが午前2時から午前1時にロールバックされ、さ​​らに30分経過した2回目)。したがって、その1時間のレポートをどのように処理するかを決定する必要があります。UTCによると、DSTを監視するタイムゾーンで1時間に2時間がマッピングされると、測定しているトラフィックまたはボリュームが2倍になるはずです。これは、イベントのシーケンスで楽しいゲームをプレイすることもできます。なぜなら、何か他のものが現れた後に論理的に起こらなければならないことがあったからです。タイミングが2時間ではなく1時間に調整されると、その前に発生します。極端な例としては、UTC 05:59に発生したページビューと、UTC 06:00に発生したクリックがあります。UTC時間ではこれらは1分間隔で発生しましたが、東部時間に変換すると、ビューは午前1時59分に発生し、クリックは1時間早く発生しました。

  • 2014-03-09 02:30アメリカでは決して起こりません。これは、午前2時に時計を午前3時に進めるためです。そのため、ユーザーがそのような時間を入力し、それをUTCに変換するように求められた場合、またはユーザーがそのような時間を選択できないようにフォームをデザインする場合は、エラーを発生させることになるでしょう。

これらのエッジケースを念頭に置いても、私はあなたが正しいアプローチを持っていると思います:UTCでデータを保存します。特に、異なるタイムゾーンが異なる日付にDSTを開始/終了する場合、および同じタイムゾーンでさえ、異なる年に異なるルールを使用して切り替えることができる場合、特定のタイムゾーンから他のタイムゾーンに移動するよりも、UTCから他のタイムゾーンにデータをマップする方がはるかに簡単です(たとえば、米国は6年ほど前にルールを変更しました)。

あなたはなく、いくつかの巨大な、このすべてのカレンダーテーブルを使用したいと思うでしょうCASE 表現(ない声明)。これについて、MSSQLTips.comの 3部構成のシリーズを書きました。3番目の部分が最も役立つと思います。

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/


その間、実際のライブの例

非常に単純なファクトテーブルがあるとします。この場合に私が気にする唯一の事実はイベント時間ですが、気になるほどテーブルを広くするために、意味のないGUIDを追加します。繰り返しになりますが、ファクトテーブルはイベントをUTC時間とUTC時間のみで保存します。列に接尾辞を付けた_UTCので、混乱はありません。

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

次に、ファクトテーブルに10,000,000行をロードします。これは、2013-12-30のUTC午前0時から2014-12-12の午前5時以降までの3秒ごと(1時間あたり1,200行)を表します。これにより、データが年の境界をまたぐようになり、DSTが複数のタイムゾーンで前後に進みます。これは恐ろしいように見えますが、私のシステムでは約9秒かかりました。テーブルは約325 MBになるはずです。

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

そして、このクエリを実行した場合に、この10 MM行テーブルに対して典型的なシーククエリがどのように見えるかを示すためだけに:

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

このプランを取得すると、25ミリ秒*で戻り、358回の読み取りを実行して、72時間ごとの合計を返します。

ここに画像の説明を入力してください

* 結果を破棄する無料のSQL Sentry Plan Explorerで測定された期間。これには、データのネットワーク転送時間、レンダリングなどは含まれません。追加の免責事項として、私はSQL Sentryで働いています。

明らかに、範囲を大きくしすぎると少し時間がかかります。1か月のデータには258ミリ秒、2か月には500ミリ秒以上かかります。並列処理が有効になる場合があります。

ここに画像の説明を入力してください

ここから、レポートクエリを満たすためのより優れた他のソリューションについて考え始めます。出力が表示するタイムゾーンとは関係ありません。それについては触れません。タイムゾーン変換によってレポートクエリが実際にそれほど多くのことを実行するわけではなく、適切にサポートされていない大きな範囲を取得している場合は、それらがすでに機能しない可能性があることを示したいだけです。インデックス。日付範囲を短くして、ロジックが正しいことを示し、タイムゾーン変換の有無にかかわらず、範囲ベースのレポートクエリが適切に実行されることを確認できるようにします。

さて、ここで、サポートされている各年のタイムゾーン(すべてのユーザーがUTCから数時間も離れているわけではないので、分単位のオフセット)とDST変更日付を格納するテーブルが必要です。簡単にするために、上記のデータと一致させるために、いくつかのタイムゾーンと1年のみを入力します。

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

さまざまなタイムゾーンがいくつか含まれています。オフセットが30分あるものや、DSTを監視しないものもあります。オーストラリアは、南半球で私たちの冬の間DSTを観察するので、そのクロックが行くことに注意してくださいバック 4月にして前方に 10月に。(上記の表では名前が逆になっていますが、南半球のタイムゾーンでこれをわかりやすくする方法がわかりません。)

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

TZがいつ変更されるかを知るためのカレンダーテーブル。関心のある行のみを挿入します(上の各タイムゾーン、および2014年の夏時間の変更のみ)。計算を簡単にするために、タイムゾーンが変化するUTCの瞬間と現地時間の同じ瞬間の両方を保存します。DSTを順守しないタイムゾーンの場合、それは1年中標準であり、DSTは1月1日に「開始」します。

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

ループではなく、アルゴリズムを確実に入力することができます(そして、今後のヒントシリーズでは、自分で言うとしたら、巧妙なセットベースの手法をいくつか使用します)。この答えのために、私は5つのタイムゾーンに1年を手動で入力することにしました。空想的なトリックに煩わされることはありません。

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

さて、ファクトデータと "ディメンション"テーブル(私が言うとうんざりします)があるので、ロジックは何ですか。ええと、ユーザーにタイムゾーンを選択してクエリの日付範囲を入力してもらうことになると思います。また、日付範囲はそれぞれのタイムゾーンで丸1日と想定します。部分的な日はありません、部分的な時間を気にしないでください。したがって、開始日、終了日、およびTimeZoneIDを渡します。そこから、スカラー関数を使用して、開始日/終了日をそのタイムゾーンからUTCに変換します。これにより、UTC範囲に基づいてデータをフィルター処理できます。これを実行して集計を実行したら、ユーザーに表示する前に、グループ化された時間の変換をソースタイムゾーンに適用し直すことができます。

スカラー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

そして、テーブル値関数:

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

そして、それを使用する手順(編集:30分のオフセットのグループ化を処理するように更新):

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

(ユーザーがUTCでのレポートを希望する場合は、そこで短絡するか、別のストアドプロシージャを使用することをお勧めします。UTCとの間の変換は明らかに無駄な多忙な作業になります。)

呼び出しの例:

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

41ms *で戻り、このプランを生成します。

ここに画像の説明を入力してください

* 繰り返しますが、結果は破棄されます。

2か月間は507ミリ秒で戻り、計画は行数以外は同じです。

ここに画像の説明を入力してください

少し複雑で、実行時間は少し長くなりますが、このタイプのアプローチは、ブリッジテーブルのアプローチよりもはるかにうまく機能すると確信しています。そして、これはdba.seの回答のオフカフ例です。私よりもはるかに賢い人が私の論理と効率を改善できると確信しています。

データを熟読して、私が話しているエッジケースを確認できます-クロックがロールフォワードする1時間の出力の行がない、それらがロールバックする1時間の2つの行(およびその時間が2回発生した)。悪い値で遊ぶこともできます。たとえば、20140309 02:30東部標準時を過ぎると、うまく機能しません。

レポートがどのように機能するかについて、すべての仮定が正しいわけではない可能性があるため、調整が必要になる場合があります。しかし、これは基本をカバーしていると思います。


0

プレゼンテーションレイヤーの代わりにストアドプロシージャまたはパラメーター化されたビューで変換を実行できますか?別のオプションは、キューブを作成し、計算をキューブに入れることです。

コメントからの説明:

OPは、プレゼンテーションレイヤーで計算を行うことにより、限られたテストでパフォーマンスの問題に遭遇しました。私の提案は、それをデータベースに移動することです。SQLでは、テーブル値関数を使用してパラメーター化されたビューを実行できます。この関数に渡されるタイムゾーンに基づいて、データを計算し、UTCテーブルから返すことができます。これで私の元の答えが明確になることを願っています。


では、100以上の追加の列があり、各行にUTCのソース時刻が100以上のすべてのタイムゾーンに変換されているビューですか?私はそのような見方がどのように書かれるかを理解することすらできません。SQL Serverには「パラメータ化されたビュー」がないことに注意してください...
アーロンバートランド

うーん..それがあなたが考えていることです。そしてそれは私が意味したものではありません。
KNI 2014

1
だから私に別のことを考えさせます。ちなみに、私はあなたの回答をより明確にするために反対票を投じただけではありませんでした。
アーロンバートランド

opは、プレゼンテーションレイヤーで計算を行うことにより、限られたテストでパフォーマンスの問題に遭遇しました。私の提案は、それをデータベースに移動することです。SQLでは、テーブル値関数を使用してパラメーター化されたビューを実行できます。この関数に渡されるタイムゾーンに基づいて、データを計算し、utcテーブルから返すことができます。これで私の元の答えが明確になることを願っています。
KNI 2014

データが集約されている場合、これはどのように機能しますか?タイムゾーンが30分のオフセットである場合、データは別のグループに分類されます。プレゼンテーションレイヤーに表示されているラベルを変更することはできません。
Colin 't Hart
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.