ACTUALIZACIÓN : para obtener un ejemplo más genérico de crear y completar un calendario o tabla de dimensiones, consulte este consejo:
Para la pregunta específica en cuestión, aquí está mi intento. Actualizaré esto con la magia que usas para determinar cosas como Fiscal_MonthNumber y Fiscal_MonthName, porque en este momento son la única parte no intuitiva de tu pregunta, y es la única información tangible que realmente no incluiste.
La "mejor" (léase: la más eficiente) forma de llenar una tabla de calendario, en mi humilde opinión, es usar un conjunto, en lugar de un bucle. Y puede generar este conjunto sin enterrar la lógica en funciones definidas por el usuario, que realmente no le dan más que encapsulación; de lo contrario, es solo otro objeto para mantener. Hablo de esto con mucho más detalle en esta serie de blogs:
Si desea seguir usando su función, asegúrese de que no sea una función con valores de tabla de varias instrucciones; eso no va a ser eficiente en absoluto. Desea asegurarse de que esté en línea (por ejemplo, que tenga una sola RETURN
declaración y no una @table
declaración explícita ), que tenga WITH SCHEMABINDING
y no use CTE recursivos. Fuera de una función, así es como lo haría:
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 tabla en su lugar, puede realizar una inserción única basada en conjuntos de tantos años de datos como desee desde la fecha de inicio que elija. Simplemente especifique la fecha de inicio y el número de años. Utilizo una técnica de "CTE apilado" para evitar la redundancia y solo realizo una gran cantidad de cálculos una vez; Las columnas de salida de los CTE anteriores se utilizan posteriormente en otros cálculos posteriores.
-- 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;
Ahora, todavía tiene que lidiar con estas columnas de "feriado" y "día laborable"; esto se vuelve un poco más engorroso, pero necesita actualizar esas tres columnas con cualquier feriado que aparezca en su rango de fechas. Cosas como el día de Navidad son realmente fáciles:
UPDATE dbo.DateDimension
SET IsWorkday = 0, IsHoliday = 1, HolidayName = 'Christmas'
WHERE Calendar_Month_Number = 12 AND Calendar_DayOfMonth = 25;
Cosas como la Pascua se vuelven mucho más complicadas: he publicado algunas ideas aquí hace muchos años .
Y, por supuesto, los días no laborables de su empresa que no tienen absolutamente nada que ver con los días festivos, etc., deben ser actualizados directamente por usted: SQL Server no tendrá una forma integrada de conocer el calendario de su empresa.
Ahora, deliberadamente me mantuve alejado de calcular cualquiera de estas columnas, porque dijiste algo como lo han dicho los usuarios finales previously preferred fields they can drag and drop
: no estoy seguro de si los usuarios finales realmente saben o les importa si la fuente de una columna es una columna real, una columna calculada , o proviene de una vista, consulta o función ...
Asumiendo que hacer que desee ver en el cálculo de algunas de estas columnas para facilitar en su mantenimiento (y persistir a almacenamiento de pago de la velocidad de consulta), se puede ver en eso. Sin embargo, solo como advertencia, algunas de estas columnas no se pueden definir como calculadas y persistentes porque no son deterministas. Aquí hay un ejemplo y cómo solucionarlo.
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS DATEPART(WEEKDAY, [date]) PERSISTED
);
Resultados:
Msg 4936, Nivel 16, Estado 1, Línea 130
La columna calculada 'DayOfWeek_Number' en la tabla 'Prueba' no puede persistir porque la columna no es determinista.
La razón por la que esto no puede persistir es porque muchas funciones relacionadas con la fecha dependen de la configuración de la sesión del usuario, como DATEFIRST
. SQL Server no puede persistir en la columna anterior porque DATEPART(WEEKDAY
debería dar resultados diferentes, dados los mismos datos, para dos usuarios diferentes que tienen DATEFIRST
configuraciones diferentes .
Entonces puede ser inteligente y decir, bueno, puedo configurarlo para que sea el número de días, módulo 7, compensado de algún día que sé que es un sábado (digamos '2000-01-01'
). Entonces intentas:
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS
COALESCE(NULLIF(DATEDIFF(DAY,'20000101',[date])%7,0),7) PERSISTED
);
Pero, el mismo error.
En lugar de usar una conversión implícita de un literal de cadena que represente una fecha y hora en un formato inequívoco (para nosotros, pero no SQL Server), podemos usar el número de días entre la "fecha cero" (1900-01-01) y esa fecha sabemos que es un sábado (2000-01-01). Si usamos un número entero aquí para representar la diferencia en días, SQL Server no puede quejarse, porque no hay forma de malinterpretar ese número. Entonces esto funciona:
-- 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
);
¡Éxito!
Si está interesado en buscar columnas calculadas para algunos de estos cálculos, hágamelo saber.
Ah, y una última cosa: no sé por qué alguna vez fregarías esta tabla y la volverías a llenar desde cero. ¿Cuántas de estas cosas van a cambiar? ¿Vas a alterar tu año fiscal constantemente? ¿Cambiar cómo quieres deletrear marzo? ¿Establecer su semana para comenzar el lunes una semana y el jueves la próxima? Esto realmente debería ser una tabla de compilación, y luego realiza pequeños ajustes (como actualizar filas individuales con información de vacaciones nueva / modificada).