【问题标题】:T-SQL calculate the first monday of the first ISO week in a year and enumerate all weeks as from-to periodsT-SQL 计算一年中第一个 ISO 周的第一个星期一,并将所有周枚举为从到周期
【发布时间】:2014-06-20 13:49:24
【问题描述】:

我想计算给定年份第一个 ISO 8601 周的第一天(星期一),然后枚举给定年份的所有 ISO 8601 周,包括它们的数字。我想知道这是否可以比我迄今为止做得更好,也许使用内置函数 datepart(iso_week, getdate())?这是我的代码:

DECLARE @y as int = 2011

DECLARE @firstDayOfYear date = CAST(CAST(@y AS varchar(4)) + '-01-01' AS DATE)
--thursday before 1st Jan
DECLARE @Thursday date = DATEADD(day,
               3 - (DATEPART(dw, @firstDayOfYear) + @@DATEFIRST - 2) % 7,
               @firstDayOfYear) 
DECLARE @FirstDayOfIsoWeek date = DATEADD(day,
               - (DATEPART(dw, @firstDayOfYear) + @@DATEFIRST - 2) % 7,
               @firstDayOfYear)
if (@Thursday<@firstDayOfYear)
    SELECT @FirstDayOfIsoWeek  = DATEADD(d,7, @FirstDayOfIsoWeek) 

SELECT @FirstDayOfIsoWeek

我也在寻找一种方法来枚举从第一个星期一开始的所有 ISO 周,作为包含 StartDate、EndDate、Year、Month、ISOWeekNo 列的周期表。如果有人知道快速干净的解决方案,请提供帮助。

接受的解决方案:

我做了一些编辑,所以它可以按我的需要工作 - 以 Outlook 日历方式枚举一年中的周数:

CREATE FUNCTION [dbo].[FGetISOWeeks](@y int)
RETURNS 
@ISOWeeks TABLE (StartDate Date NOT NULL, EndDate Date NOT NULL, YearNo int not null, MonthNo int not null, WeekNo int not null)
AS
BEGIN
    DECLARE @weeknumbers as TABLE ( weeknum int not null primary key (weeknum)) -- helper table of week numbers
    declare @weeknum int = 1
    while (@weeknum <= 53)
    begin
      insert @weeknumbers values(@weeknum)
      set @weeknum = @weeknum + 1
    end
    DECLARE @firstDayOfYear date = CAST(CAST(@y AS varchar(4)) + '-01-01' AS DATE)  
    DECLARE @Thursday date = DATEADD(day,3 - (DATEPART(dw, @firstDayOfYear) + @@DATEFIRST - 2) % 7,@firstDayOfYear)  --thursday before 1st Jan
    DECLARE @FirstDayOfIsoWeek date = DATEADD(day, - (DATEPART(dw, @firstDayOfYear) + @@DATEFIRST - 2) % 7, @firstDayOfYear)
    if (@Thursday<@firstDayOfYear) SELECT @FirstDayOfIsoWeek  = DATEADD(d,7, @FirstDayOfIsoWeek) -- calculate first day of iso year
    declare @Monday0 date = DATEADD(d,-7, @FirstDayOfIsoWeek)
    INSERT INTO @ISOWeeks
    select DATEADD(WEEK, N.weeknum, @Monday0) as StartDate
          ,DATEADD(day, 7*N.weeknum+6, @Monday0) as EndDate, @y as YearNo
          ,DATEPART(month,  DATEADD(DAY, 7*N.weeknum+3, @Monday0)) as MonthNo
          ,DATEPART(ISO_WEEK,  DATEADD(WEEK, N.weeknum, @Monday0)) as WeekNo    
    from @weeknumbers N
    where DATEPART(year,  DATEADD(day, 7*N.weeknum+3, @Monday0)) = @y
    order by N.weeknum  
    RETURN 
END

【问题讨论】:

    标签: sql sql-server date iso8601


    【解决方案1】:

    我会创建一个值为 1 到 53 的表

    create table weeknumbers
    (
      weeknum int not null
      primary key (weeknum)
    )
    
    declare @weeknum int
    set @weeknum = 1
    
    while (@weeknum <= 53)
    begin
      insert weeknumbers values(@weeknum)
      set @weeknum = @weeknum + 1
    end
    

    那么这个存储过程应该会给你正确的结果

    create isoweeks(@y int) as
    begin
    
    DECLARE @firstDayOfYear date = CAST(CAST(@y AS varchar(4)) + '-01-01' AS DATE)
    --thursday before 1st Jan
    DECLARE @Thursday date = DATEADD(day,
                   3 - (DATEPART(dw, @firstDayOfYear) + @@DATEFIRST - 2) % 7,
                   @firstDayOfYear) 
    DECLARE @FirstDayOfIsoWeek date = DATEADD(day,
                   - (DATEPART(dw, @firstDayOfYear) + @@DATEFIRST - 2) % 7,
                   @firstDayOfYear)
    if (@Thursday<@firstDayOfYear)
        SELECT @FirstDayOfIsoWeek  = DATEADD(d,7, @FirstDayOfIsoWeek)
    
    declare @Monday0 date = DATEADD(d,-7, @FirstDayOfIsoWeek)
    
    -- SELECT @FirstDayOfIsoWeek
    
    select DATEADD(WEEK, N.weeknum, @Monday0) as StartDate
    , DATEADD(day, 7*N.weeknum+6, @Monday0) as EndDate
    , DATEADD(day, 7*N.weeknum+6, @Monday0) as EndDate
    , @y as Year
    ,   DATEPART(month,  DATEADD(WEEK, N.weeknum, @Monday0)) as Month
    ,   DATEPART(ISO_WEEK,  DATEADD(WEEK, N.weeknum, @Monday0)) as Month
    from dbo.weeknumbers N
    where DATEPART(year,  DATEADD(WEEK, N.weeknum, @Monday0)) = @y
    order by N.weeknum
    
    end
    

    不知道这个解决方案是否比其他发布的更好。可能更快或更慢,更容易理解与否。我想这取决于你的喜好。我使用您现有的代码作为存储过程的开头,但您应该调整代码以简化 Monday0(上一个 ISO 年的最后一个星期一)的计算

    【讨论】:

    • WHERE 条件只有一点问题,应该是:where DATEPART(year, DATEADD(day, 7*N.weeknum+3, @Monday0)) = @y, 反映星期四。你介意在你的答案中编辑它吗?谢谢,它是这样工作的。
    • 另外,您在输出中指定了两次 EndDate。在我的解决方案中实现您的答案时,我将 weeknumbers 表更改为 @weeknumbers 表变量。尽管进行了一些小修改,但它为我节省了一些工作,谢谢。
    • 请随意编辑。对小问题感到抱歉。也应该提到。拥有一个数字表通常对其他事情很有用。即,如果您的 numbers 表有 10K 行,只需将 where num
    • 我又发现了一个问题:月数不是 ISO 月数 - 即 2013-12-30 ... 2014-01-05 应该是 2014 年的第 1 个月,而不是 12 个月。
    【解决方案2】:

    除非我误解你所说的 ISO 周是什么意思,否则我认为下面的代码应该适合你。

    第一部分看起来确实很丑,但执行得很快。目标是找到一年中第一周的第一天。如果那不是星期一,那么第一天将在前一年,我们想去下周。

    从那里递归 CTE 简化了问题的第二部分。

    DECLARE 
        @year CHAR(4) = 1972,
        @YearDate DATE
    
    SELECT
         DATEADD(WEEK,DATEDIFF(WEEK,0,DATEADD(YEAR,DATEDIFF(YEAR,0,@year + '-01-01'),0)),0)
        ,DATEADD(WEEK,1 + DATEDIFF(WEEK,0,DATEADD(YEAR,DATEDIFF(YEAR,0,@year + '-01-01'),0)),0)
    
    IF(DATENAME(YEAR,DATEADD(WEEK,DATEDIFF(WEEK,0,DATEADD(YEAR,DATEDIFF(YEAR,0,@year + '-01-01'),0)),0)) = @year)
    BEGIN
    SELECT @YearDate = DATEADD(WEEK,DATEDIFF(WEEK,0,DATEADD(YEAR,DATEDIFF(YEAR,0,@year + '-01-01'),0)),0)
    END
    ELSE
    BEGIN
    SELECT @YearDate = DATEADD(WEEK,1 + DATEDIFF(WEEK,0,DATEADD(YEAR,DATEDIFF(YEAR,0,@year + '-01-01'),0)),0)
    END
    
    ;WITH DateCTE (StartDate, EndDate, YearNum, MnthName, WeekNumber) AS (
        SELECT
             @YearDate
            ,DATEADD(DAY,7,@YearDate)
            ,DATENAME(YEAR,@YearDate)
            ,DATENAME(MONTH,@YearDate)
            ,DATEDIFF(WEEK,DATEADD(WEEK,DATEDIFF(WEEK,0,@YearDate)-1,0),@YearDate)
    
        UNION ALL
    
        SELECT
             EndDate
            ,DATEADD(DAY,7,EndDate)
            ,DATENAME(YEAR,EndDate)
            ,DATENAME(MONTH,EndDate)
            ,DATEDIFF(WEEK,DATEADD(WEEK,DATEDIFF(WEEK,0,@YearDate)-1,0),EndDate)
        FROM DateCTE
        WHERE DATENAME(YEAR,EndDate) = @year
    )
    SELECT
    *
    FROM DateCTE
    

    【讨论】:

    • 谢谢,看起来很有希望,但仍然存在严重缺陷。 2014 年第一个 ISO 周从 2013 年 12 月 30 日开始。您的第一周从 2014 年 1 月 6 日开始。您输出中的第 52 周实际上是 2015 年 ISO 的第 1 周。您可以轻松检查:SELECT DATEPART(ISO_WEEK, '2014-12-29')
    • 我知道你是对的,我认为这是我不确定你希望它如何运作的部分。要修复,只需使用 IF 块中的第一条语句,而无需使用 IF 语句检查它。第一条语句返回正确的 2013-12-30 日期,如 IF 上方的 select 语句的第一部分所示。
    • 我已经尝试过了,但它并没有工作多年。我认为检查本周的星期四是否在所选年份内很重要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-07
    • 2014-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-29
    相关资源
    最近更新 更多