Question

I'm fairly new to PostgreSQL and recently on my project I've encountered problem with retrieving all records for specific week of year. The main problem is of course with border weeks (1, and 52/53).

For this I've wrote a function:

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;

And the usage of it is, eg.:

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

Is there a better/faster/simpler solution for this (maybe some built-in functionality)?

I'm using PostgreSQL 9.2 and by week I mean ISO week of year (starts on monday)

Was it helpful?

Solution

Much simpler, yet:

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;

The major point is that you can add integer to date to increment days in PostgreSQL.
date_trunc() returns a timestamp, so we need to cast to date another time.

I start with the 4th of January because (quoting the manual here):

By definition (ISO 8601), weeks start on Mondays and the first week of a year contains January 4 of that year.

The function happily accepts impossible weeks like -1 or 55 and returns a somewhat reasonable result. To disallow that, I would make it a plpgsql function and check the input values.

This returns a set instead of an array. It's trivial to make it an array instead (SELECT ARRAY(SELECT ...). But this should be faster. Your query could look like this:

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

Aside: You shouldn't use date as column name, since it is a reserved word in SQL.

OTHER TIPS

I think this is simpler. It will return all days of a given week in a given year.

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top