【问题标题】:Get Start and End Date from Week Number SQL Server从周数 SQL Server 获取开始和结束日期
【发布时间】:2022-01-07 00:41:48
【问题描述】:

我们将WeekNoYear 输入到以下格式的表格中:

YYYY-ww     (2021-48)

我想要从给定周开始的开始日期和结束日期,没有年份,格式如上。

如何从 SQL 查询或过程中获取?

谢谢,

【问题讨论】:

    标签: sql sql-server tsql week-number


    【解决方案1】:

    (一个额外的答案,因为这个方法有点不同。)

    此方法是关于使用包含日期和日期相关内容的计数表。

    那么这样的日历表就可以用来查询年份和星期了。

    例如

    创建日历表

    create table REF_Calendar (
     cal_date date primary key not null,
     cal_year smallint not null,
     cal_month tinyint not null,
     cal_day tinyint not null,
     cal_dayofyear smallint not null,
     cal_quarter tinyint not null,
     cal_weekday_abbrev char(3) not null,
     cal_iso_week tinyint,
     cal_df7_week tinyint not null,
     cal_df7_weekday tinyint not null,
     cal_df1_week tinyint,
     cal_df1_weekday tinyint,
     cal_mooncycle tinyint,
     index idx_Calendar_year_week unique (cal_year, cal_df7_week, cal_df7_weekday)
    );
    

    填写日历

    SET DATEFIRST 7; -- 7: week starts on sunday
    
    declare @startdate date = '2020-01-01';
    declare @enddate   date = '2023-01-01';
    
    with rcte_calendar as (
      select @startdate as cal_date
      union all
      select dateadd(day, 1, cal_date)
      from rcte_calendar
      where cal_date < @enddate
    )
    insert into ref_calendar 
    (cal_date, cal_year, cal_month, cal_day, cal_dayofyear, cal_quarter, cal_weekday_abbrev, 
     cal_df7_week, cal_df7_weekday, cal_mooncycle)
    select
      cal_date
    , datepart(year, cal_date) as cal_year
    , datepart(month, cal_date) as cal_month
    , datepart(day, cal_date) as cal_day
    , datepart(dayofyear, cal_date) as cal_dayofyear
    , datepart(quarter, cal_date) as cal_quarter
    , lower(left(datename(weekday, cal_date), 3)) as cal_weekday_abbrev
    , datepart(week, cal_date) as cal_df7_week
    , datepart(weekday, cal_date) as cal_df7_weekday
    , abs(1-abs(round(((14-1.0*CONVERT(int, CONVERT(nvarchar(2), cal_date, 131)))/14)
     , 1, 1)))*100 as cal_mooncycle
    from rcte_calendar cte
    where not exists (
            select 1 
            from ref_calendar cal
            where cal.cal_date = cte.cal_date
          )
    option (maxrecursion 0);
    

    更新额外的

    SET DATEFIRST 1; -- 1: week starts on monday
    update ref_calendar
    set cal_df1_week = datepart(week, cal_date)
      , cal_df1_weekday = datepart(weekday, cal_date)
      , cal_iso_week = datepart(iso_week, cal_date)
    where cal_df1_week is null
       or cal_df1_weekday is null
       or cal_iso_week is null;
    

    查看外观

    select top 9 * 
    from ref_calendar 
    where cal_mooncycle = 100
      and cal_date >= getdate()
      and cal_year <= 1 + datepart(year, current_timestamp)
    order by cal_date asc;
    
    cal_date cal_year cal_month cal_day cal_dayofyear cal_quarter cal_weekday_abbrev cal_iso_week cal_df7_week cal_df7_weekday cal_df1_week cal_df1_weekday cal_mooncycle
    2021-12-17 2021 12 17 351 4 fri 50 51 6 51 5 100
    2021-12-18 2021 12 18 352 4 sat 50 51 7 51 6 100
    2021-12-19 2021 12 19 353 4 sun 50 52 1 51 7 100
    2022-01-16 2022 1 16 16 1 sun 2 4 1 3 7 100
    2022-01-17 2022 1 17 17 1 mon 3 4 2 4 1 100
    2022-01-18 2022 1 18 18 1 tue 3 4 3 4 2 100
    2022-02-14 2022 2 14 45 1 mon 7 8 2 8 1 100
    2022-02-15 2022 2 15 46 1 tue 7 8 3 8 2 100
    2022-02-16 2022 2 16 47 1 wed 7 8 4 8 3 100

    测试将日历应用于表格

    DECLARE @Test TABLE (col char(7)); 
    INSERT INTO @Test VALUES 
      ('2021-01'), ('2021-02')
    , ('2021-48')
    , ('2022-01'), ('2022-02')
    ;
    
    select *
    from @test t
    outer apply (
      select 
        min(cal_date) as startofweek
      , max(cal_date) as endofweek
      from (values (try_cast(left(col, 4) as int),
                abs(try_cast(right(col, 2) as int))) 
      ) as val(yr, ww)
      join ref_calendar as cal
        on ((cal_year = yr and cal_df7_week = ww)
            or (ww = 1  and cal_year = yr-1 and cal_df7_week = 53) 
            or (ww = 53 and cal_year = yr+1 and cal_df7_week = 1))
    ) cal
    
    col startofweek endofweek
    2021-01 2020-12-27 2021-01-02
    2021-02 2021-01-03 2021-01-09
    2021-48 2021-11-21 2021-11-27
    2022-01 2021-12-26 2022-01-01
    2022-02 2022-01-02 2022-01-08

    dbfiddle here

    上的演示

    【讨论】:

    • 感谢一百万!
    【解决方案2】:

    这里有两种方法可以计算查询中年-周字符串的开始和结束日期。

    1) 没有 UDF

    SET DATEFIRST 7; -- 7: Sunday has weekday 1
    DECLARE @Test TABLE ([YEAR-WW] char(7)); 
    INSERT INTO @Test VALUES 
      ('2020-53'), ('2021-01'), ('2021-02')
    , ('2021-48')
    , ('2021-53'), ('2022-01');
    
    SELECT [YEAR-WW]
    , [FirstDayOfWeek] = CAST(DATEADD(day, 1-DATEPART(weekday, DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01')), DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01')) AS DATE)
    , [LastDayOfWeek]  = CAST(DATEADD(day, 6, DATEADD(day, 1-DATEPART(weekday, DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01')), DATEADD(week, ABS(RIGHT([YEAR-WW],2))-1, LEFT([YEAR-WW],5)+'01-01'))) AS DATE)
    FROM @Test
    ORDER BY 1;
    
    YEAR-WW FirstDayOfWeek LastDayOfWeek
    2020-53 2020-12-27 2021-01-02
    2021-01 2020-12-27 2021-01-02
    2021-02 2021-01-03 2021-01-09
    2021-48 2021-11-21 2021-11-27
    2021-53 2021-12-26 2022-01-01
    2022-01 2021-12-26 2022-01-01

    2) 使用此 UDF

    CREATE FUNCTION dbo.GetDateFromYearWeek (
     @YearWeek VARCHAR(7) = '000101', -- default
     @WeekDay INT = 1, -- default
     @FirstWeekDayName VARCHAR(9) = 'monday' -- default
    ) RETURNS DATE
    BEGIN
     
     IF @YearWeek = '000101'
       SET @YearWeek = CONCAT(DATEPART(year, GETDATE()), '-', DATEPART(week, GETDATE())); 
     
     IF @YearWeek NOT LIKE '[0-9][0-9][0-9][0-9]%[0-9-][0-9]'
       RETURN NULL;
     
     IF @WeekDay < 1 OR @WeekDay > 7
       RETURN NULL;
     
     DECLARE @FirstWeekDay INT = CHARINDEX(LOWER(LEFT(@FirstWeekDayName,3)), '   montuewedthufrisatsun')/3;
     IF @FirstWeekDay = 0 -- not found in string
       SET @FirstWeekDay = @@DATEFIRST;
     
     DECLARE @Year INT = TRY_CAST(LEFT(@YearWeek, 4) AS INT);
     DECLARE @Week INT = ABS(TRY_CAST(RIGHT(@YearWeek, 2) AS INT));
     
     DECLARE @Date DATE = TRY_CAST(CONCAT(@Year,'-01-01') AS DATE);
     SET @Date = DATEADD(week, @Week-1, @Date);
     
     DECLARE @DowDiff INT = 0-( (@@DATEFIRST+6-(@FirstWeekday%7))%7 + DATEPART(weekday,@Date) )%7;
     
     SET @Date = DATEADD(day, @DowDiff, @Date);
     SET @Date = DATEADD(day, @WeekDay-1, @Date);
    
     RETURN @Date;
    END;
    
    CREATE FUNCTION dbo.GetDateFromYearWeekSundayFirst (
     @YearWeek VARCHAR(7) = '000101',
     @WeekDay INT = 1
    ) RETURNS DATE
    BEGIN
      RETURN dbo.GetDateFromYearWeek(@YearWeek, @Weekday, 'sunday');
    END;
    

    使用示例

    DECLARE @Test TABLE ([YEAR-WW] char(7)); 
    INSERT INTO @Test VALUES 
      ('2020-53'), ('2021-01'), ('2021-02')
    , ('2021-48')
    , ('2021-53'), ('2022-01'), ('2022-02')
    ;
    
    SELECT [YEAR-WW]
    , [FirstOfWeek_Mon] = dbo.GetDateFromYearWeek([YEAR-WW], 1, 'mon')
    , [LastOfWeek_Mon]  = dbo.GetDateFromYearWeek([YEAR-WW], 7, 'Monday')
    , [FirstOfWeek_Sun] = dbo.GetDateFromYearWeek([YEAR-WW], 1, 'SUNDAY')
    , [LastOfWeek_Sun]  = dbo.GetDateFromYearWeekSundayFirst([YEAR-WW], 7)
    FROM @Test
    ORDER BY 1;
    
    YEAR-WW FirstOfWeek_Mon LastOfWeek_Mon FirstOfWeek_Sun LastOfWeek_Sun
    2020-53 2020-12-28 2021-01-03 2020-12-27 2021-01-02
    2021-01 2020-12-28 2021-01-03 2020-12-27 2021-01-02
    2021-02 2021-01-04 2021-01-10 2021-01-03 2021-01-09
    2021-48 2021-11-22 2021-11-28 2021-11-21 2021-11-27
    2021-53 2021-12-27 2022-01-02 2021-12-26 2022-01-01
    2022-01 2021-12-27 2022-01-02 2021-12-26 2022-01-01
    2022-02 2022-01-03 2022-01-09 2022-01-02 2022-01-08

    dbfiddle here

    上的演示

    【讨论】:

    • 我还想在这个答案中补充一点,如果你真的经常需要这个,我建议你使用一个明确的日历表。就像在这个答案中:stackoverflow.com/questions/12597143/… 或第二个回答者:stackoverflow.com/questions/32474236/…
    • @MisterT 我已经玩过了,并为这个问题添加了一个额外的答案。但是您会注意到第 1 周和第 53 周是查找的麻烦制造者。
    • 是的,你是对的 :-) 我从来没有这样看。 (?!)对我来说,日历表中还有一个“正常周”。而且我可能只会在 2021 年的第一周交付 - 3 天......在我们的应用程序中,2021 年的第一周开始于 4.1。我必须记住这一点。
    【解决方案3】:

    这个怎么样?

    declare @dateText varchar(7);
    declare @week int;
    declare @year int;
    
    set datefirst 1;              -- Set first day of week to Monday
    set @dateText = '2021-48';
    set @year = SUBSTRING(@dateText, 1, 4)
    set @week = SUBSTRING(@dateText, 6, 2)
    
    select DATEADD(week, @week, DATEADD(year, @year-1900, 0)) - 4 -
         DATEPART(dw, DATEADD(week, @week, DATEADD(year, @year-1900, 0)) - 4) + 1
    
    Output
    -----------------
    2021-11-29 00:00:00.000
    

    或者你可以创建一个函数:

    CREATE FUNCTION dbo.GetStartOfWeek(@dateText varchar(7))
    RETURNS Datetime
    BEGIN
        
    declare @week int;
    declare @year int;
    declare @offset int;
    
    select
        @offset =  @@DATEFIRST - 1, 
        @year = SUBSTRING(@dateText, 1, 4),
        @week = SUBSTRING(@dateText, 6, 2)
    
     RETURN DATEADD(week, @week, DATEADD(year, @year-1900, 0)) - 4 -
            DATEPART(dw, DATEADD(week, @week, DATEADD(year, @year-1900, 0) + @offset) - 4) + 1
    END;
    

    您可以在 SQL 查询或存储过程中使用:

    select dbo.GetStartOfWeek('2021-48') as StartDate, DATEADD(d, 7, dbo.GetStartOfWeek('2021-48')) as EndDate
    

    【讨论】:

    • ..那一周的结束日期呢???
    • 您可以执行与上述类似的操作,可以是单独的函数,也可以在一周的开始时添加 7 天。
    • 添加结束日期(开始日期后7天),如果您想要结束日期的开始,您可以根据您的要求将其更改为6。这对你现在有用吗@Bhavin?
    • 是的,为开始日期和结束日期开发了 2 个函数。但正如我们所知,在函数中,我们不能使用 set datefirst 1。所以需要一个解决方案......
    • 你为什么不像上面那样在一周的第一天使用偏移量呢?