Postgres generate_series excluding date ranges Postgres generate_series excluding date ranges postgresql postgresql

Postgres generate_series excluding date ranges


First you create a time series of dates over the next two years, EXCEPT your blackout dates:

SELECT dtFROM generate_series('2015-08-01'::date, '2017-08-01'::date, interval '1 day') AS s(dt)EXCEPTSELECT dtFROM generate_series('2015-08-27'::date, '2015-09-03'::date, interval '1 day') as ex1(dt)

Note that you can have as many EXCEPT clauses as you need. For individual blackout days (as opposed to ranges) you could use a VALUES clause instead of a SELECT.

Then you window over that time-series to generate row numbers of billable days:

SELECT row_number() OVER (ORDER BY dt) AS rn, dtFROM (<query above>) x

Then you select those days where you want to bill:

SELECT dtFROM (<query above>) yWHERE rn % 30 = 1; -- billing on the first day of the period

(This latter query following Craig's advice of billing by 30 days)

Yields:

SELECT dtFROM (  SELECT row_number() OVER (ORDER BY dt) AS rn, dt  FROM (    SELECT dt    FROM generate_series('2015-08-01'::date, '2017-08-01'::date, interval '1 day') AS s(dt)    EXCEPT    SELECT dt    FROM generate_series('2015-08-27'::date, '2015-09-03'::date, interval '1 day') as ex1(dt)  ) x) yWHERE rn % 30 = 1;


You will have to split the call to generate series for exclusions. Some thing like this:

  • Union of 3 queries
  • First query pulls dates from start to exclusion range from
  • Second query pulls dates between exclusion range to and your end date
  • Third query pulls dates when none of your series dates cross exclusion range

Note: You still need a way to loop through exclusion list (if you have one). Also this query may not be very efficient as such scenarios can be better handled through functions or procedural code.