【问题标题】:SQL: get first day of the current year week basedSQL:获取基于当前年份周的第一天
【发布时间】:2021-12-28 12:07:46
【问题描述】:

许多人建议如何获取当年的第一个日期(基于月份) 例如:

SELECT DATEADD(yy, DATEDIFF(yy, 0, GETDATE()), 0)

这将导致:

2021-01-01 00:00:00.000

应该是 2021-01-04。 (第一周的星期一(美国的星期日))

我需要的是类似的,但基于周。例如,第一周可能从 12 月 31 日开始,因此该函数应返回 12 月 31 日,而不是 1 月 1 日。

有什么想法吗?

【问题讨论】:

  • 什么定义了“第一周”?例如,在ISO terms 中,一年的第一周直到该周有星期四/包含 1 月 4 日。
  • 获得本年度第一个日期的更好、更直观(但不一定更快)的方法是SELECT DATEFROMPARTS(YEAR(GETDATE()), 1, 1);@987654322 对您来说可能更有意义@ 你可以根据你的规则以你想要的方式明确定义一年中第一周的第一天,而不管 SQL Server 的 WEEKISO_WEEK 日期部分说什么。
  • 您能否提供几年的示例(例如,从 5 年前到 5 年后),您可以在其中明确告诉我们您对每一年“第一周”开始的预期?
  • 我今年真的需要这个并且包装在一个标量函数中。函数的问题 - 不支持 FIRSTDAY 选项,但我也无法为参数设置默认值(只有存储过程允许),所以我不想更改所有引用,以防我添加多年的参数(获取日期其他年份)。残酷的方法是检查日名是否为星期日
  • 等年(无列名) 2016 2016-01-04 2017 2017-01-02 2018 2018-01-01 2019 2018-12-31 2020 2019-12-30 2021 2021-01-04

标签: sql sql-server


【解决方案1】:

为此使用内联表值函数比使用标量函数好得多(一些细节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 在英语领域。

  • 示例db<>fiddle - 周日也有,但我猜你的规则,因为你没有提供任何示例数据/所需的结果。

但实际上,如果几年中的每一年只有一个星期日或星期一,并且您已经知道规则,为什么不创建一个表,一次定义这些规则,而不是想出古怪而明显灵活的语法呢?

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,你可以在列中硬编码FirstSundayOfYearFirstMondayOfYear

【讨论】:

    【解决方案2】:

    所以我想出了一个解决方案,但可能有一个更优雅的解决方案

    
    -- =============================================
    -- Author:      PLANSIS
    -- Create date: 2020-11-25
    -- Description: first day of the current year week based (monday of the first week)
    -- =============================================
      CREATE OR ALTER      FUNCTION [dbo].[A_FN_TI_FirstDayCurrentYearWeek] 
    (
        @date date  = null
    )
    RETURNS  datetime
    AS
    BEGIN
        set @date = coalesce( @date , getdate() ) 
        
        DECLARE @ResultVar datetime 
        DECLARE @wk int  
        DECLARE @yr int  
    
        SET @yr = year(@date)
        SET @wk = 1
    
        SELECT @ResultVar  =  dateadd (week, @wk, dateadd (year, @yr-1900, 0)) - 4 -
             datepart(dw, dateadd (week, @wk, dateadd (year, @yr-1900, 0)) - 4) + 2 -- +2 for europe, +1 for US
        -- Return the result of the function
        RETURN @ResultVar 
    END
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-29
      • 2020-09-15
      • 2011-02-25
      • 1970-01-01
      相关资源
      最近更新 更多