Desain Gudang Data untuk melaporkan terhadap data untuk banyak zona waktu

10

Kami mencoba untuk mengoptimalkan desain data warehouse yang akan mendukung pelaporan terhadap data untuk banyak zona waktu. Misalnya, kami mungkin memiliki laporan untuk aktivitas selama satu bulan (jutaan baris) yang perlu menunjukkan aktivitas yang dikelompokkan berdasarkan jam dalam sehari. Dan tentu saja jam itu harus menjadi jam "lokal" untuk zona waktu tertentu.

Kami memiliki desain yang bekerja dengan baik ketika kami baru saja mendukung UTC dan satu kali waktu lokal. Desain standar dimensi Tanggal dan Waktu untuk UTC dan waktu lokal, id ada di tabel Fakta. Namun, pendekatan itu tampaknya tidak berskala jika kita harus mendukung pelaporan untuk 100+ zona waktu.

Tabel Fakta kami akan menjadi sangat luas. Selain itu, kami harus menyelesaikan masalah sintaksis dalam SQL untuk menentukan tanggal dan id waktu mana yang akan digunakan untuk pengelompokan pada setiap menjalankan laporan. Mungkin pernyataan KASUS yang sangat besar?

Saya telah melihat beberapa saran untuk mendapatkan semua data dengan rentang waktu UTC yang Anda liput, lalu mengembalikannya ke lapisan presentasi untuk dikonversi ke lokal dan agregat di sana, tetapi pengujian terbatas dengan SSRS menunjukkan bahwa akan sangat lambat.

Saya telah berkonsultasi dengan beberapa buku tentang masalah ini, dan mereka semua tampaknya mengatakan hanya memiliki UTC dan mengkonversi pada layar atau memiliki UTC dan satu lokal. Sangat menghargai pemikiran dan saran.

Catatan: Pertanyaan ini mirip dengan: Menangani zona waktu di data mart / gudang , tapi saya tidak bisa mengomentari pertanyaan itu, jadi merasa ini pantas untuk pertanyaannya sendiri.

Pembaruan: Saya memilih jawaban Aaron setelah dia membuat beberapa pembaruan signifikan dan memposting kode contoh dan diagram. Komentar saya sebelumnya tentang jawabannya tidak akan masuk akal lagi karena mereka merujuk pada pengeditan asli jawabannya. Saya akan mencoba untuk kembali dan memperbarui ini lagi jika diperlukan

Peter M
sumber
Dalam konteks jawaban saya (dan pembaruan saya akan posting nanti), seberapa jauh data Anda berjalan? Apakah laporan bulanan menunjukkan 28-31 set potongan 24 jam? Apakah akan selalu menjadi "bulan kalender" atau mungkinkah rentangnya benar? Apa yang harus ditampilkan ketika salah satu tanggal adalah tanggal maju / mundur spring DST untuk zona waktu yang dipilih? Juga, apa sebenarnya input untuk laporan? Apakah Anda mengonversi waktu lokal pengguna ke UTC berdasarkan lokal mereka saat ini secara otomatis, apakah mereka memiliki preferensi, apakah mereka memilih secara manual, atau apakah Anda menyimpulkan dengan cara lain, atau Anda ingin kueri mengetahuinya?
Aaron Bertrand
Untuk menjawab pertanyaan Anda: Data dapat kembali hingga 2 tahun. Kami memiliki beberapa laporan yang menunjukkan hanya satu set potongan 24 jam dan laporan lain yang memiliki potongan 24 jam per hari dalam rentang tanggal laporan. Rentang tanggal benar-benar dapat menjadi apa pun yang diinginkan pengguna. Pengguna memilih tanggal mulai dan berakhir (dan waktu) dan kemudian memilih zona waktu yang mereka inginkan dari dropdown
Peter M
kemungkinan duplikat zona waktu Penanganan di data mart / gudang
Jon of All Trades

Jawaban:

18

Saya telah memecahkan ini dengan memiliki tabel kalender yang sangat sederhana - setiap tahun memiliki satu baris per zona waktu yang didukung , dengan offset standar dan datetime awal / akhir dat DST dan offsetnya (jika zona waktu mendukungnya). Kemudian fungsi inline, terikat skema, bernilai tabel yang mengambil waktu sumber (dalam UTC tentu saja) dan menambah / mengurangi offset.

Ini jelas tidak akan pernah berkinerja sangat baik jika Anda melaporkan sebagian besar data; partisi mungkin terlihat membantu, tetapi Anda masih memiliki kasus di mana beberapa jam terakhir dalam satu tahun atau beberapa jam pertama di tahun berikutnya sebenarnya milik tahun yang berbeda ketika dikonversi ke zona waktu tertentu - sehingga Anda tidak akan pernah mendapatkan partisi yang benar isolasi, kecuali ketika rentang pelaporan Anda tidak termasuk 31 Desember atau 1 Januari.

Ada beberapa kasus tepi aneh yang perlu Anda pertimbangkan:

  • 2014-11-02 05:30 UTC dan 2014-11-02 06:30 UTC keduanya dikonversi menjadi 01:30 pagi di zona waktu Timur, misalnya (satu untuk pertama kali 01:30 ditabrak secara lokal, lalu satu untuk kedua kalinya ketika jam diputar dari 2:00 pagi sampai 1:00 pagi, dan setengah jam berlalu). Jadi, Anda perlu memutuskan bagaimana menangani jam pelaporan - menurut UTC, Anda harus melihat dua kali lipat lalu lintas atau volume apa pun yang Anda ukur setelah dua jam dipetakan menjadi satu jam di zona waktu yang mengamati DST. Ini juga dapat memainkan permainan yang menyenangkan dengan mengurutkan acara, karena sesuatu yang secara logis harus terjadi setelah sesuatu yang lain bisa munculterjadi sebelum itu setelah waktunya disesuaikan dengan satu jam, bukan dua. Contoh ekstrem adalah tampilan halaman yang terjadi pada 05:59 UTC, lalu klik yang terjadi pada 06:00 UTC. Dalam waktu UTC ini terjadi satu menit terpisah, tetapi ketika dikonversi ke waktu Timur, tampilan terjadi pada 1:59, dan klik terjadi satu jam sebelumnya.

  • 2014-03-09 02:30 tidak pernah terjadi di AS. Ini karena pada jam 2:00 pagi kami menggulung jam ke depan hingga jam 3 pagi. Sangat mungkin Anda ingin meningkatkan kesalahan jika pengguna memasukkan waktu seperti itu dan meminta Anda untuk mengubahnya menjadi UTC, atau merancang formulir Anda sehingga pengguna tidak dapat memilih waktu seperti itu.

Bahkan dengan mempertimbangkan kasus tepi tersebut, saya masih berpikir Anda memiliki pendekatan yang tepat: menyimpan data dalam UTC. Jauh lebih mudah untuk memetakan data ke zona waktu lain dari UTC daripada dari beberapa zona waktu ke beberapa zona waktu lain, terutama ketika zona waktu yang berbeda memulai / mengakhiri DST pada tanggal yang berbeda, dan bahkan zona waktu yang sama dapat beralih menggunakan aturan yang berbeda di tahun yang berbeda ( misalnya AS mengubah aturan 6 tahun yang lalu atau lebih).

Anda akan ingin menggunakan tabel kalender untuk semua ini, bukan CASE ekspresi raksasa (bukan pernyataan ). Saya baru saja menulis seri tiga bagian untuk MSSQLTips.com tentang ini; Saya pikir bagian ke-3 akan menjadi yang paling berguna untuk Anda:

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/


Contoh nyata yang nyata, sementara itu

Katakanlah Anda memiliki tabel fakta yang sangat sederhana. Satu-satunya fakta yang saya pedulikan dalam hal ini adalah waktu acara, tetapi saya akan menambahkan GUID yang tidak berarti hanya untuk membuat tabel cukup luas untuk diperhatikan. Sekali lagi, untuk menjadi eksplisit, tabel fakta menyimpan acara dalam waktu UTC dan waktu UTC saja. Saya bahkan sudah suffix kolom dengan _UTCsehingga tidak ada kebingungan.

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

Sekarang, mari kita muat tabel fakta kami dengan 10.000.000 baris - mewakili setiap 3 detik (1.200 baris per jam) dari 2013-12-30 di tengah malam UTC hingga sekitar pukul 5 pagi UTC pada 2014-12-12. Ini memastikan bahwa data mengangkang batas tahun, serta DST maju dan mundur untuk beberapa zona waktu. Ini terlihat sangat menakutkan, tetapi membutuhkan waktu ~ 9 detik pada sistem saya. Tabel seharusnya sekitar 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;

Dan hanya untuk menunjukkan seperti apa bentuk kueri pencarian terhadap tabel baris 10 MM ini, jika saya menjalankan kueri ini:

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

Saya mendapatkan paket ini, dan kembali dalam 25 milidetik *, menghasilkan 358 kali dibaca, untuk mengembalikan total 72 jam:

masukkan deskripsi gambar di sini

* Durasi yang diukur oleh SQL Sentry Plan Explorer gratis kami , yang membuang hasil, jadi ini tidak termasuk waktu transfer jaringan dari data, rendering, dll. Sebagai penafian tambahan, saya bekerja untuk SQL Sentry.

Dibutuhkan sedikit lebih lama, tentu saja, jika saya membuat jangkauan saya terlalu besar - sebulan data membutuhkan 258 ms, dua bulan membutuhkan lebih dari 500 ms, dan seterusnya. Paralelisme dapat mendorong:

masukkan deskripsi gambar di sini

Di sinilah Anda mulai memikirkan solusi lain yang lebih baik untuk memenuhi permintaan pelaporan, dan itu tidak ada hubungannya dengan zona waktu apa yang akan ditampilkan oleh output Anda. Saya tidak akan membahasnya, saya hanya ingin menunjukkan bahwa konversi zona waktu tidak benar-benar akan membuat kueri pelaporan Anda menyedot lebih banyak, dan mereka mungkin sudah menyedot jika Anda mendapatkan rentang besar yang tidak didukung oleh yang tepat indeks. Saya akan berpegang pada rentang tanggal kecil untuk menunjukkan bahwa logika itu benar, dan membiarkan Anda khawatir tentang memastikan kueri pelaporan berbasis rentang Anda berkinerja memadai, dengan atau tanpa konversi zona waktu.

Oke, sekarang kita perlu tabel untuk menyimpan zona waktu kita (dengan offset, dalam hitungan menit, karena tidak semua orang bahkan berjam-jam di UTC) dan DST mengubah tanggal untuk setiap tahun yang didukung. Untuk kesederhanaan, saya hanya akan memasukkan beberapa zona waktu dan satu tahun untuk mencocokkan data di atas.

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

Termasuk beberapa zona waktu untuk variasi, beberapa dengan offset setengah jam, beberapa yang tidak mematuhi DST. Perhatikan bahwa Australia, di belahan bumi selatan mengamati DST selama musim dingin kami, jadi jam mereka kembali pada bulan April dan maju pada bulan Oktober. (Tabel di atas membalik nama-nama, tapi saya tidak yakin bagaimana membuat ini kurang membingungkan untuk zona waktu belahan bumi selatan.)

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

Sekarang, tabel kalender untuk mengetahui kapan TZ berubah. Saya hanya akan memasukkan baris yang menarik (setiap zona waktu di atas, dan hanya perubahan DST untuk 2014). Untuk kemudahan perhitungan bolak-balik, saya menyimpan momen di UTC di mana zona waktu berubah, dan momen yang sama di waktu setempat. Untuk zona waktu yang tidak mematuhi DST, standar sepanjang tahun, dan DST "dimulai" pada 1 Januari.

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

Anda pasti dapat mengisi ini dengan algoritma (dan seri tip yang akan datang menggunakan beberapa teknik berbasis set pintar, jika saya mengatakannya sendiri), daripada loop, mengisi secara manual, apa pun yang Anda miliki. Untuk jawaban ini saya memutuskan untuk secara manual mengisi satu tahun untuk lima zona waktu, dan saya tidak akan mengganggu trik mewah.

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

Oke, jadi kita punya data fakta kita, dan tabel "dimensi" kita (saya merasa ngeri ketika mengatakan itu), jadi apa logikanya? Yah, saya kira Anda akan meminta pengguna memilih zona waktu mereka dan memasukkan rentang tanggal untuk kueri. Saya juga akan berasumsi bahwa rentang tanggal akan menjadi hari penuh di zona waktu mereka sendiri; tidak ada hari parsial, apalagi parsial jam. Jadi mereka akan melewati tanggal mulai, tanggal akhir, dan TimeZoneID. Dari sana kami akan menggunakan fungsi skalar untuk mengonversi tanggal mulai / berakhir dari zona waktu tersebut ke UTC, yang akan memungkinkan kami untuk memfilter data berdasarkan rentang UTC. Setelah kami selesai melakukannya, dan melakukan agregasi kami di atasnya, kami kemudian dapat menerapkan konversi waktu yang dikelompokkan kembali ke zona waktu sumber, sebelum ditampilkan kepada pengguna.

Skalar 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

Dan fungsi bernilai tabel:

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

Dan prosedur yang menggunakannya ( edit : diperbarui untuk menangani pengelompokan offset 30 menit):

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

(Anda mungkin ingin melakukan hubungan arus pendek di sana, atau prosedur tersimpan terpisah, dalam hal pengguna ingin melaporkan dalam UTC - jelas menerjemahkan ke dan dari UTC akan menjadi pekerjaan yang sibuk dan boros.)

Contoh panggilan:

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

Kembali dalam 41ms *, dan menghasilkan rencana ini:

masukkan deskripsi gambar di sini

* Sekali lagi, dengan hasil yang dibuang.

Selama 2 bulan, ia kembali dalam 507 ms, dan rencananya identik selain jumlah baris:

masukkan deskripsi gambar di sini

Meskipun sedikit lebih kompleks dan sedikit meningkatkan waktu berjalan, saya cukup yakin bahwa jenis pendekatan ini akan berhasil, jauh lebih baik daripada pendekatan tabel jembatan. Dan ini adalah contoh spontan untuk jawaban dba.se; Saya yakin logika dan efisiensi saya dapat ditingkatkan oleh orang-orang yang jauh lebih pintar daripada saya.

Anda dapat membaca dengan teliti data untuk melihat kasus tepi yang saya bicarakan - tidak ada deretan output untuk jam di mana jam bergulir ke depan, dua baris untuk jam di mana mereka memutar kembali (dan jam itu terjadi dua kali). Anda juga dapat bermain dengan nilai-nilai buruk; jika Anda lulus pada 20140309 02:30 waktu Timur, misalnya, itu tidak akan berfungsi dengan baik.

Saya mungkin tidak memiliki semua asumsi yang benar tentang cara kerja pelaporan Anda, jadi Anda mungkin harus melakukan beberapa penyesuaian. Tapi saya pikir ini mencakup dasar-dasarnya.

Aaron Bertrand
sumber
0

Bisakah Anda melakukan transformasi dalam proc yang disimpan atau tampilan parameter bukan lapisan presentasi? Pilihan lain adalah membuat kubus dan memiliki perhitungan di kubus.

Penjelasan dari komentar:

OP mengalami masalah kinerja dengan pengujian terbatasnya dengan melakukan perhitungan di lapisan presentasi. Saran saya adalah memindahkannya ke database. Dalam sql, Anda bisa melakukan tampilan parameter menggunakan fungsi nilai tabel. Berdasarkan zona waktu yang dilewatkan ke fungsi ini, data dapat dihitung dan dikembalikan dari tabel UTC. Semoga ini menjelaskan jawaban asli saya.

KNI
sumber
Jadi tampilan yang memiliki 100+ kolom tambahan di mana setiap baris memiliki waktu sumber dalam UTC diterjemahkan ke semua 100 zona waktu? Saya bahkan tidak dapat mulai memahami bagaimana pandangan seperti itu akan ditulis. Perhatikan juga SQL Server tidak memiliki "tampilan berparameter" ...
Aaron Bertrand
hmm .. jadi itulah yang Anda pikirkan. dan bukan itu yang saya maksud.
KNI
1
Jadi buat aku berpikir sebaliknya. Ngomong-ngomong, saya bukan yang memilih, hanya mencoba mendorong kejelasan yang lebih baik dalam jawaban Anda.
Aaron Bertrand
op mengalami masalah kinerja dengan pengujian terbatasnya dengan melakukan perhitungan di lapisan presentasi. Saran saya adalah memindahkannya ke basis data. Dalam sql, Anda bisa melakukan tampilan parameter menggunakan fungsi nilai tabel. Berdasarkan zona waktu yang dilewatkan ke fungsi ini, data dapat dihitung dan dikembalikan dari tabel utc. Semoga ini menjelaskan jawaban asli saya.
KNI
Bagaimana ini bisa berfungsi jika data diagregasi? Jika zona waktu 30 menit diimbangi maka data akan jatuh ke grup yang berbeda. Anda tidak bisa hanya mengubah label yang dipajang di lapisan presentasi.
Colin 't Hart