Function
While using the function addbusinessdays()
, consider this instead:
CREATE OR REPLACE FUNCTION addbusinessdays(date, integer)
RETURNS date AS
$func$
SELECT day
FROM (
SELECT i, $1 + i * sign($2)::int AS day
FROM generate_series(0, ((abs($2) * 7) / 5) + 3) i
) sub
WHERE EXTRACT(ISODOW FROM day) < 6 -- truncate weekend
ORDER BY i
OFFSET abs($2)
LIMIT 1
$func$ LANGUAGE sql IMMUTABLE;
Major points
Never quote the language name
sql
. It's an identifier, not a string.Why was the function
VOLATILE
? Make itIMMUTABLE
for better performance in repeated use and more options (like using it in a functional index).(ABS($2) + 5)*2)
is way too much padding. Replace with((abs($2) * 7) / 5) + 3)
.Multiple levels of CTEs were useless cruft.
ORDER BY
in last CTE was useless, too.As mentioned in my previous answer,
extract(
ISODOWFROM ...)
is more convenient to truncate weekends.
Query
That said, I wouldn't use above function for this query at all. Build a complete grid of relevant days once instead of calculating the range of days for every single row.
Based on this assertion in a comment (should be in the question, really!):
two subsequent windows of the same firm can never overlap.
WITH range AS ( -- only with flag
SELECT company
, min(day) - 2 AS r_start
, max(day) + 1 AS r_stop
FROM tbl t
WHERE flag <> 0
GROUP BY 1
)
, grid AS (
SELECT company, day::date
FROM range r
,generate_series(r.r_start, r.r_stop, interval '1d') d(day)
WHERE extract('ISODOW' FROM d.day) < 6
)
SELECT *, sum(flag) OVER(PARTITION BY company ORDER BY day
ROWS BETWEEN UNBOUNDED PRECEDING
AND 2 following) AS window_nr
FROM (
SELECT t.*, max(t.flag) OVER(PARTITION BY g.company ORDER BY g.day
ROWS BETWEEN 1 preceding
AND 2 following) in_window
FROM grid g
LEFT JOIN tbl t USING (company, day)
) sub
WHERE in_window > 0 -- only rows in [-2,1] window
AND day IS NOT NULL -- exclude missing days in [-2,1] window
ORDER BY company, day;
How?
Build a grid of all business days: CTE
grid
.To keep the grid to its smallest possible size, extract minimum and maximum (plus buffer) day per company: CTE
range
.LEFT JOIN
actual rows to it. Now the frames for ensuing window functions works with static numbers.To get distinct numbers per flag and company (
window_nr
), just count flags from the start of the grid (taking buffers into account).Only keep days inside your [-2,1] windows (
in_window > 0
).Only keep days with actual rows in the table.
Voilá.