MISE À JOUR : pour un exemple plus générique de création et de remplissage d'un calendrier ou d'un tableau de dimensions, consultez cette astuce:
Pour la question spécifique à portée de main, voici ma tentative. Je mettrai à jour cela avec la magie que vous utilisez pour déterminer des choses comme Fiscal_MonthNumber et Fiscal_MonthName, car en ce moment, elles sont la seule partie non intuitive de votre question, et ce sont les seules informations tangibles que vous n'avez pas réellement incluses.
La "meilleure" manière (lire: la plus efficace) de remplir une table de calendrier, à mon humble avis, est d'utiliser un ensemble, plutôt qu'une boucle. Et vous pouvez générer cet ensemble sans enfouir la logique dans des fonctions définies par l'utilisateur, qui ne vous gagnent vraiment que l'encapsulation - sinon c'est juste un autre objet à maintenir. J'en parle beaucoup plus en détail dans cette série de blogs:
Si vous souhaitez continuer à utiliser votre fonction, assurez-vous que ce n'est pas une fonction table multi-instructions; ça ne va pas être efficace du tout. Vous voulez vous assurer qu'il est en ligne (par exemple, a une seule RETURN
déclaration et aucune @table
déclaration explicite ), a WITH SCHEMABINDING
et n'utilise pas de CTE récursifs. En dehors d'une fonction, voici comment je le ferais:
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.
Avec la table en place, vous pouvez effectuer une insertion unique, basée sur un ensemble, d'autant d'années de données que vous le souhaitez à partir de la date de début que vous choisissez. Précisez simplement la date de début et le nombre d'années. J'utilise une technique "CTE empilé" pour éviter la redondance et n'effectue une multitude de calculs qu'une seule fois; les colonnes de sortie des CTE antérieurs sont ensuite utilisées ultérieurement dans d'autres calculs.
-- 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;
Maintenant, vous avez encore ces colonnes "vacances" et "journée de travail" à gérer - cela devient un peu plus lourd, mais vous devez mettre à jour ces trois colonnes avec tous les jours fériés qui apparaissent dans votre plage de dates. Des choses comme le jour de Noël sont vraiment faciles:
UPDATE dbo.DateDimension
SET IsWorkday = 0, IsHoliday = 1, HolidayName = 'Christmas'
WHERE Calendar_Month_Number = 12 AND Calendar_DayOfMonth = 25;
Des choses comme Pâques deviennent beaucoup plus délicates - j'ai blogué quelques idées ici il y a de nombreuses années .
Et bien sûr, les jours fériés de votre entreprise qui n'ont absolument rien à voir avec les jours fériés, etc. doivent être mis à jour directement par vous - SQL Server n'a pas de moyen intégré de connaître le calendrier de votre entreprise.
Maintenant, je me suis délibérément éloigné du calcul de ces colonnes, parce que vous avez dit quelque chose comme les utilisateurs finaux previously preferred fields they can drag and drop
- je ne sais pas si les utilisateurs finaux savent vraiment ou se soucient si la source d'une colonne est une vraie colonne, une colonne calculée , ou provient d'une vue, d'une requête ou d'une fonction ...
En supposant que vous ne voulez regarder dans le calcul de certaines de ces colonnes pour faciliter l' entretien de votre (et les persistent au stockage de payer pour la vitesse de requête), vous pouvez vérifier. Cependant, juste comme un avertissement, certaines de ces colonnes ne peuvent pas être définies comme calculées et persistantes car elles ne sont pas déterministes. Voici un exemple et comment le contourner.
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS DATEPART(WEEKDAY, [date]) PERSISTED
);
Résultats:
Msg 4936, niveau 16, état 1, ligne 130
La colonne calculée «DayOfWeek_Number» dans la table «Test» ne peut pas être conservée car la colonne n'est pas déterministe.
La raison pour laquelle cela ne peut pas être persistant est que de nombreuses fonctions liées à la date dépendent des paramètres de session de l'utilisateur, comme DATEFIRST
. SQL Server ne peut pas conserver la colonne ci-dessus car il DATEPART(WEEKDAY
devrait donner des résultats différents - avec les mêmes données - pour deux utilisateurs différents qui ont des DATEFIRST
paramètres différents .
Ensuite, vous pourriez devenir intelligent et dire, eh bien, je peux le définir comme le nombre de jours, modulo 7, décalé d'un jour que je sais être un samedi (disons '2000-01-01'
). Vous essayez donc:
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS
COALESCE(NULLIF(DATEDIFF(DAY,'20000101',[date])%7,0),7) PERSISTED
);
Mais, même erreur.
Au lieu d'utiliser une conversion implicite à partir d'un littéral de chaîne qui représente une date-heure dans un format non ambigu (pour nous, mais pas pour SQL Server), nous pouvons utiliser le nombre de jours entre la "date zéro" (1900-01-01) et cette date que nous connaissons est un samedi (2000-01-01). Si nous utilisons un entier ici pour représenter la différence en jours, SQL Server ne peut pas se plaindre, car il n'y a aucun moyen de mal interpréter ce nombre. Donc ça marche:
-- 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
);
Succès!
Si vous êtes intéressé à poursuivre les colonnes calculées pour certains de ces calculs, faites-le moi savoir.
Oh, et une dernière chose: je ne sais pas pourquoi vous feriez jamais frotter cette table et la re-remplir à partir de zéro. Combien de ces choses vont changer? Allez-vous modifier constamment votre exercice financier? Changer la façon dont vous voulez épeler mars? Réglez votre semaine pour commencer le lundi une semaine et jeudi la prochaine? Cela devrait vraiment être une table à construire une fois, puis vous apportez des modifications mineures (comme la mise à jour de lignes individuelles avec des informations de vacances nouvelles / modifiées).