为此使用内联表值函数比使用标量函数好得多(一些细节over here)。
根据上面评论中显示的预期结果,我创建了一个生成任何给定日期一年的第一个星期一。如果您想要周日的东西,请在您的问题中提供示例数据/所需的结果:
CREATE FUNCTION dbo.GetFirstMonday
(
@date date
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH km(km) AS (SELECT CONVERT(date, '20180101')), -- known Monday
d AS (SELECT FirstOfYear = DATEFROMPARTS(YEAR(@date),1,1)),
w AS (SELECT FirstMonday = DATEADD(WEEK, DATEDIFF(WEEK, km, FirstOfYear)
+ CASE WHEN ((@@DATEFIRST + DATEPART(WEEKDAY, FirstOfYear))) IN (6,13)
THEN 1 ELSE 0 END, km) FROM d CROSS JOIN km)
SELECT FirstMonday FROM w
);
用法:
SET DATEFIRST 5;
DECLARE @src TABLE(InputDate date);
INSERT @src(InputDate) VALUES
('20160608'),('20170505'),('20180405'),
('20190303'),('20200903'),('20210706');
SELECT * FROM @src AS src
CROSS APPLY dbo.GetFirstMonday(src.InputDate);
结果(对于任何DATEFIRST 设置):
InputDate FirstMonday
---------- -----------
2016-06-08 2016-01-04
2017-05-05 2017-01-02
2018-04-05 2018-01-01
2019-03-03 2018-12-31
2020-09-03 2019-12-30
2021-07-06 2021-01-04
另一种思考方式是:如果一年中的第一个星期一距离一年的第一天超过 2 天(例如,一年开始于 ,则取第一个星期一之前一年中的第一天,最多可以提前 3 天。因此,此函数从 {first of year} - {3 days} => {first of year} + {3 days} 的 7 天范围内获取匹配工作日的 MIN() - 其中一天必须是你之后的星期一(如果你的规则不是三天,很容易改变):
CREATE FUNCTION dbo.GetFirstWeekday
(
@date date,
@DayName char(6)
)
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN (WITH n(n) AS (SELECT -3 UNION ALL SELECT n+1 FROM n WHERE n < 3),
d(d) AS (SELECT DATEADD(DAY, n, DATEFROMPARTS(YEAR(@date),1,1)) FROM n)
SELECT DayName = @DayName, FirstWeekday = MIN(d) FROM d
WHERE DATENAME(WEEKDAY, d) = @DayName
);
所以给定:
DECLARE @src TABLE(InputDate date);
INSERT @src(InputDate) VALUES
('20160108'),('20170505'),('20180405'),
('20190303'),('20200403'),('20210506');
SELECT * FROM @src AS src
CROSS APPLY dbo.GetFirstWeekday(src.InputDate, 'Monday');
结果:
InputDate FirstWeekday
---------- ------------
2016-06-08 2016-01-04
2017-05-05 2017-01-02
2018-04-05 2018-01-01
2019-03-03 2018-12-31
2020-09-03 2019-12-30
2021-07-06 2021-01-04
这也与@@DATEFIRST 没有任何联系,但它确实依赖于@@LANGUAGE 在英语领域。
但实际上,如果几年中的每一年只有一个星期日或星期一,并且您已经知道规则,为什么不创建一个表,一次定义这些规则,而不是想出古怪而明显灵活的语法呢?
CREATE TABLE dbo.FirstSundayMondayRules
(
TheYear int PRIMARY KEY,
FirstSunday date NOT NULL,
FirstMonday date NOT NULL
);
-- guessing at your Sunday rules here
INSERT dbo.FirstSundayMondayRules VALUES
(2016, '20160103', '20160104'),
(2019, '20181230', '20181231');
现在您可以创建一个更简单的函数:
CREATE FUNCTION dbo.GetFirstWeekday
(
@Date date,
@DayName char(6)
)
RETURNS TABLE WITH SCHEMABINDING
AS
RETURN (SELECT FirstWeekday = CASE @DayName
WHEN 'Sunday' THEN FirstSunday
WHEN 'Monday' THEN FirstMonday END
FROM dbo.FirstSundayMondayRules
WHERE TheYear = DATEPART(YEAR, @Date)
);
并且DATEFIRST 不再具有任何相关性,至少作为任何计算的一部分:
SET DATEFIRST 5;
DECLARE @src TABLE(InputDate date);
INSERT @src(InputDate) VALUES
('20160608'),('20190303');
SELECT 'Sunday', * FROM @src AS src
CROSS APPLY dbo.GetFirstWeekday(src.InputDate, 'Sunday');
SELECT 'Monday', * FROM @src AS src
CROSS APPLY dbo.GetFirstWeekday(src.InputDate, 'Monday');
结果:
(No column name) InputDate FirstWeekday
---------------- ---------- ------------
Sunday 2016-06-08 2016-01-03
Sunday 2019-03-03 2018-12-30
(No column name) InputDate FirstWeekday
---------------- ---------- ------------
Monday 2016-06-08 2016-01-04
Monday 2019-03-03 2018-12-31
或者更好的是,给自己一个Calendar table,你可以在列中硬编码FirstSundayOfYear 和FirstMondayOfYear。