ОБНОВЛЕНИЕ : для более общего примера создания и заполнения календаря или таблицы измерений, смотрите этот совет:
Для конкретного вопроса, вот моя попытка. Я дополню это магией, которую вы используете для определения таких вещей, как 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;
Теперь у вас все еще есть эти столбцы «выходной» и «рабочий день», с которыми приходится иметь дело - это становится немного более громоздким, но вам нужно обновить эти три столбца с любыми выходными, которые появляются в вашем диапазоне дат. Такие вещи, как Рождество очень просты:
UPDATE dbo.DateDimension
SET IsWorkday = 0, IsHoliday = 1, HolidayName = 'Christmas'
WHERE Calendar_Month_Number = 12 AND Calendar_DayOfMonth = 25;
Такие вещи, как Пасха, становятся намного сложнее - я опубликовал здесь некоторые идеи много лет назад .
И, конечно, нерабочие дни вашей компании, которые не имеют абсолютно никакого отношения к праздничным дням и т. Д., Должны обновляться непосредственно вами - у SQL Server не будет встроенного способа узнать календарь вашей компании.
Теперь я намеренно держался подальше от вычисления любого из этих столбцов, потому что вы сказали что-то вроде конечных пользователей previously preferred fields they can drag and drop
- я не уверен, что конечные пользователи действительно знают или заботятся, является ли источник столбца реальным столбцом, вычисляемым столбцом или исходит из представления, запроса или функции ...
Предполагая, что вы действительно хотите изучить некоторые из этих столбцов, чтобы упростить обслуживание (и сохранить их, чтобы оплачивать хранилище для скорости запросов), вы можете в этом разобраться. Однако, как предупреждение, некоторые из этих столбцов не могут быть определены как вычисленные и сохраненные, поскольку они недетерминированы. Вот один пример, и как обойти это.
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS DATEPART(WEEKDAY, [date]) PERSISTED
);
Результаты:
Сообщение 4936, Уровень 16, Состояние 1, Строка 130
Вычисляемый столбец 'DayOfWeek_Number' в таблице 'Тест' не может быть сохранен, поскольку столбец недетерминирован.
Причина, по которой это не может быть сохранено, заключается в том, что многие функции, связанные с датами, полагаются на настройки сеанса пользователя, например DATEFIRST
. SQL Server не может сохранить вышеприведенный столбец, потому что он DATEPART(WEEKDAY
должен давать разные результаты - при одинаковых данных - для двух разных пользователей, у которых разные DATEFIRST
параметры.
Тогда вы можете проявить смекалку и сказать: ну, я могу установить это число дней, по модулю 7, смещение от того дня, который я знаю, как субботу (скажем, '2000-01-01'
). Итак, вы попробуйте:
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
);
Успех!
Если вы заинтересованы в поиске вычисляемых столбцов для некоторых из этих вычислений, дайте мне знать.
Да, и еще одна вещь: я не знаю, почему вы когда-нибудь почистили бы этот стол и заново заполнили его с нуля. Сколько из этих вещей изменится? Собираетесь ли вы постоянно менять свой финансовый год? Изменить, как вы хотите записать март? Установить свою неделю, чтобы начать в понедельник одну неделю и четверг в следующую? Это действительно должна быть таблица «один раз в сборку», а затем вы вносите небольшие изменения (например, обновляете отдельные строки новой / измененной информацией о праздниках).