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

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

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 :

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 ?

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 :

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 ?

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之间的小时:分钟:秒。
CREATE TABLE tbl_history (
	entity_id INT,
  	phase VARCHAR(255),
  	old_phase VARCHAR(255), 
  	time BIGINT, 
  	next_time BIGINT
);
英文:

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
CREATE TABLE tbl_history (
	entity_id INT,
  	phase VARCHAR(255),
  	old_phase VARCHAR(255), 
  	time BIGINT, 
  	next_time BIGINT
);
INSERT INTO tbl_history VALUES
(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);
CREATE TABLE test_c (
	entity_id INT,
  	time_c timestamp ,
  	next_time_c timestamp 
);
INSERT INTO test_c VALUES
(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')
SELECT
    t.entity_id
  , t.phase
  , t.old_phase
  , ts.bus_days
  , bd.bus_interval
  , justify_interval(bd.bus_interval) as alt_interval
  , bd.interval_24x7
  , ts.start_ts
  , ts.end_ts
  , t.time
  , t.next_time
FROM tbl_history AS t
inner join lateral (
  /* this lateral join "extends" the source row with these columns */
  SELECT 
       to_timestamp(t.time / 1000)::timestamp      AS start_ts
     , to_timestamp(t.next_time / 1000)::timestamp AS end_ts
     , (SELECT COUNT(*)::int 
        FROM generate_series(to_timestamp(t.time / 1000)::DATE, to_timestamp(t.next_time / 1000)::DATE, '1 day')
        WHERE EXTRACT(ISODOW FROM generate_series) < 6
        ) AS bus_days
  ) AS ts ON true
inner join lateral (
  /* additional lateral join "further extends" the row with these columns 
     and it can access the columns produced by earlier lateral joins
  */
  SELECT 
    ts.end_ts - ts.start_ts as interval_24x7
  , make_interval(0,0,0,ts.bus_days - 1) + ((ts.end_ts - ts.start_ts)::time)::interval  as bus_interval
  ) 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”,计算它们之间整数天数,忽略周末。然后,开始和结束的时间部分被减去作为一个时间间隔,然后再加回到整数天数中。因此,最终的间隔如下:

CREATE OR REPLACE FUNCTION business_interval(start_time BIGINT, end_time BIGINT)
RETURNS INTERVAL AS $$
DECLARE
    bus_days INTEGER;
    interval_24x7 INTERVAL;
BEGIN
    bus_days := (SELECT COUNT(*) FROM generate_series(
                         to_timestamp(start_time / 1000)::date
                       , to_timestamp(end_time / 1000)::date
                       , '1 day') WHERE EXTRACT(ISODOW FROM generate_series) < 6);

    interval_24x7 := to_timestamp(end_time / 1000)::timestamp 
                   - to_timestamp(start_time / 1000)::timestamp;

    RETURN make_interval(days := bus_days-1) + ((interval_24x7::time)::interval);

END;
$$ LANGUAGE plpgsql;
select
  business_interval(time,next_time) as bus_interval
, *
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:

CREATE OR REPLACE FUNCTION business_interval(start_time BIGINT, end_time BIGINT)
RETURNS INTERVAL AS $$
DECLARE
    bus_days INTEGER;
    interval_24x7 INTERVAL;
BEGIN
    bus_days := (SELECT COUNT(*) FROM generate_series(
                         to_timestamp(start_time / 1000)::date
                       , to_timestamp(end_time / 1000)::date
                       , &#39;1 day&#39;) WHERE EXTRACT(ISODOW FROM generate_series) &lt; 6);

    interval_24x7 := to_timestamp(end_time / 1000)::timestamp 
                   - to_timestamp(start_time / 1000)::timestamp;

    RETURN make_interval(days := bus_days-1) + ((interval_24x7::time)::interval);
  
END;
$$ LANGUAGE plpgsql;
select
      business_interval(time,next_time) as bus_interval
    , *
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:

确定