CẬP NHẬT : để biết ví dụ chung hơn về việc tạo và điền vào bảng lịch hoặc thứ nguyên, hãy xem mẹo này:
Đối với câu hỏi cụ thể trong tay, đây là nỗ lực của tôi. Tôi sẽ cập nhật điều này với phép thuật mà bạn sử dụng để xác định những thứ như Frypt_MonthNumber và Frypt_MonthName, bởi vì ngay bây giờ chúng là phần không trực quan duy nhất trong câu hỏi của bạn và đó là thông tin hữu hình duy nhất bạn thực sự không bao gồm.
Cách "tốt nhất" (đọc: hiệu quả nhất) để điền vào bảng lịch, IMHO, là sử dụng một tập hợp, thay vì một vòng lặp. Và bạn có thể tạo tập hợp này mà không cần chôn logic vào các hàm do người dùng xác định, điều này thực sự không mang lại cho bạn bất cứ điều gì ngoài việc đóng gói - nếu không thì đó chỉ là một đối tượng khác để duy trì. Tôi nói về điều này chi tiết hơn rất nhiều trong loạt blog này:
Nếu bạn muốn tiếp tục sử dụng chức năng của mình, hãy đảm bảo rằng đó không phải là hàm có giá trị bảng nhiều câu lệnh; điều đó sẽ không hiệu quả chút nào Bạn muốn đảm bảo rằng nó là nội tuyến (ví dụ: có một RETURN
tuyên bố duy nhất và không có @table
tuyên bố rõ ràng ), có WITH SCHEMABINDING
và không sử dụng CTE đệ quy. Ngoài chức năng, đây là cách tôi sẽ làm:
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.
Với bảng đã có, bạn có thể thực hiện một lần chèn dựa trên tập hợp bao nhiêu năm dữ liệu bạn muốn từ bất kỳ ngày bắt đầu nào bạn chọn. Chỉ cần xác định ngày bắt đầu và số năm. Tôi sử dụng kỹ thuật "xếp chồng CTE" để tránh dư thừa và chỉ thực hiện một loạt các phép tính một lần; các cột đầu ra từ các CTE trước đó sau đó được sử dụng trong các tính toán tiếp theo sau này.
-- 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;
Bây giờ, bạn vẫn còn các cột "ngày nghỉ" và "ngày làm việc" để giải quyết - việc này sẽ hơi phức tạp hơn một chút, nhưng bạn cần cập nhật ba cột đó với bất kỳ ngày lễ nào xuất hiện trong phạm vi ngày của bạn. Những thứ như ngày Giáng sinh thực sự dễ dàng:
UPDATE dbo.DateDimension
SET IsWorkday = 0, IsHoliday = 1, HolidayName = 'Christmas'
WHERE Calendar_Month_Number = 12 AND Calendar_DayOfMonth = 25;
Những thứ như Lễ Phục Sinh trở nên phức tạp hơn nhiều - tôi đã viết một số ý tưởng ở đây nhiều năm trước .
Và tất nhiên, công ty của bạn không phải là ngày làm việc hoàn toàn không liên quan gì đến ngày lễ, v.v. phải được bạn cập nhật trực tiếp - SQL Server sẽ không có cách tích hợp để biết lịch của công ty bạn.
Bây giờ, tôi cố tình tránh xa việc tính toán bất kỳ cột nào trong số này, bởi vì bạn đã nói điều gì đó giống như người dùng cuối có previously preferred fields they can drag and drop
- Tôi không chắc người dùng cuối có thực sự biết hoặc quan tâm nếu nguồn của cột là cột thực, cột được tính không hoặc xuất phát từ chế độ xem, truy vấn hoặc chức năng ...
Giả sử bạn làm muốn nhìn vào tính toán một số các cột này để giảm bớt về bảo trì của bạn (và tồn tại chúng sang bộ nhớ trả tiền cho tốc độ truy vấn), bạn có thể nhìn vào đó. Tuy nhiên, giống như một cảnh báo, một số trong các cột này không thể được định nghĩa là được tính toán và tồn tại bởi vì chúng không mang tính quyết định. Đây là một ví dụ và cách khắc phục nó.
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS DATEPART(WEEKDAY, [date]) PERSISTED
);
Các kết quả:
Msg 4936, Cấp 16, Trạng thái 1, Dòng 130
Cột được tính 'DayOfWeek_Number' trong bảng 'Kiểm tra' không thể được duy trì vì cột không xác định.
Lý do điều này không thể được duy trì là vì nhiều chức năng liên quan đến ngày dựa vào cài đặt phiên của người dùng, như thế DATEFIRST
. Máy chủ SQL không thể duy trì cột trên vì DATEPART(WEEKDAY
sẽ cho các kết quả khác nhau - được cung cấp cùng một dữ liệu - cho hai người dùng khác nhau có các DATEFIRST
cài đặt khác nhau .
Sau đó, bạn có thể trở nên thông minh, và nói, tốt, tôi có thể đặt nó là số ngày, modulo 7, bù vào một ngày nào đó tôi biết là thứ bảy (giả sử '2000-01-01'
). Vì vậy, bạn hãy thử:
CREATE TABLE dbo.Test
(
[date] DATE PRIMARY KEY,
DayOfWeek_Number AS
COALESCE(NULLIF(DATEDIFF(DAY,'20000101',[date])%7,0),7) PERSISTED
);
Nhưng, cùng một lỗi.
Thay vì sử dụng một chuyển đổi ngầm định từ một chuỗi ký tự đại diện cho thời gian ngày theo định dạng rõ ràng (với chúng tôi, nhưng không phải là SQL Server), chúng tôi có thể sử dụng số ngày giữa "ngày không" (1900-01-01) và ngày đó chúng ta biết là thứ bảy (2000-01-01). Nếu chúng tôi sử dụng một số nguyên ở đây để biểu thị sự khác biệt theo ngày, SQL Server không thể khiếu nại, vì không có cách nào để hiểu sai số đó. Vì vậy, điều này hoạt động:
-- 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
);
Sự thành công!
Nếu bạn quan tâm đến việc theo đuổi các cột được tính toán cho một số tính toán này, hãy cho tôi biết.
Ồ, và một điều cuối cùng: Tôi không biết tại sao bạn lại chùi xuống cái bàn này và điền lại từ đầu. Có bao nhiêu trong số những điều này sẽ thay đổi? Bạn sẽ thay đổi năm tài chính của bạn liên tục? Thay đổi cách bạn muốn đánh vần tháng ba? Đặt tuần của bạn để bắt đầu vào thứ Hai một tuần và thứ năm tuần sau? Đây thực sự nên là một bảng xây dựng một lần, và sau đó bạn thực hiện các điều chỉnh nhỏ (như cập nhật các hàng riêng lẻ với thông tin kỳ nghỉ mới / thay đổi).