生成一年中特定一周的日期

时间:2013-05-17 08:03:06

标签: sql postgresql date plpgsql week-number

我对PostgreSQL很新,最近在我的项目中,我遇到了检索一年中特定一周的所有记录的问题。主要问题当然是边界周(1和52/53)。

为此,我写了一个函数:

CREATE OR REPLACE FUNCTION week_dates(_week integer, _year integer) RETURNS date[] AS $$
DECLARE
    result date[];
BEGIN
    WITH RECURSIVE dates(wdate) AS (
        SELECT MAX(date) AS wdate FROM time WHERE woy = _week AND year = _year AND dow > 3
        UNION ALL
        SELECT wdate-1 AS wdate FROM dates WHERE EXTRACT(WEEK from (wdate-1)) = _week 
    ),
    dates2(wdate) AS (
        SELECT MAX(wdate) AS wdate FROM dates 
        UNION ALL
        SELECT wdate+1 AS wdate FROM dates WHERE EXTRACT(WEEK from (wdate+1)) = _week 
    ),
    sorted AS ((SELECT * FROM dates) UNION (SELECT * FROM dates2) ORDER BY wdate ASC)
    -- sorted AS (SELECT wdate FROM dates ORDER BY wdate ASC)
    SELECT array_agg(wdate) INTO result FROM sorted;
    -- SELECT wdate FROM sorted;
    RETURN result;
END;
$$ LANGUAGE plpgsql;

它的用法是,例如:

SELECT * FROM "some_report_cache_table" WHERE "date" = ANY(week_dates(1, 2013));

是否有更好/更快/更简单的解决方案(可能是一些内置功能)?

我正在使用PostgreSQL 9.2,按周我的意思是ISO周年(星期一开始)

2 个答案:

答案 0 :(得分:4)

更简单,但是:

CREATE OR REPLACE FUNCTION week_dates(_week integer, _year integer)
  RETURNS SETOF date AS
$func$
SELECT date_trunc('week', ($2::text || '-1-4')::timestamp)::date
       + 7 * ($1 - 1)  -- fix off-by-one
       + generate_series (0,6)
$func$ LANGUAGE sql;

重点是您可以将integer添加到date以增加PostgreSQL中的天数。
date_trunc()会返回timestamp,因此我们需要再次投放date

我从1月4日开始,因为(quoting the manual here):

  

根据定义(ISO 8601),周数从周一和第一周开始   一年中包含当年的1月4日。

该功能乐意接受不可能的周,如-155,并返回一个有些合理的结果。为了禁止这种情况,我会将它设为plpgsql函数并检查输入值。

这会返回 set 而不是数组。将它变成一个数组(SELECT ARRAY(SELECT ...)是微不足道的。但这应该更快。您的查询可能如下所示:

SELECT *
FROM   some_report_cache_table
JOIN   week_dates(1, 2013) w("date") USING ("date")

除此之外:您不应将date用作列名,因为它是reserved word in SQL

答案 1 :(得分:3)

我认为这更简单。它将返回给定年份中给定周的所有日子。

create or replace function week_dates(_week integer, _year integer)
returns setof date as $$

    with first_day_of_first_week as (
        select distinct date_trunc('week', d)::date
        from generate_series(
            to_date(_year::text, 'YYYY'),
            to_date(_year::text, 'YYYY') + 3,
            '1 day'
        ) s(d)
        where extract(week from d) = 1
    )
    select
        (select first_day_of_first_week)
        + (_week - 1) * 7
        + generate_series(0, 6) "day" 
;

$$ language sql;

select * from week_dates(1, 2012) s("day");
    day     
------------
 2012-01-02
 2012-01-03
 2012-01-04
 2012-01-05
 2012-01-06
 2012-01-07
 2012-01-08