日付ディメンションテーブルにデータを入力するための最適な方法


8

SQL Server 2008データベースに日付ディメンションテーブルを設定することを検討しています。テーブルのフィールドは次のとおりです。

[DateId]                    INT IDENTITY(1,1) PRIMARY KEY
[DateTime]                  DATETIME
[Date]                      DATE
[DayOfWeek_Number]          TINYINT
[DayOfWeek_Name]            VARCHAR(9)
[DayOfWeek_ShortName]       VARCHAR(3)
[Week_Number]               TINYINT
[Fiscal_DayOfMonth]         TINYINT
[Fiscal_Month_Number]       TINYINT
[Fiscal_Month_Name]         VARCHAR(12)
[Fiscal_Month_ShortName]    VARCHAR(3)
[Fiscal_Quarter]            TINYINT     
[Fiscal_Year]               INT
[Calendar_DayOfMonth]       TINYINT
[Calendar_Month Number]     TINYINT     
[Calendar_Month_Name]       VARCHAR(9)
[Calendar_Month_ShortName]  VARCHAR(3)
[Calendar_Quarter]          TINYINT
[Calendar_Year]             INT
[IsLeapYear]                BIT
[IsWeekDay]                 BIT
[IsWeekend]                 BIT
[IsWorkday]                 BIT
[IsHoliday]                 BIT
[HolidayName]               VARCHAR(255)

2つのパラメーターの日付D1とD2の間のすべての日付を返す関数DateListInRange(D1、D2)を作成しました。

すなわち。パラメータ '2014-01-01'および '2014-01-03'は以下を返します。

2014-01-01
2014-01-02
2014-01-03

2010-01-01から2020-01-01の範囲内のすべての日付のDATE_DIMテーブルにデータを入力したい。ほとんどのフィールドには、SQL 2008のDATEPART、DATENAME、およびYEAR関数を入力できます。

会計データにはもう少し多くのロジックが含まれており、その一部は互いに依存しています。例:会計四半期1->会計月は1、2または3でなければなりません会計四半期2->会計月は4、5または6でなければなりません

特定の日付を受け入れ、すべての会計データまたはすべてのフィールドを出力するテーブル値関数を簡単に作成できます。次に、この関数は、DateListInRange関数の各行で実行する必要があります。

休日の表が変更された場合、これは年に数回入力するだけでよいので、速度にはあまり関心がありません。

これをSQLで書くための最良の方法は何ですか?

現在、このように:

SELECT 
    [Date],
    CAST([Date] AS DATE)                AS [Date],
    DATEPART(W,[Date])                  AS [DayOfWeek_Number], -- First day of week is sunday
    DATENAME(W,[Date])                  AS [DayOfWeek_Name],
    SUBSTRING(DATENAME(DW,[Date]),1,3)  AS [DayOfWeek_ShortName],
    DATEPART(WK, [Date])                AS [WeekNumber],
    DATEPART(M, [Date])                 AS [Calendar_Month_Number],
    DATENAME(M, [Date])                 AS [Calendar_Month_Name],
    SUBSTRING(DATENAME(M, [Date]),1,3)  AS [Calendar_Month_ShortName],
    DATEPART(QQ, [Date])                AS [Calendar_Quarter],
    YEAR([Date])                        AS [Calendar_Year],

    CASE WHEN
    (
        (YEAR([Date]) % 4 = 0) AND (YEAR([Date]) % 100 != 0) 
        OR
        (YEAR([Date]) % 400 = 0)
    )
    THEN 1 ELSE 0 
    END                                     AS [IsLeapYear],

    CASE WHEN
    (
        DATEPART(W,[Date]) = 1 OR DATEPART(W,[Date]) = 7
    )
    THEN 0 ELSE 1
    END                                     AS [IsWeekDay]
FROM [DateListForRange] 
('2014-01-01','2014-01-31')

会計データについても同じことを行うと、各ケースのステートメントがかなり繰り返されるため、関数を使用するとステートメントを回避でき、日付のリストにTVFをクロス適用できます。

SQL Server 2008を使用しているため、新しい日付機能の多くは最小限に抑えられています。

回答:


12

UPDATE:カレンダーまたはディメンションテーブルを作成してデータを追加する一般的な例については、次のヒントを参照してください。

手元にある特定の質問については、これが私の試みです。これは、Fiscal_MonthNumberやFiscal_MonthNameのようなものを決定するために使用する魔法で更新します。現時点では、これらは質問の非直感的な部分だけであり、実際には含まれていない唯一の具体的な情報だからです。

カレンダーテーブルIMHOにデータを入力するための「最良の」(読み取り:最も効率的な)方法は、ループではなくセットを使用することです。そして、ユーザー定義関数にロジックを埋め込むことなく、このセットを生成できます。これは、カプセル化以外には何も得られません。それ以外の場合は、維持する別のオブジェクトにすぎません。このことについては、このブログシリーズでさらに詳しく説明します。

関数を使い続けたい場合は、それが複数ステートメントのテーブル値関数ではないことを確認してください。それはまったく効率的ではありません。インラインであること(たとえば、単一のRETURNステートメントがあり、明示的な@table宣言WITH SCHEMABINDINGがないこと)、があり、再帰的なCTEを使用していないことを確認する必要があります。関数の外で、私はそれをどのように行うかを以下に示します:

CREATE TABLE dbo.DateDimension
(
  [Date]                      DATE PRIMARY KEY,
  [DayOfWeek_Number]          TINYINT,
  [DayOfWeek_Name]            VARCHAR(9),
  [DayOfWeek_ShortName]       VARCHAR(3),
  [Week_Number]               TINYINT,
  [Fiscal_DayOfMonth]         TINYINT,
  [Fiscal_Month_Number]       TINYINT,
  [Fiscal_Month_Name]         VARCHAR(12),
  [Fiscal_Month_ShortName]    VARCHAR(3),
  [Fiscal_Quarter]            TINYINT,     
  [Fiscal_Year]               SMALLINT,
  [Calendar_DayOfMonth]       TINYINT,
  [Calendar_Month Number]     TINYINT,     
  [Calendar_Month_Name]       VARCHAR(9),
  [Calendar_Month_ShortName]  VARCHAR(3),
  [Calendar_Quarter]          TINYINT,
  [Calendar_Year]             SMALLINT, 
  [IsLeapYear]                BIT,
  [IsWeekDay]                 BIT,
  [IsWeekend]                 BIT,
  [IsWorkday]                 BIT,
  [IsHoliday]                 BIT,
  [HolidayName]               VARCHAR(255)
);
-- add indexes, constraints, etc.

テーブルを配置すると、選択した任意の開始日から、必要に応じて何年ものデータの単一のセットベースの挿入を実行できます。開始日と年数を指定するだけです。「スタックCTE」手法を使用して冗長性を回避し、一連の計算を一度だけ実行します。その後、以前のCTEからの出力列は、後でさらに計算に使用されます。

-- these are important:
SET LANGUAGE US_ENGLISH;
SET DATEFIRST 7;

DECLARE @start DATE = '20100101', @years TINYINT = 20;

;WITH src AS
(
  -- you don't need a function for this...
  SELECT TOP (DATEDIFF(DAY, @start, DATEADD(YEAR, @years, @start)))
    d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY s1.number)-1, @start)
   FROM master.dbo.spt_values AS s1
   CROSS JOIN master.dbo.spt_values AS s2
   -- your own numbers table works much better here, but this'll do
),
w AS 
(
  SELECT d, 
    wd      = DATEPART(WEEKDAY,d), 
    wdname  = DATENAME(WEEKDAY,d), 
    wnum    = DATEPART(ISO_WEEK,d),
    qnum    = DATEPART(QUARTER, d),
    y       = YEAR(d),
    m       = MONTH(d),
    mname   = DATENAME(MONTH,d),
    md      = DAY(d)
  FROM src
),
q AS
(
  SELECT *, 
    wdsname   = LEFT(wdname,3),
    msname    = LEFT(mname,3),
    IsWeekday = CASE WHEN wd IN (1,7) THEN 0 ELSE 1 END,
    fq1 = DATEADD(DAY,25,DATEADD(MONTH,2,DATEADD(YEAR,YEAR(d)-1900,0)))
  FROM w
),
q1 AS
(
  SELECT *, 
    -- useless, just inverse of IsWeekday, but okay:
    IsWeekend = CASE WHEN IsWeekday = 1 THEN 0 ELSE 1 END,
    fq = COALESCE(NULLIF(DATEDIFF(QUARTER,DATEADD(DAY,6,fq1),d) 
         + CASE WHEN md >= 26 AND m%3 = 0 THEN 2 ELSE 1 END,0),4)
    FROM q
)
--INSERT dbo.DimWithDateAllPersisted(Date)
SELECT 
  DateKey = d,
  DayOfWeek_Number = wd,
  DayOfWeek_Name = wdname,
  DayOfWeek_ShortName = wdsname,
  Week_Number = wnum,
  -- I'll update these four lines when I have usable info
  Fiscal_DayOfMonth      = 0,--'?magic?',
  Fiscal_Month_Number    = 0,--'?magic?',
  Fiscal_Month_Name      = 0,--'?magic?',
  Fiscal_Month_ShortName = 0,--'?magic?',
  Fiscal_Quarter = fq,
  Fiscal_Year = CASE WHEN fq = 4 AND m < 3 THEN y-1 ELSE y END,
  Calendar_DayOfMonth = md,
  Calendar_Month_Number = m,
  Calendar_Month_Name = mname,
  Calendar_Month_ShortName = msname,
  Calendar_Quarter = qnum,
  Calendar_Year = y,
  IsLeapYear = CASE 
    WHEN (y%4 = 0 AND y%100 != 0) OR (y%400 = 0) THEN 1 ELSE 0 END,
  IsWeekday,
  IsWeekend,
  IsWorkday = CASE WHEN IsWeekday = 1 THEN 1 ELSE 0 END,
  IsHoliday = 0,
  HolidayName = ''
FROM q1;

これで、これらの「休日」列と「平日」列を処理することができます。これは少し面倒ですが、日付範囲に表示される休日でこれらの3つの列を更新する必要があります。クリスマスのようなことは本当に簡単です:

UPDATE dbo.DateDimension
  SET IsWorkday = 0, IsHoliday = 1, HolidayName = 'Christmas'
  WHERE Calendar_Month_Number = 12 AND Calendar_DayOfMonth = 25;

イースターのようなものはもっとトリッキーになります- 私はここ何年か前にいくつかのアイデアをブログに書いています

そしてもちろん、祝日などとはまったく関係のない非就業日は、直接更新する必要があります。SQLServerには、会社のカレンダーを知るための組み込みの方法がありません。

今、私は意図的にこれらの列の計算に近づきませんでした。なぜなら、エンドユーザーが持っているようなことを言ったからですpreviously preferred fields they can drag and drop- エンドユーザーが列のソースが実際の列であるか計算された列であるかどうか本当にエンドユーザーが知っているかどうかはわかりません、またはビュー、クエリ、または関数からのもの...

メンテナンスを容易にするためにこれらの列のいくつかを計算することを検討たい(そして、クエリ速度のためにストレージを支払うためにそれらを維持たい)と仮定すると、それを調査することができます。ただし、警告と同様に、これらの列の一部は非決定的であるため、計算済みとして定義できず、永続化できません。以下に1つの例とその回避方法を示します。

CREATE TABLE dbo.Test
(
  [date] DATE PRIMARY KEY,
  DayOfWeek_Number AS DATEPART(WEEKDAY, [date]) PERSISTED
);

結果:

メッセージ4936、レベル16、状態1、行130
テーブル 'Test'の計算列 'DayOfWeek_Number'は、列が非決定的であるため保持できません。

これを保持できないのは、などの多くの日付関連関数がユーザーのセッション設定に依存しているためDATEFIRSTです。SQL Serverは上記の列を永続化できません。DATEPART(WEEKDAY異なるDATEFIRST設定を持つ2人の異なるユーザーに対して、同じデータが与えられると、異なる結果が得られるはずです。

すると、賢くなるかもしれません'2000-01-01'。たとえば、土曜日であることがわかっているある日からのオフセットである7を法とする日数に設定できます(たとえば、)。だからあなたは試してみます:

CREATE TABLE dbo.Test
(
  [date] DATE PRIMARY KEY,
  DayOfWeek_Number AS 
    COALESCE(NULLIF(DATEDIFF(DAY,'20000101',[date])%7,0),7) PERSISTED
);

しかし、同じエラー。

明確な(SQL Serverではなく)日付形式で日付時刻を表す文字列リテラルからの暗黙の変換を使用する代わりに、「ゼロ日付」(1900-01-01)と私たちが知っているその日付は土曜日(2000-01-01)です。ここで整数を使用して日数の違いを表す場合、SQL Serverはその数を誤って解釈する方法がないため、文句を言うことはできません。したがって、これは機能します:

-- SELECT DATEDIFF(DAY, 0, '20000101');  -- 36524

CREATE TABLE dbo.Test
(
  [date] DATE PRIMARY KEY,
  DayOfWeek_Number AS 
    COALESCE(NULLIF(DATEDIFF(DAY,36524,[date])%7,0),7) PERSISTED
    -----------------------------^^^^^  only change
);

成功!

これらの計算の一部で計算カラムを追求することに興味がある場合は、お知らせください。

ああ、最後にもう1つあります。なぜこのテーブルをスクラブして、最初から再作成するのか、わかりません。これらのうちどれだけが変わるのでしょうか?あなたは常にあなたの会計年度を変更するつもりですか?マーチの綴り方を変更しますか?週を月曜日に開始し、次の週の木曜日に開始するように設定しますか?これは実際には1回限りのビルドテーブルである必要があります。その後、マイナーな調整を行います(新しい/変更された休日情報で個々の行を更新するなど)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.