AGGIORNAMENTO : per un esempio più generico di creazione e popolamento di un calendario o di una tabella di dimensioni, vedere questo suggerimento:
Per la domanda specifica a portata di mano, ecco il mio tentativo. Lo aggiornerò con la magia che usi per determinare cose come Fiscal_MonthNumber e Fiscal_MonthName, perché in questo momento sono l'unica parte non intuitiva della tua domanda, ed è l'unica informazione tangibile che in realtà non hai incluso.
Il modo "migliore" (leggi: più efficiente) per popolare una tabella di calendario, IMHO, è utilizzare un set, piuttosto che un ciclo. E puoi generare questo set senza seppellire la logica in funzioni definite dall'utente, che in realtà non ti fanno guadagnare altro che incapsulamento, altrimenti è solo un altro oggetto da mantenere. Ne parlo in modo molto più dettagliato in questa serie di blog:
Se si desidera continuare a utilizzare la propria funzione, assicurarsi che non sia una funzione con valori di tabella con più istruzioni; non sarà affatto efficiente. Vuoi assicurarti che sia in linea (ad es. Abbia una singola RETURN
istruzione e nessuna @table
dichiarazione esplicita ), abbia WITH SCHEMABINDING
e non usi CTE ricorsivi. Al di fuori di una funzione, ecco come lo farei:
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.
Con la tabella in atto, è possibile eseguire un singolo inserimento basato su set di tutti gli anni di dati desiderati da qualsiasi data di inizio scelta. Basta specificare la data di inizio e il numero di anni. Uso una tecnica "CTE in pila" per evitare la ridondanza ed eseguire una sola serie di calcoli una sola volta; le colonne di output dei precedenti CTE vengono successivamente utilizzate in ulteriori calcoli in seguito.
-- 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;
Ora, hai ancora queste colonne "ferie" e "giorni feriali" da affrontare: questo diventa un po 'più ingombrante, ma devi aggiornare quelle tre colonne con tutte le festività che compaiono nell'intervallo di date. Cose come il giorno di Natale sono davvero facili:
UPDATE dbo.DateDimension
SET IsWorkday = 0, IsHoliday = 1, HolidayName = 'Christmas'
WHERE Calendar_Month_Number = 12 AND Calendar_DayOfMonth = 25;
Le cose come la Pasqua diventano molto più complicate - ho scritto alcune idee sul blog qui molti anni fa .
E, naturalmente, i giorni non lavorativi della tua azienda che non hanno assolutamente nulla a che fare con i giorni festivi ecc. Devono essere aggiornati direttamente da te - SQL Server non avrà un modo integrato per conoscere il calendario della tua azienda.
Ora, ho volutamente tenuto lontano dal calcolare una di queste colonne, perché hai detto qualcosa come gli utenti finali hanno previously preferred fields they can drag and drop
: non sono sicuro che gli utenti finali sappiano davvero o si preoccupino se l'origine di una colonna è una colonna reale, una colonna calcolata o proviene da una vista, query o funzione ...
Supponendo che fare voler esaminare calcolo alcune di queste colonne per facilitare il vostro manutenzione (e persistere per stoccaggio retribuzione per velocità di query), si può guardare in quello. Tuttavia, proprio come un avvertimento, alcune di queste colonne non possono essere definite come calcolate e persistenti perché non sono deterministiche. Ecco un esempio e come aggirarlo.
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS DATEPART(WEEKDAY, [date]) PERSISTED
);
risultati:
Messaggio 4936, livello 16, stato 1, riga 130
La colonna calcolata "DayOfWeek_Number" nella tabella "Test" non può essere mantenuta poiché la colonna non è deterministica.
Il motivo per cui non è possibile persistere è perché molte funzioni relative alla data si basano sulle impostazioni della sessione dell'utente, come DATEFIRST
. SQL Server non può persistere nella colonna sopra perché DATEPART(WEEKDAY
dovrebbe dare risultati diversi - dati gli stessi dati - per due utenti diversi che hanno DATEFIRST
impostazioni diverse .
Allora potresti diventare intelligente, e dire, beh, posso impostarlo come il numero di giorni, modulo 7, offset da un giorno che so essere un sabato (diciamo, '2000-01-01'
). Quindi provi:
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS
COALESCE(NULLIF(DATEDIFF(DAY,'20000101',[date])%7,0),7) PERSISTED
);
Ma stesso errore.
Invece di utilizzare una conversione implicita da una stringa letterale che rappresenta una data e ora in un formato non ambiguo (per noi, ma non SQL Server), possiamo usare il numero di giorni tra la "data zero" (1900-01-01) e quella data che conosciamo è un sabato (2000-01-01). Se utilizziamo un numero intero qui per rappresentare la differenza in giorni, SQL Server non può lamentarsi, perché non c'è modo di interpretare erroneamente quel numero. Quindi funziona:
-- 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
);
Successo!
Se sei interessato a perseguire colonne calcolate per alcuni di questi calcoli, fammi sapere.
Oh, e un'ultima cosa: non so perché dovresti mai pulire questo tavolo e ripopolarlo da zero. Quante di queste cose cambieranno? Hai intenzione di modificare costantemente l'anno fiscale? Cambia il modo in cui vuoi scrivere marzo? Impostare la settimana in modo che inizi lunedì una settimana e giovedì il prossimo? Questa dovrebbe davvero essere una tabella build-it-up-once e quindi apportare piccole modifiche (come l'aggiornamento di singole righe con informazioni sulle vacanze nuove / modificate).