简体   繁体   English

如何从日期 PostgreSQL 中提取月份中的星期几

[英]How to extract week of the month from date PostgreSQL

I would like to extract from a DATE type the number of the week within the month (NOT in the year).我想从 DATE 类型中提取一个月内的周数(不是一年)。

Example : today's date is 07/08/2018.示例:今天的日期是 07/08/2018。 I do NOT want 32 as a result (week in the year) but 2 , since today is the second week of August.结果我不想要32 (一年中的一周),而是2 ,因为今天是八月的第二周。

I would expect something like我希望像

SELECT EXTRACT($func FROM current_date);

with result: 2结果: 2

Where the first week of the month lasts from the first day of the month until the first Sunday (which may last from 1 to 7 days for the first week, eg for this month the first week lasts from Wednesday 1/8/2018 to Sunday 5/8/2018, 5 days).该月的第一周从该月的第一天持续到第一个星期日(第一周可能持续 1 到 7 天,例如本月的第一周从 2018 年 1 月 8 日星期三到星期日2018 年 5 月 8 日,5 天)。

Adding the calendar of this month to give more context to users:添加本月的日历以向用户提供更多上下文:

2018 年 8 月

Try extracting the day, and the dividing by 7:尝试提取日期,然后除以 7:

SELECT
    1 + FLOOR((EXTRACT(DAY FROM date_col) - 1) / 7) AS week_of_month
FROM your_table;

Demo演示

Another option would be to use TO_CHAR() , like this:另一种选择是使用TO_CHAR() ,如下所示:

SELECT TO_CHAR( CURRENT_DATE, 'W' )::integer

However this returns 1 , too, as it counts weeks FROM the 1st of the month... (Thus not from a Monday or a Sunday.)然而,这也返回1 ,因为它从一个月的第一天开始计算周数......(因此不是从星期一或星期日开始。)

Your week of the month is business data.一个月中的一周是业务数据。 Usually, business data belongs in tables.通常,业务数据属于表。

Data in tables is easier to audit than complicated date arithmetic.表中的数据比复杂的日期算法更容易审计。 (Witness all the incorrect date arithmetic in the answers here.) It's simpler to implement arbitrary logic with data in tables. (在此处的答案中见证所有不正确的日期算术。)使用表中的数据实现任意逻辑更简单。 It's easier to understand and troubleshoot, too.也更容易理解和排除故障。

create table my_calendar (
  cal_date date primary key, 
  cal_year integer not null 
    check (cal_year = extract (year from cal_date)),
  cal_month integer not null 
    check (cal_month = extract (month from cal_date)),
  cal_week integer not null 
    check (cal_week between 1 and 5)
);

I included the year and month (cal_year and cal_month), because that's the next thing everyone seems to need.我包括了年和​​月(cal_year 和 cal_month),因为这是每个人似乎都需要的下一个东西。

Some starter data.一些入门数据。

insert into my_calendar values
('2018-08-01',  2018, 8, 1),
('2018-08-02',  2018, 8, 1),
('2018-08-03',  2018, 8, 1),
('2018-08-04',  2018, 8, 1),
('2018-08-05',  2018, 8, 1),
('2018-08-06',  2018, 8, 2),
('2018-08-07',  2018, 8, 2),
('2018-08-08',  2018, 8, 2),
('2018-08-09',  2018, 8, 2),
('2018-08-10',  2018, 8, 2),
('2018-08-11',  2018, 8, 2),
('2018-08-12',  2018, 8, 2),
('2018-08-13',  2018, 8, 3),
('2018-08-14',  2018, 8, 3),
('2018-08-15',  2018, 8, 3);

And a query to verify the logic.以及验证逻辑的查询。

select cal_date
from my_calendar
where cal_year = 2018 
  and cal_month = 8 
  and cal_week = 1
order by cal_date;

cal_date
date
--
2018-08-01
2018-08-02
2018-08-03
2018-08-04
2018-08-05

Carefully control privileges in this kind of table.小心控制这种表中的权限。 You probably want everybody to be able to read from it, but almost nobody should be able to write to it.您可能希望每个人都能够读取它,但几乎没有人应该能够写入它。

Old question, and found many poorly formatted & explained or wrong answers.老问题,发现许多格式和解释不当或错误的答案。 For whoever comes next, here's the right answer:对于下一个来的人,这是正确的答案:

SELECT CEILING((DATE_PART( 'day', CURRENT_DATE ) - DATE_PART( 'dow', CURRENT_DATE )) / 7) +1; SELECT CEILING((DATE_PART('day', CURRENT_DATE) - DATE_PART('dow', CURRENT_DATE)) / 7) +1;

Simply: Find the prior Sunday, divide by 7 and (optionally) add 1.很简单:找到前一个星期日,除以 7 并(可选)加 1。

Counterintuitively it'll calc.违反直觉,它会计算。 a negative value for the first fractional (sometimes full) week, but since it's fractional it'll round up (CEILING) to a zero.第一个小数周(有时是完整周)的负值,但由于它是小数,它会四舍五入(CEILING)为零。 The final "+1" bring it to integers 1 through 5 instead of -0 through 4.最后的“+1”将其变为整数 1 到 5 而不是 -0 到 4。

See dbfiddle.uk: https://dbfiddle.uk/?rdbms=postgres_9.4&fiddle=f20ec2a530f0b33c673f669d07717e4e请参阅dbfiddle.uk:https://dbfiddle.uk/?rdbms=postgres_9.4&fiddle=f20ec2a530f0b33c673f669d07717e4e

WITH _dt (dt, i ) AS (SELECT * FROM 
     ( SELECT '2022-01-01'::DATE + i AS dt , i 
        FROM  (SELECT generate_series( 0, 365, 1 ) i ) i ( i) 
      ) dt
) 
SELECT DISTINCT
    dt
    ,                                     DATE_PART( 'month', dt ) AS month
    ,                                     DATE_PART( 'dow' , dt )  AS day_of_week
    ,CEILING((DATE_PART( 'day', dt.dt ) - DATE_PART( 'dow', dt.dt )) / 7) +1  AS week_of_month
    ,ROUND(((DATE_PART( 'day', dt.dt ) - DATE_PART( 'dow', dt.dt )) / 7)::NUMERIC, 2 ) AS raw_wom
    ,DATE_PART( 'year', dt ) AS year
    FROM _dt dt   
    ORDER BY year, month, week_of_month, day_of_week
    

I've tested this & it appears to work, weeks 0 - 5:我已经对此进行了测试,它似乎可以工作,第 0 - 5 周:

SELECT CEILING((DATE_PART( 'day', CURRENT_DATE ) - DATE_PART( 'dow', CURRENT_DATE )) / 7); SELECT CEILING((DATE_PART('day', CURRENT_DATE) - DATE_PART('dow', CURRENT_DATE)) / 7);

Simply: Find the prior Sunday, divide by 7.很简单:找到前一个星期日,除以 7。

However for months where the first week starts on Sunday (a full week), an adjustment needs to be made so its ranking behavior (0 - 5) is the same as other months (see flag "is_1st_week_7") .然而,对于第一周从星期日开始的月份(整周),需要进行调整,使其排名行为(0 - 5)与其他月份相同(参见标志“is_1st_week_7”) ** **

Note that in raw form it calculates (somewhat counterintuitively) a negative value for the first week, but it'll round up (CEILING) to a zero.请注意,在原始形式中,它会计算(有点违反直觉)第一周的负值,但它会四舍五入(CEILING)为零。

** (my prior reply had this functional bug) ** (我之前的回复有这个功能错误)

at dbfiddle.uk: https://dbfiddle.uk/?rdbms=postgres_10&fiddle=e70251943588251fb1883fa6fa44a2dd dbfiddle.uk:https://dbfiddle.uk/?rdbms=postgres_10&fiddle=e70251943588251fb1883fa6fa44a2dd

WITH _dt (dt, i ) AS (SELECT *
FROM  ( SELECT '2023-01-01'::DATE + i AS dt , i FROM  (SELECT generate_series( 0, 365, 1 ) i ) i ( i) ) dt
) 
SELECT
*
,        ROUND((((dom) - dow) / 7)::NUMERIC, 2 )  - is_1st_week_7 AS raw_wom /** raw week of month **/
,CEILING(ROUND((((dom) - dow) / 7)::NUMERIC, 2 )) - is_1st_week_7 AS wom /** week of month **/
FROM
(
SELECT DISTINCT
 -- full first weeks start the month off with 1 instead of 0, so need to decrement by 1 (above)
 (DATE_PART( 'dow'  , (dt.dt - ((DATE_PART( 'day', dt.dt )::INT)-1)))=0)::INT AS is_1st_week_7
,dt.dt                               /* the date    */
,DATE_PART( 'month', dt )    AS mo   /* month       */  
,DATE_PART( 'day'  , dt.dt ) AS dom  /* day of month*/  
,DATE_PART( 'dow'  , dt )    AS dow  /* day of week */  
,DATE_PART( 'year' , dt )    AS yr   /* year        */ 
FROM _dt dt
) dt
ORDER BY dt 
;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM