UPDATE : Ein allgemeineres Beispiel zum Erstellen und Auffüllen eines Kalenders oder einer Dimensionstabelle finden Sie in diesem Tipp:
Für die spezifische Frage hier ist mein Versuch. Ich werde dies mit der Magie aktualisieren, mit der Sie Dinge wie Fiscal_MonthNumber und Fiscal_MonthName bestimmen, da sie derzeit der einzige nicht intuitive Teil Ihrer Frage sind und die einzigen konkreten Informationen, die Sie tatsächlich nicht aufgenommen haben.
Die "beste" (sprich: effizienteste) Methode zum Auffüllen einer Kalendertabelle, IMHO, ist die Verwendung eines Satzes anstelle einer Schleife. Und Sie können diesen Satz generieren, ohne die Logik in benutzerdefinierte Funktionen zu vergraben, die Ihnen wirklich nichts anderes als die Kapselung bringen - ansonsten ist es nur ein weiteres zu wartendes Objekt. Ich spreche in dieser Blogserie viel ausführlicher darüber:
Wenn Sie Ihre Funktion weiterhin verwenden möchten, stellen Sie sicher, dass es sich nicht um eine Tabellenwertfunktion mit mehreren Anweisungen handelt. das wird überhaupt nicht effizient sein. Sie möchten sicherstellen, dass es inline ist (z. B. eine einzelne RETURN
Anweisung und keine explizite @table
Deklaration), WITH SCHEMABINDING
rekursive CTEs hat und nicht verwendet. Außerhalb einer Funktion würde ich Folgendes tun:
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.
Wenn die Tabelle vorhanden ist, können Sie ab einem beliebigen Startdatum eine einzelne, satzbasierte Einfügung von Daten aus so vielen Jahren durchführen, wie Sie möchten. Geben Sie einfach das Startdatum und die Anzahl der Jahre an. Ich verwende eine "gestapelte CTE" -Technik, um Redundanz zu vermeiden, und führe eine ganze Reihe von Berechnungen nur einmal durch. Die Ausgabespalten der früheren CTEs werden später in weiteren Berechnungen verwendet.
-- 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;
Jetzt müssen Sie noch diese Spalten "Feiertag" und "Arbeitstag" bearbeiten - dies wird etwas umständlicher, aber Sie müssen diese drei Spalten mit allen Feiertagen aktualisieren, die in Ihrem Datumsbereich angezeigt werden. Dinge wie der Weihnachtstag sind wirklich einfach:
UPDATE dbo.DateDimension
SET IsWorkday = 0, IsHoliday = 1, HolidayName = 'Christmas'
WHERE Calendar_Month_Number = 12 AND Calendar_DayOfMonth = 25;
Dinge wie Ostern werden viel schwieriger - ich habe hier vor vielen Jahren einige Ideen gebloggt .
Und natürlich müssen Ihre arbeitsfreien Tage, die absolut nichts mit Feiertagen usw. zu tun haben, direkt von Ihnen aktualisiert werden - SQL Server verfügt nicht über eine integrierte Methode, um den Kalender Ihres Unternehmens zu kennen.
Jetzt habe ich mich absichtlich von der Berechnung einer dieser Spalten ferngehalten, weil Sie so etwas wie die Endbenutzer gesagt haben previously preferred fields they can drag and drop
- ich bin mir nicht sicher, ob die Endbenutzer wirklich wissen oder sich darum kümmern, ob die Quelle einer Spalte eine echte Spalte ist, eine berechnete Spalte oder stammt aus einer Ansicht, Abfrage oder Funktion ...
Angenommen , Sie tun wollen Einblick in einige dieser Spalten Berechnung auf Wartung zu erleichtern (und bestehen bleiben , sie zu bezahlen Speicher für Abfragegeschwindigkeit), können Sie in diesem Blick. Nur als Warnung können einige dieser Spalten jedoch nicht als berechnet und beibehalten definiert werden, da sie nicht deterministisch sind. Hier ist ein Beispiel und wie man es umgeht.
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS DATEPART(WEEKDAY, [date]) PERSISTED
);
Ergebnisse:
Meldung 4936, Ebene 16,
Status 1, Zeile 130
Die berechnete Spalte 'DayOfWeek_Number' in der Tabelle 'Test' kann nicht beibehalten werden, da die Spalte nicht deterministisch ist.
Der Grund, warum dies nicht beibehalten werden kann, ist, dass viele datumsbezogene Funktionen von den Sitzungseinstellungen des Benutzers abhängen, wie z DATEFIRST
. SQL Server kann die obige Spalte nicht beibehalten, da sie DATEPART(WEEKDAY
bei gleichen Daten unterschiedliche Ergebnisse für zwei verschiedene Benutzer liefern sollte, die zufällig unterschiedliche DATEFIRST
Einstellungen haben.
Dann könnten Sie schlau werden und sagen, nun, ich kann es auf die Anzahl der Tage einstellen, Modulo 7, versetzt von einem Tag, von dem ich weiß, dass es ein Samstag ist (sagen wir '2000-01-01'
). Also versuchst du:
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS
COALESCE(NULLIF(DATEDIFF(DAY,'20000101',[date])%7,0),7) PERSISTED
);
Aber der gleiche Fehler.
Anstatt eine implizite Konvertierung aus einem Zeichenfolgenliteral zu verwenden, das eine Datums- und Uhrzeitangabe in einem eindeutigen Format (für uns, jedoch nicht für SQL Server) darstellt, können wir die Anzahl der Tage zwischen dem "Nulldatum" (1900-01-01) und verwenden Dieses Datum, das wir kennen, ist ein Samstag (2000-01-01). Wenn wir hier eine Ganzzahl verwenden, um den Unterschied in Tagen darzustellen, kann sich SQL Server nicht beschweren, da es keine Möglichkeit gibt, diese Zahl falsch zu interpretieren. Das funktioniert also:
-- 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
);
Erfolg!
Wenn Sie für einige dieser Berechnungen berechnete Spalten verwenden möchten, lassen Sie es mich wissen.
Oh, und noch eine letzte Sache: Ich weiß nicht, warum Sie diesen Tisch jemals schrubben und von Grund auf neu füllen würden. Wie viele dieser Dinge werden sich ändern? Ändern Sie Ihr Geschäftsjahr ständig? Ändern Sie, wie Sie März buchstabieren möchten? Stellen Sie Ihre Woche so ein, dass sie am Montag einer Woche und am Donnerstag der nächsten beginnt? Dies sollte wirklich eine einmalige Tabelle sein, und dann nehmen Sie kleinere Änderungen vor (z. B. das Aktualisieren einzelner Zeilen mit neuen / geänderten Urlaubsinformationen).