从表格和CTE(通用表达式)获得不同结果。

huangapple go评论107阅读模式
英文:

Getting different result from table and CTE

问题

以下是代码部分的翻译:

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 :

  1. WITH parms (entity_id, start_date, end_date) AS
  2. (
  3. SELECT
  4. entity_id,
  5. time_c::timestamp,
  6. next_time_c::timestamp
  7. FROM
  8. test_c
  9. ), weekend_days (entity_id, wkend) AS
  10. (
  11. SELECT
  12. entity_id,
  13. SUM(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
  14. FROM
  15. parms
  16. CROSS JOIN
  17. generate_series(start_date, end_date, interval '1 day') dn(d)
  18. GROUP BY entity_id
  19. )
  20. SELECT
  21. entity_id AS "ID",
  22. CONCAT(
  23. extract(day from diff), ' days ',
  24. extract( hours from diff) , ' hours ',
  25. extract( minutes from diff) , ' minutes ',
  26. extract( seconds from diff)::int , ' seconds '
  27. ) AS "Duration (excluding saturday and sunday)",
  28. justify_interval(end_date::timestamp - start_date::timestamp) AS "Duration full"
  29. FROM (
  30. SELECT
  31. start_date,
  32. end_date,
  33. entity_id,
  34. (end_date-start_date) - (wkend * interval '1 day') AS diff
  35. FROM parms
  36. JOIN weekend_days USING(entity_id)
  37. ) 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 :

  1. WITH temp2 AS (
  2. SELECT
  3. entity_id,
  4. old_phase,
  5. phase,
  6. time,
  7. next_time,
  8. 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",
  9. 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",
  10. ((next_time - time)/1000.0) AS DIFF
  11. FROM
  12. tbl_history
  13. WHERE phase = 'Fulfill'
  14. ),parms (entity_id, start_date, end_date) AS
  15. (
  16. SELECT
  17. entity_id,
  18. "TIME 2"::timestamp,
  19. "NEXT TIME 2"::timestamp
  20. FROM
  21. temp2
  22. ), weekend_days (entity_id, wkend) AS
  23. (
  24. SELECT
  25. entity_id,
  26. SUM(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
  27. FROM
  28. parms
  29. CROSS JOIN
  30. generate_series(start_date, end_date, interval '1 day') dn(d)
  31. GROUP BY entity_id
  32. )
  33. SELECT
  34. entity_id AS "ID",
  35. CONCAT(
  36. extract(day from diff), ' days ',
  37. extract( hours from diff) , ' hours ',
  38. extract( minutes from diff) , ' minutes ',
  39. extract( seconds from diff)::int , ' seconds '
  40. ) AS "Duration (excluding saturday and sunday)",
  41. justify_interval(end_date::timestamp - start_date::timestamp) AS "Duration full"
  42. FROM (
  43. SELECT
  44. start_date,
  45. end_date,
  46. entity_id,
  47. (end_date-start_date) - (wkend * interval '1 day') AS diff
  48. FROM parms
  49. JOIN weekend_days USING(entity_id)
  50. ) 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 ?

Demo : https://www.db-fiddle.com/f/ujJe2t9CYLhoRm23RAbSbv/1

英文:

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 :

  1. WITH parms (entity_id, start_date, end_date) AS
  2. (
  3. SELECT
  4. entity_id,
  5. time_c::timestamp,
  6. next_time_c::timestamp
  7. FROM
  8. test_c
  9. ), weekend_days (entity_id, wkend) AS
  10. (
  11. SELECT
  12. entity_id,
  13. SUM(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
  14. FROM
  15. parms
  16. CROSS JOIN
  17. generate_series(start_date, end_date, interval '1 day') dn(d)
  18. GROUP BY entity_id
  19. )
  20. SELECT
  21. entity_id AS "ID",
  22. CONCAT(
  23. extract(day from diff), ' days ',
  24. extract( hours from diff) , ' hours ',
  25. extract( minutes from diff) , ' minutes ',
  26. extract( seconds from diff)::int , ' seconds '
  27. ) AS "Duration (excluding saturday and sunday)",
  28. justify_interval(end_date::timestamp - start_date::timestamp) AS "Duration full"
  29. FROM (
  30. SELECT
  31. start_date,
  32. end_date,
  33. entity_id,
  34. (end_date-start_date) - (wkend * interval '1 day') AS diff
  35. FROM parms
  36. JOIN weekend_days USING(entity_id)
  37. ) 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 :

  1. WITH temp2 AS (
  2. SELECT
  3. entity_id,
  4. old_phase,
  5. phase,
  6. time,
  7. next_time,
  8. 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",
  9. 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",
  10. ((next_time - time)/1000.0) AS DIFF
  11. FROM
  12. tbl_history
  13. WHERE phase = 'Fulfill'
  14. ),parms (entity_id, start_date, end_date) AS
  15. (
  16. SELECT
  17. entity_id,
  18. "TIME 2"::timestamp,
  19. "NEXT TIME 2"::timestamp
  20. FROM
  21. temp2
  22. ), weekend_days (entity_id, wkend) AS
  23. (
  24. SELECT
  25. entity_id,
  26. SUM(case when extract(isodow from d) in (6, 7) then 1 else 0 end)
  27. FROM
  28. parms
  29. CROSS JOIN
  30. generate_series(start_date, end_date, interval '1 day') dn(d)
  31. GROUP BY entity_id
  32. )
  33. SELECT
  34. entity_id AS "ID",
  35. CONCAT(
  36. extract(day from diff), ' days ',
  37. extract( hours from diff) , ' hours ',
  38. extract( minutes from diff) , ' minutes ',
  39. extract( seconds from diff)::int , ' seconds '
  40. ) AS "Duration (excluding saturday and sunday)",
  41. justify_interval(end_date::timestamp - start_date::timestamp) AS "Duration full"
  42. FROM (
  43. SELECT
  44. start_date,
  45. end_date,
  46. entity_id,
  47. (end_date-start_date) - (wkend * interval '1 day') AS diff
  48. FROM parms
  49. JOIN weekend_days USING(entity_id)
  50. ) 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 ?

Demo : https://www.db-fiddle.com/f/ujJe2t9CYLhoRm23RAbSbv/1

答案1

得分: 1

以下是一种替代方法,不需要函数。而是使用侧向连接来模拟之前提供的函数的效果,这种方法的一个有用特性是在一个侧向连接中形成的列别名可以在任何后续的侧向连接中引用,有助于简化用于创建bus_interval列的语法。

注意:

  1. 在第一个侧向连接中,我们将timenext_time转换为时间戳。
  2. 在同一个侧向连接中,使用generate_series我们可以排除不想要的周末,但结果是整数天数,忽略开始时间(每天的时间)和结束时间(每天的时间)。
  3. 在第二个侧向连接中,时间戳值用于获取从start_tsend_ts的整个时间间隔,interval_24x7(注意:这是一个可选列,可以省略)。
  4. 然后我们制作一个由工作日数减1组成的时间间隔,再加上start_tsend_ts之间的小时:分钟:秒。
  1. CREATE TABLE tbl_history (
  2. entity_id INT,
  3. phase VARCHAR(255),
  4. old_phase VARCHAR(255),
  5. time BIGINT,
  6. next_time BIGINT
  7. );
英文:

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:

  1. In the first lateral join we convert time and next_time into timestamps
  2. in the same lateral using generate_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)
  3. in the second lateral join the timestamp values are used to get the whole interval between start_ts and end_ts, interval_24x7 (nb: this is an optional column that can be ommitted)
  4. then we make an interval of the number of business day - 1, plus the hours:minutes:seconds from interval between start_ts and end_ts
  1. CREATE TABLE tbl_history (
  2. entity_id INT,
  3. phase VARCHAR(255),
  4. old_phase VARCHAR(255),
  5. time BIGINT,
  6. next_time BIGINT
  7. );
  1. INSERT INTO tbl_history VALUES
  2. (1, 'Log', null, 1654781946989, 1654781949732),
  3. (1, 'Approve', 'Log', 1654781949732, 1654781952676),
  4. (1, 'Fulfill', 'Approve', 1654781952676, 1677506971778),
  5. (1, 'Accept', 'Fulfill', 1677506971778, 1677518742552),
  6. (1, 'Review', 'Accept', 1677518742552, 1678097845979),
  7. (1, 'Fulfill', 'Review', 1678097845979, 1678097847325),
  8. (1, 'Accept', 'Fulfill', 1678097847325, 1678097977816),
  9. (1, 'Review', 'Accept', 1678097977816, null);
  1. CREATE TABLE test_c (
  2. entity_id INT,
  3. time_c timestamp ,
  4. next_time_c timestamp
  5. );
  1. INSERT INTO test_c VALUES
  2. (1, '2022-06-09 15:39:00', '2023-02-27 15:09:48')
  3. ,(2, '2023-03-06 11:17:12', '2023-03-06 11:17:14')
  1. SELECT
  2. t.entity_id
  3. , t.phase
  4. , t.old_phase
  5. , ts.bus_days
  6. , bd.bus_interval
  7. , justify_interval(bd.bus_interval) as alt_interval
  8. , bd.interval_24x7
  9. , ts.start_ts
  10. , ts.end_ts
  11. , t.time
  12. , t.next_time
  13. FROM tbl_history AS t
  14. inner join lateral (
  15. /* this lateral join "extends" the source row with these columns */
  16. SELECT
  17. to_timestamp(t.time / 1000)::timestamp AS start_ts
  18. , to_timestamp(t.next_time / 1000)::timestamp AS end_ts
  19. , (SELECT COUNT(*)::int
  20. FROM generate_series(to_timestamp(t.time / 1000)::DATE, to_timestamp(t.next_time / 1000)::DATE, '1 day')
  21. WHERE EXTRACT(ISODOW FROM generate_series) < 6
  22. ) AS bus_days
  23. ) AS ts ON true
  24. inner join lateral (
  25. /* additional lateral join "further extends" the row with these columns
  26. and it can access the columns produced by earlier lateral joins
  27. */
  28. SELECT
  29. ts.end_ts - ts.start_ts as interval_24x7
  30. , make_interval(0,0,0,ts.bus_days - 1) + ((ts.end_ts - ts.start_ts)::time)::interval as bus_interval
  31. ) as bd on true
entity_id phase old_phase bus_days bus_interval alt_interval interval_24x7 start_ts end_ts time next_time
1 Log null 1 00:00:03 00:00:03 00:00:03 2022-06-09 14:39:06 2022-06-09 14:39:09 1654781946989 1654781949732
1 Approve Log 1 00:00:03 00:00:03 00:00:03 2022-06-09 14:39:09 2022-06-09 14:39:12 1654781949732 1654781952676
1 Fulfill Approve 188 187 days 23:30:19 6 mons 7 days 23:30:19 262 days 23:30:19 2022-06-09 14:39:12 2023-02-27 14:09:31 1654781952676 1677506971778
1 Accept Fulfill 1 03:16:11 03:16:11 03:16:11 2023-02-27 14:09:31 2023-02-27 17:25:42 1677506971778 1677518742552
1 Review Accept 6 5 days 16:51:43 5 days 16:51:43 6 days 16:51:43 2023-02-27 17:25:42 2023-03-06 10:17:25 1677518742552 1678097845979
1 Fulfill Review 1 00:00:02 00:00:02 00:00:02 2023-03-06 10:17:25 2023-03-06 10:17:27 1678097845979 1678097847325
1 Accept Fulfill 1 00:02:10 00:02:10 00:02:10 2023-03-06 10:17:27 2023-03-06 10:19:37 1678097847325 1678097977816
1 Review Accept 0 null null null 2023-03-06 10:19:37 null 1678097977816 null

See this operating here dbfiddle

答案2

得分: 0

建议一个用于处理你的数据的函数。它接受你的两个整数“timestamps”,计算它们之间整数天数,忽略周末。然后,开始和结束的时间部分被减去作为一个时间间隔,然后再加回到整数天数中。因此,最终的间隔如下:

  1. CREATE OR REPLACE FUNCTION business_interval(start_time BIGINT, end_time BIGINT)
  2. RETURNS INTERVAL AS $$
  3. DECLARE
  4. bus_days INTEGER;
  5. interval_24x7 INTERVAL;
  6. BEGIN
  7. bus_days := (SELECT COUNT(*) FROM generate_series(
  8. to_timestamp(start_time / 1000)::date
  9. , to_timestamp(end_time / 1000)::date
  10. , '1 day') WHERE EXTRACT(ISODOW FROM generate_series) < 6);
  11. interval_24x7 := to_timestamp(end_time / 1000)::timestamp
  12. - to_timestamp(start_time / 1000)::timestamp;
  13. RETURN make_interval(days := bus_days-1) + ((interval_24x7::time)::interval);
  14. END;
  15. $$ LANGUAGE plpgsql;
  1. select
  2. business_interval(time,next_time) as bus_interval
  3. , *
  4. from tbl_history
bus_interval entity_id phase old_phase time next_time
00:00:03 1 Log null 1654781946989 1654781949732
00:00:03 1 Approve Log 1654781949732 1654781952676
187 days 23:30:19 1 Fulfill Approve 1654781952676 1677506971778
03:16:11 1 Accept Fulfill 1677506971778 1677518742552
5 days 16:51:43 1 Review Accept 1677518742552 1678097845979
00:00:02 1 Fulfill Review 1678097845979 1678097847325
00:02:10 1 Accept Fulfill 1678097847325 1678097977816
null 1 Review Accept 1678097977816 null
英文:

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:

  1. CREATE OR REPLACE FUNCTION business_interval(start_time BIGINT, end_time BIGINT)
  2. RETURNS INTERVAL AS $$
  3. DECLARE
  4. bus_days INTEGER;
  5. interval_24x7 INTERVAL;
  6. BEGIN
  7. bus_days := (SELECT COUNT(*) FROM generate_series(
  8. to_timestamp(start_time / 1000)::date
  9. , to_timestamp(end_time / 1000)::date
  10. , &#39;1 day&#39;) WHERE EXTRACT(ISODOW FROM generate_series) &lt; 6);
  11. interval_24x7 := to_timestamp(end_time / 1000)::timestamp
  12. - to_timestamp(start_time / 1000)::timestamp;
  13. RETURN make_interval(days := bus_days-1) + ((interval_24x7::time)::interval);
  14. END;
  15. $$ LANGUAGE plpgsql;
  1. select
  2. business_interval(time,next_time) as bus_interval
  3. , *
  4. from tbl_history
bus_interval entity_id phase old_phase time next_time
00:00:03 1 Log null 1654781946989 1654781949732
00:00:03 1 Approve Log 1654781949732 1654781952676
187 days 23:30:19 1 Fulfill Approve 1654781952676 1677506971778
03:16:11 1 Accept Fulfill 1677506971778 1677518742552
5 days 16:51:43 1 Review Accept 1677518742552 1678097845979
00:00:02 1 Fulfill Review 1678097845979 1678097847325
00:02:10 1 Accept Fulfill 1678097847325 1678097977816
null 1 Review Accept 1678097977816 null
> ``` status
> SELECT 8
> ```

huangapple
  • 本文由 发表于 2023年3月7日 22:30:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75663285.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定