获取星期数,星期几从星期日开始,例如Excel WEEKNUM

时间:2019-09-06 20:03:51

标签: postgresql calendar

在PostgreSQL(我的版本为9.6.6)中,最简单的方法是从星期日开始获取星期数?

DATE_PART('week',x)返回:

  

ISO 8601的星期编号,是一年中的第几周。根据定义,ISO周从星期一开始,一年的第一周包含该年的1月4日。换句话说,一年的第一个星期四在该年的第1周。 (doc

说我的查询就像:

WITH dates as (SELECT generate_series(timestamp '2014-01-01', 
                                      timestamp '2014-01-31', 
                                      interval  '1 day'
                                      )::date AS date
) 
SELECT 
    date, 
    TO_CHAR(date,'Day') AS dayname,
    DATE_PART('week',date) AS weekofyear
FROM dates

返回:

date        dayname   weekofyear
--------------------------------
2014-01-01  Wednesday   1
2014-01-02  Thursday    1
2014-01-03  Friday      1
2014-01-04  Saturday    1
2014-01-05  Sunday      1 <- I want this to be 2
2014-01-06  Monday      2
2014-01-07  Tuesday     2
2014-01-08  Wednesday   2

到目前为止,我已经尝试过:

SELECT 
    date, 
    TO_CHAR(date,'Day') AS dayname,
    DATE_PART('week',date) AS week_iso,
    DATE_PART('week',date + interval '1 day') AS week_alt
FROM dates

如果年份是从星期日开始的话,这是行不通的。

此外,我希望第1周包含该年的1月1日。因此,如果1月1日为星期六,我希望第1周为一天(而不是ISO风格的第53周)。此行为与Excel WEEKNUM function一致。

4 个答案:

答案 0 :(得分:1)

要获取一年中的第几周,从星期日开始的几周,我们需要知道从一年的第一天到目标日期之间有多少个星期日。

我改编了@Erwin Brandstetter的解决方案here。此解决方案计算包括一年中第一天在内的星期日,但不包括目标日期。

然后,因为我希望第一(部分)周成为第一周(而不是零),所以我需要添加1 ,除非一年的第一天是星期日(在这种情况下为已经第一周了。

WITH dates as (SELECT generate_series(timestamp '2014-01-01', 
                                      timestamp '2014-01-31', 
                                      interval  '1 day'
                                      )::date AS date
) 
SELECT 
    date, 
    TO_CHAR(date,'Day') AS dayname,
    DATE_PART('week',date) AS week_iso,
    ((date - DATE_TRUNC('year',date)::date) + DATE_PART('isodow', DATE_TRUNC('year',date)) )::int / 7 
       + CASE WHEN DATE_PART('isodow', DATE_TRUNC('year',date)) = 7 THEN 0 ELSE 1 END  
         AS week_sundays

FROM dates

返回

date        dayname   weekofyear   week_sundays
--------------------------------
2014-01-01  Wednesday   1   1
2014-01-02  Thursday    1   1
2014-01-03  Friday      1   1
2014-01-04  Saturday    1   1
2014-01-05  Sunday      1   2
2014-01-06  Monday      2   2
2014-01-07  Tuesday     2   2

显示从星期日开始的几年中如何工作:

2017-01-01  Sunday      52  1
2017-01-02  Monday      1   1
2017-01-03  Tuesday     1   1
2017-01-04  Wednesday   1   1
2017-01-05  Thursday    1   1
2017-01-06  Friday      1   1
2017-01-07  Saturday    1   1
2017-01-08  Sunday      1   2

答案 1 :(得分:0)

以下方法可能有效-考虑到以下两种情况进行了测试:

WITH dates as (SELECT generate_series(timestamp '2014-01-01', 
                                      timestamp '2014-01-10', 
                                      interval  '1 day'
                                      )::date AS date
               union
               SELECT generate_series(timestamp '2017-01-01', 
                                      timestamp '2017-01-10', 
                                      interval  '1 day'
                                      )::date AS date
) 
, alt as (
SELECT 
    date, 
    TO_CHAR(date,'Day') AS dayname,
    DATE_PART('week',date) AS week_iso,
    DATE_PART('week',date + interval '1 day') AS week_alt
FROM dates
    )
select date, dayname, 
week_iso, week_alt, case when week_alt <> week_iso 
                           then week_alt
                           else week_iso end as expected_week
from alt
order by date

输出:

date        dayname   week_iso   week_alt expected_week
2014-01-01  Wednesday   1   1   1
2014-01-02  Thursday    1   1   1
2014-01-03  Friday      1   1   1
2014-01-04  Saturday    1   1   1
2014-01-05  Sunday      1   2   2
2014-01-06  Monday      2   2   2
2014-01-07  Tuesday     2   2   2
....
2017-01-01  Sunday      52  1   1
2017-01-02  Monday      1   1   1
2017-01-03  Tuesday     1   1   1
2017-01-04  Wednesday   1   1   1
2017-01-05  Thursday    1   1   1
2017-01-06  Friday      1   1   1
2017-01-07  Saturday    1   1   1
2017-01-08  Sunday      1   2   2

答案 2 :(得分:0)

任务并不像它第一次出现时那样令人生畏。它主要需要在1月1日或之后找到第一个太阳。该日期成为第一周的最后一天。从那里开始,仅计算随后的几周。一个额外的问题。另一个重要的意义是,在每周定义中,每年始终有53周,而最后一周的最后一天是12月31日。以下为该周定义生成年度日历。

create or replace function non_standard_cal(year_in integer)
  returns table (week_number integer, first_day_of_week date, last_day_of_week date)
  language sql immutable leakproof strict rows 53
  as $$
with recursive cal as
     (select 1 wk, d1 start_of_week, ds end_of_week, de stop_date
        from (select d1+substring( '0654321' 
                             , extract(dow from d1)::integer+1
                             , 1)::integer ds
                  , d1, de
               from ( select make_date (year_in, 1,1)      d1
                           , make_date (year_in+1, 1,1) -1 de
                    ) a
             ) b
      union all 
      select wk+1, end_of_week+1,  case when end_of_week+7 > stop_date
                                        then stop_date  
                                        else end_of_week+7
                                   end
             , stop_date
        from cal
       where wk < 53
     )                                         
select wk, start_of_week, end_of_week from cal; 
$$ ;  

作为一般规则,我避免使用幻数,但有时它们很有用;在这种情况下。在魔术数字(实际上是字符串)中,“ 0654321”中的每个数字表示当通过标准日期编号系统(0-6表示星期日)进行索引时,在1月1日当天或之后到达第一个星期一所需的天数。结果是星期一是第一周的最后一天。生成递归CTE的第一行。剩余的行只为每个星期添加适当的天数,直到生成53周为止。
以下显示了确保每周的每一天轮到1月1日(是的,有些日子重复)所需的年数。逐年运行以验证其日历。

do $$
declare 
  cal record;
  yr_cal cursor (yr integer) for
        select * from non_standard_cal(2000+yr) limit 1;

begin 
  for yr in 18 .. 26
  loop 
       open yr_cal(yr);
       fetch yr_cal into cal;
       raise notice 'For Year: %, week: %, first_day: %, Last_day: %, First day is: %'
                    , 2000+yr
                    ,cal.week_number
                    ,cal.first_day_of_week
                    ,cal.last_day_of_week
                    ,to_char(cal.first_day_of_week, 'Day');
       close yr_cal;
  end loop;
 end; $$; 

答案 3 :(得分:0)

此查询完美地将星期一替换为星期日作为一周的开始。

查询

 SELECT CASE WHEN EXTRACT(day from '2014-01-05'::date)=4 AND
 EXTRACT(month from '2014-01-05'::date)=1 THEN date_part('week',
 '2014-01-05'::date) ELSE date_part('week', '2014-01-05'::date + 1)
 END;

输出

  date_part
 -----------
          2 
(1 row)