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
Jawaban:
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
_UTC
sehingga tidak ada kebingungan.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.
Dan hanya untuk menunjukkan seperti apa bentuk kueri pencarian terhadap tabel baris 10 MM ini, jika saya menjalankan kueri ini:
Saya mendapatkan paket ini, dan kembali dalam 25 milidetik *, menghasilkan 358 kali dibaca, untuk mengembalikan total 72 jam:
* 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:
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.
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.)
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.
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.
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:
Dan fungsi bernilai tabel:
Dan prosedur yang menggunakannya ( edit : diperbarui untuk menangani pengelompokan offset 30 menit):
(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:
Kembali dalam 41ms *, dan menghasilkan rencana ini:
* Sekali lagi, dengan hasil yang dibuang.
Selama 2 bulan, ia kembali dalam 507 ms, dan rencananya identik selain jumlah baris:
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.
sumber
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:
sumber