My goal is to calculate the interval between two date with and without Saturday and Sunday.
If my table look like this :
entity_id | time_c | next_time_c |
---|---|---|
1 | ‘2022-06-09 15:39:00’ | ‘2023-02-27 15:09:48’ |
2 | ‘2023-03-06 11:17:12’ | ‘2023-03-06 11:17:14’ |
I’m able to calculate the interval using :
WITH parms (entity_id, start_date, end_date) AS
(
SELECT
entity_id,
time_c::timestamp,
next_time_c::timestamp
FROM
test_c
), weekend_days (entity_id, wkend) AS
(
SELECT
entity_id,
SUM(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
FROM
parms
CROSS JOIN
generate_series(start_date, end_date, interval '1 day') dn(d)
GROUP BY entity_id
)
SELECT
entity_id AS "ID",
CONCAT(
extract(day from diff), ' days ',
extract( hours from diff) , ' hours ',
extract( minutes from diff) , ' minutes ',
extract( seconds from diff)::int , ' seconds '
) AS "Duration (excluding saturday and sunday)",
justify_interval(end_date::timestamp - start_date::timestamp) AS "Duration full"
FROM (
SELECT
start_date,
end_date,
entity_id,
(end_date-start_date) - (wkend * interval '1 day') AS diff
FROM parms
JOIN weekend_days USING(entity_id)
) sq;
Output (which is correct):
ID | Duration (excluding saturday and sunday) | Duration full |
---|---|---|
1 | 186 days 23 hours 30 minutes 48 seconds | {"months":8,"days":22,"hours":23,"minutes":30,"seconds":48} |
2 | 0 days 0 hours 0 minutes 2 seconds | {"seconds":2} |
On the other side I have my history table :
entity_id | phase | old_phase | time | next_time |
---|---|---|---|---|
1 | ‘Log’ | null | 1654781946989 | 1654781949732 |
1 | ‘Approve’ | ‘Log’ | 1654781949732 | 1654781952676 |
1 | ‘Fulfill’ | ‘Approve’ | 1654781952676 | 1677506971778 |
1 | ‘Accept’ | ‘Fulfill’ | 1677506971778 | 1677518742552 |
1 | ‘Review’ | ‘Accept’ | 1677518742552 | 1678097845979 |
1 | ‘Fulfill’ | ‘Review’ | 1678097845979 | 1678097847325 |
1 | ‘Accept’ | ‘Fulfill’ | 1678097847325 | 1678097977816 |
1 | ‘Review’ | ‘Accept’ | 1678097977816 | null |
The goal is to calculate the interval of the Fulfill
phase.
I came up with the following query :
WITH temp2 AS (
SELECT
entity_id,
old_phase,
phase,
time,
next_time,
to_timestamp(to_char(to_timestamp("time"/1000.0) at time zone 'Europe/Paris', 'yyyy-mm-dd HH24:MI:SS'), 'yyyy-mm-dd HH24:MI:SS') at time zone 'Europe/Paris' AS "TIME 2",
to_timestamp(to_char(to_timestamp("next_time"/1000.0) at time zone 'Europe/Paris', 'yyyy-mm-dd HH24:MI:SS'), 'yyyy-mm-dd HH24:MI:SS') at time zone 'Europe/Paris'AS "NEXT TIME 2",
((next_time - time)/1000.0) AS DIFF
FROM
tbl_history
WHERE phase = 'Fulfill'
),parms (entity_id, start_date, end_date) AS
(
SELECT
entity_id,
"TIME 2"::timestamp,
"NEXT TIME 2"::timestamp
FROM
temp2
), weekend_days (entity_id, wkend) AS
(
SELECT
entity_id,
SUM(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
FROM
parms
CROSS JOIN
generate_series(start_date, end_date, interval '1 day') dn(d)
GROUP BY entity_id
)
SELECT
entity_id AS "ID",
CONCAT(
extract(day from diff), ' days ',
extract( hours from diff) , ' hours ',
extract( minutes from diff) , ' minutes ',
extract( seconds from diff)::int , ' seconds '
) AS "Duration (excluding saturday and sunday)",
justify_interval(end_date::timestamp - start_date::timestamp) AS "Duration full"
FROM (
SELECT
start_date,
end_date,
entity_id,
(end_date-start_date) - (wkend * interval '1 day') AS diff
FROM parms
JOIN weekend_days USING(entity_id)
) sq;
But the output isn’t as expected (should be the same as the previous output) :
ID | Duration (excluding saturday and sunday) | Duration full |
---|---|---|
1 | -76 days 0 hours 0 minutes 2 seconds | {"seconds":2} |
1 | 186 days 22 hours 30 minutes 19 seconds | {"months":8,"days":22,"hours":22,"minutes":30,"seconds":19} |
Why do I have a wrong output ?
2
Answers
I suggest a function to work with your data. It accepts your 2 integer "timestamps" , calculates the number of whole days between them, ignoring weekends. Then the time portions of the start and end are subtracted as an interval which is added back to the number of days as an interval. Hence the overall interval is povided:
Here is an alternative approach that doesn’t require a function. Instead it uses lateral joins to mimic the effect of the function provided earlier, a useful feature of this approach is that column aliases formed in the one lateral join can be referenced in any subsequent lateral join which helps simplify the syntax used to create the
bus_interval
column.Notes:
time
andnext_time
into timestampsgenerate_series
we can exclude the unwanted weekends, but that result is an integer of whole days which ignores the starting time(of day) and finish time(of day)interval_24x7
(nb: this is an optional column that can be ommitted)See this operating here dbfiddle