Birçok zaman dilimindeki verilere karşı raporlama için Veri Ambarı tasarımı


10

Birçok zaman dilimi için verilere karşı raporlamayı destekleyecek bir veri ambarı tasarımını optimize etmeye çalışıyoruz. Örneğin, bir saatlik etkinlik (milyonlarca satır) için, günün saatine göre gruplandırılmış etkinliği göstermesi gereken bir raporumuz olabilir. Ve elbette günün o saati, verilen zaman dilimi için "yerel" saat olmalıdır.

UTC'yi ve bir yerel saati desteklediğimizde iyi çalışan bir tasarımımız vardı. UTC ve yerel saat için Tarih ve Saat boyutlarının standart tasarımı, Gerçekler tablosundaki id'ler. Ancak, 100'den fazla saat dilimi için raporlamayı desteklememiz gerekiyorsa bu yaklaşım ölçeklendirilmiş görünmemektedir.

Gerçek tablolarımız çok geniş olacaktı. Ayrıca, SQL'deki sözdizimi sorununu, raporun belirli bir çalıştırmasında gruplama için hangi tarih ve saat kimliklerinin kullanılacağını belirleyerek çözmemiz gerekir. Belki de çok büyük bir CASE ifadesi?

Tüm verileri kapsadığınız UTC zaman aralığına göre almak için bazı öneriler gördüm, ardından yerel ve toplu olarak dönüştürmek için sunum katmanına geri döndüm, ancak SSRS ile sınırlı test yapılması son derece yavaş olacağını gösteriyor.

Konuyla ilgili bazı kitaplara da danıştım ve hepsi sadece UTC'ye sahip olduğunu ve ekranda dönüştüğünü veya UTC ve bir yerel olduğunu söylüyor gibi görünüyor. Herhangi bir düşünce ve önerilerinizi takdir ediyorum.

Not: Bu soru şuna benzer: Veri mart / depodaki saat dilimlerini işleme , ancak bu soru hakkında yorum yapamam, bu yüzden kendi sorusunu hak ettiğini hissettim.

Güncelleme: Bazı önemli güncellemeler yaptıktan ve örnek kod ve diyagramlar yayınladıktan sonra Aaron'un cevabını seçtim. Cevabına ilişkin daha önceki yorumlarım, cevabın orijinal düzenlemesine atıfta bulundukları için artık bir şey ifade etmiyor. Gerekirse geri dönüp tekrar güncellemeye çalışacağım


Cevabım (ve daha sonra cevaplayacağım güncellemeler) bağlamında, verileriniz ne kadar geriye gidiyor? Aylık bir raporda 28-31 set 24 saatlik yığın gösterilecek mi? Her zaman "takvim ayı" olacak mı yoksa gerçekten herhangi bir aralık olabilir mi? Tarihlerden biri, seçilen saat dilimi için bir DST yay ileri / geri alma tarihi olduğunda ne göstermelidir? Ayrıca, raporun girdisi tam olarak nedir? Kullanıcının yerel saatini geçerli yerel ayarlarına göre otomatik olarak UTC'ye dönüştürüyor musunuz, tercihleri ​​var mı, manuel olarak mı seçiyorlar, yoksa başka bir şekilde mi çıkıyorsunuz yoksa sorguyu çözmesini mi istiyorsunuz?
Aaron Bertrand

Sorularınızı cevaplamak için: Veriler 2 yıla kadar geriye gidebilir. Rapor tarih aralığındaki her gün yalnızca bir saatlik 24 saatlik yığınlar ve 24 saatlik yığın içeren diğer raporlarımız var. Tarih aralığı gerçekten kullanıcının istediği herhangi bir şey olabilir. Kullanıcı başlangıç ​​ve bitiş tarihini (ve zamanlarını) seçer ve ardından bir açılır menüden istediği zaman dilimini seçer
Peter M

Yanıtlar:


18

Bunu çok basit bir takvim tablosuna sahip olarak çözdüm - her yıl desteklenen zaman dilimi başına bir satır vardır, standart ofset ve DST ve başlangıç ​​tarih / bitiş tarih saatiyle (bu saat dilimi destekliyorsa). Ardından kaynak zamanını alan (elbette UTC'de) ve ofseti toplayan / çıkaran satır içi, şemaya bağlı, tablo değerli bir işlev.

Verilerin büyük bir kısmına karşı bildirimde bulunuyorsanız, bu kesinlikle hiçbir zaman iyi bir performans göstermeyecektir; bölümleme yardımcı gibi görünebilir, ancak yine de bir yılda son birkaç saatin veya gelecek yılın ilk birkaç saatinin belirli bir saat dilimine dönüştürüldüğünde aslında farklı bir yıla ait olduğu durumlara sahip olacaksınız - böylece asla gerçek bölüm elde edemezsiniz raporlama aralığınızın 31 Aralık veya 1 Ocak içermediği durumlar dışında, yalıtım.

Dikkate almanız gereken birkaç garip kenar durumu var:

  • 2014-11-02 05:30 UTC ve 2014-11-02 06:30 UTC her ikisi de Doğu saat diliminde 01:30 AM, örneğin (ilk kez 01:30 yerel olarak vuruldu ve sonra bir saatlerin 2:00 AM - 1:00 AM arasında geri döndüğü ikinci kez ve yarım saat daha geçtiği). Bu nedenle, bu saat raporunun nasıl ele alınacağına karar vermelisiniz - UTC'ye göre, bu iki saat DST'yi gözlemleyen bir saat diliminde tek bir saate eşlendikten sonra ölçtüğünüz şeyin trafiğini veya hacmini iki katına çıkarmalısınız. Bu aynı zamanda mantıksal başka bir şey olabilir sonra ne zorunda şey beri, olayların dizileme ile eğlenceli oyunlar oynayabilirsiniz görünürzamanlama iki yerine tek bir saate ayarlandıktan sonra gerçekleşir. Aşırı bir örnek, 05:59 UTC'de gerçekleşen bir sayfa görünümüdür, ardından 06:00 UTC'de gerçekleşen bir tıklamadır. UTC zamanında bunlar bir dakika arayla gerçekleşti, ancak Doğu zamanına dönüştürüldüğünde, görünüm 1:59 AM'de gerçekleşti ve tıklama bir saat önce gerçekleşti.

  • 2014-03-09 02:30 ABD'de asla olmaz. Bunun nedeni, 2: 00'de saatleri 3: 00'a doğru yuvarlamamızdır. Bu nedenle, kullanıcı böyle bir zaman girerse ve bunu UTC'ye dönüştürmenizi isterse veya formunuzu, kullanıcıların böyle bir zaman seçememesi için tasarlamanız istenirse, bir hata oluşturmak isteyeceksiniz.

Bu uç durumlar göz önünde bulundurulduğunda bile, hala doğru yaklaşıma sahip olduğunuzu düşünüyorum: verileri UTC'de saklayın. Verileri UTC'den diğer saat dilimlerine eşleştirmek, özellikle farklı saat dilimleri farklı tarihlerde DST'yi başlattığında / sonlandırdığında ve hatta aynı saat dilimi farklı yıllarda farklı kurallar kullanarak geçiş yapabiliyorsa, bir saat diliminden diğer bir saat dilimine eşlemek çok daha kolaydır. örneğin ABD kuralları 6 yıl önce değiştirdi).

Buna, bazı devasa tümü için takvim tablo kullanmak isteyeceksiniz CASE ifadesi (değil bildirimi ). Bu konuda MSSQLTips.com için üç bölümlük bir seri yazdım ; Bence 3. bölüm sizin için en faydalı olacak:

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/


Bu arada gerçek bir canlı örnek

Diyelim ki çok basit bir olgu tablonuz var. Bu durumda umursadığım tek gerçek olay zamanıdır, ancak tabloyu önemsemek için yeterince geniş hale getirmek için anlamsız bir GUID ekleyeceğim. Yine açık olmak gerekirse, olgu tablosu olayları yalnızca UTC ve UTC saatlerinde saklar. Sütunu bile ekledim, _UTCbu yüzden karışıklık yok.

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

Şimdi, gerçek tablonuzu 10.000.000 satırla yükleyelim - 2013-12-30 arası gece yarısı UTC'de gece saatlerinde 2014-12-12 arası saat 5'e kadar her 3 saniyede bir (saatte 1.200 satır). Bu, verilerin bir yıl sınırı ile DST'nin birden fazla zaman dilimi için ileri ve geri olmasını sağlar. Bu gerçekten korkutucu görünüyor, ancak sistemimde ~ 9 saniye sürdü. Tablo 325 MB civarında olmalıdır.

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

Ve bu sorguyu çalıştırırsam, tipik bir arama sorgusunun bu 10MM satır tablosuna nasıl görüneceğini göstermek için:

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

Bu planı alıyorum ve 72 saatlik toplamları döndürmek için 358 okuma yaparak 25 milisaniye * içinde dönüyor:

resim açıklamasını buraya girin

* Sonuçları atar ücretsiz SQL Sentry Plan Explorer tarafından ölçülen süre , bu nedenle veri, render, vb ağ aktarım süresini içermez. Ek bir feragatname olarak, SQL Sentry için çalışıyorum.

Açıkçası, aralığımı çok fazla büyütürsem biraz daha uzun sürer - bir aylık veri 258 ms sürer, iki ay 500 ms'yi alır, vb. Paralellik başlayabilir:

resim açıklamasını buraya girin

Bu, raporlama sorgularını karşılamak için diğer daha iyi çözümleri düşünmeye başladığınız yerdir ve çıktınızın hangi saat diliminde görüntüleneceği ile ilgisi yoktur. Buna girmeyeceğim, sadece zaman dilimi dönüşümünün raporlama sorgularınızı çok daha fazla emmesini sağlamayacağını göstermek istiyorum ve uygun şekilde desteklenmeyen büyük aralıklar alıyorsanız zaten emebilirler. indeksleri. Mantığın doğru olduğunu göstermek için küçük tarih aralıklarına bağlı kalacağım ve aralık tabanlı raporlama sorgularınızın saat dilimi dönüşümleriyle veya saat dilimi dönüşümleri olmadan yeterli bir şekilde çalıştığından emin olmanızı sağlayacağım.

Tamam, şimdi saat dilimlerimizi (ofsetlerle, dakikalar içinde, herkes UTC saati dışında bile olmadığından) ve desteklenen her yıl için DST değişiklik tarihlerini saklamak için tablolara ihtiyacımız var. Basit olması için, yukarıdaki verilerle eşleşmesi için yalnızca birkaç saat dilimi ve bir yıl gireceğim.

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

Bazıları yarım saat ofsetleri olan, bazıları DST'yi gözlemlemeyen çeşitlilik için birkaç zaman dilimi içeriyordu. Onların saatler gitmek böylece Avustralya, güney yarımkürede, bizim kış aylarında DST gözlemler Not geri Nisan ayında ve ileriye Ekim ayında. (Yukarıdaki tablo isimleri ters çevirir, ancak bunu güney yarımküre saat dilimleri için nasıl daha az kafa karıştırıcı hale getireceğinden emin değilim.)

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

Şimdi, TZ'lerin ne zaman değiştiğini bilmek için bir takvim tablosu. Sadece ilgilenilen satırları ekleyeceğim (yukarıdaki her saat dilimi ve 2014 için yalnızca DST değişiklikleri). Hesaplamaları kolaylaştırmak için, hem saat diliminin değiştiği anı UTC'de hem de yerel saatte aynı anı saklıyorum. DST'yi gözlemlemeyen saat dilimleri için, yıl boyunca standarttır ve DST 1 Ocak'ta "başlar".

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

Bunu kesinlikle algoritmalarla doldurabilirsiniz (ve yaklaşan ipucu serisi, kendim söylüyorsam, akıllıca set tabanlı teknikler kullanır), döngü yerine, elle doldurun, ne var. Bu cevap için beş zaman dilimi için bir yılı elle doldurmaya karar verdim ve herhangi bir fantezi hileyi rahatsız etmeyeceğim.

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

Tamam, bu yüzden gerçek verilerimiz ve "boyut" tablolarımız var (bunu söylediğimde kandırırım), o zaman mantık nedir? Kullanıcıların saat dilimlerini seçmelerini ve sorgu için tarih aralığını girmelerini sağlayacağınızı tahmin ediyorum. Ayrıca tarih aralığının kendi saat diliminde tam gün olacağını varsayacağım; kısmi gün yok, kısmi saatleri boş verin. Bu yüzden bir başlangıç ​​tarihi, bir bitiş tarihi ve bir TimeZoneID iletecekler. Oradan, başlangıç ​​/ bitiş tarihini o zaman diliminden UTC'ye dönüştürmek için bir skaler işlev kullanacağız; bu da verileri UTC aralığına göre filtrelememizi sağlayacaktır. Bunu yaptıktan ve üzerinde toplamalarımızı yaptıktan sonra, kullanıcıya göstermeden önce gruplanmış zamanların kaynak saat dilimine dönüştürülmesini uygulayabiliriz.

Skaler 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

Ve tablo değerli fonksiyon:

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

Ve bunu kullanan bir prosedür ( değiştir : 30 dakikalık ofset gruplamasını işleyecek şekilde güncellendi):

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

(Kullanıcının UTC'de raporlama yapmak istemesi durumunda, orada kısa devre yapmak veya ayrı bir saklı yordam geçirmek isteyebilirsiniz - açıkçası UTC'ye ve UTC'den çeviri yapmak boşa harcanan yoğun bir çalışma olacaktır.)

Örnek çağrı:

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

41ms * içinde döner ve bu planı oluşturur:

resim açıklamasını buraya girin

* Yine, atılan sonuçlarla.

2 ay boyunca, 507 ms içinde döner ve plan satır sayıları dışında aynıdır:

resim açıklamasını buraya girin

Biraz daha karmaşık ve artan çalışma süresi biraz olsa da, bu tür bir yaklaşımın köprü tablosu yaklaşımından çok daha iyi çalışacağından oldukça eminim. Ve bu bir dba.se cevabı için manşet dışı bir örnektir; Eminim mantığım ve verimliliğim benden çok daha akıllı insanlar tarafından geliştirilebilir.

Veriler hakkında konuştuğum kenar durumlarını görmek için verileri inceleyebilirsiniz - saatlerin ileri doğru döndüğü saat için çıkış satırı yok, geri döndükleri saat için iki sıra (ve bu saat iki kez oldu). Ayrıca kötü değerlerle oynayabilirsiniz; 20140309 02:30 'da geçerseniz, örneğin Doğu zamanı, çok iyi çalışmayacak.

Raporlamanızın nasıl çalışacağına ilişkin tüm varsayımlara sahip olamayabilirim, bu nedenle bazı ayarlamalar yapmanız gerekebilir. Ama bunun temelleri kapsadığını düşünüyorum.


0

Dönüştürmeyi sunu katmanı yerine depolanmış bir proc veya parametreli görünümde yapabilir misiniz? Başka bir seçenek, bir küp oluşturmak ve hesaplamaları küpte yapmaktır.

Yorumlardan açıklama:

OP, sunum katmanındaki hesaplamaları yaparak sınırlı testleriyle performans sorunlarıyla karşılaştı. Benim önerim veritabanına taşımaktır. Sql'de, tablo değerli bir işlev kullanarak parametreli bir görünüm yapabilirsiniz. Bu işleve iletilen saat dilimine bağlı olarak, veriler UTC tablosundan hesaplanabilir ve döndürülebilir. Umarım bu benim orijinal cevabımı netleştirir.


Peki, her satırın UTC'de kaynak saatinin 100'den fazla saat dilimine çevrildiği 100'den fazla ek sütun içeren bir görünüm? Böyle bir görüşün nasıl yazılacağını kavrayamıyorum bile. Ayrıca SQL Server'ın "parametreli görünümü" olmadığını unutmayın ...
Aaron Bertrand

hmm .. işte böyle düşünüyorsun. ve demek istediğim bu değildi.
KNI

1
Bu yüzden başka türlü düşünmeme izin ver. Bu arada aşağı oylama değildim, sadece cevabınızda daha iyi netliği teşvik etmeye çalışıyordum.
Aaron Bertrand

op, sunum katmanındaki hesaplamaları yaparak sınırlı testleriyle performans sorunlarıyla karşılaştı. Benim önerim bunu veritabanına taşımak. Sql'de, tablo değerli bir işlev kullanarak parametreli bir görünüm yapabilirsiniz. Bu işleve iletilen saat dilimine bağlı olarak, veriler hesaplanabilir ve utc tablosundan döndürülebilir. Umarım bu benim orijinal cevabımı netleştirir.
KNI

Veriler toplanırsa bu nasıl çalışır? Bir saat dilimi 30 dakika ofset ise, veriler farklı bir gruba düşecektir. Sunu katmanında görüntülenen etiketleri değiştiremezsiniz.
Colin 't Hart
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.