I need some help with PostgreSQL CTEs. I have a task to get entities from postgre with some interesting conditions:
Self-referencing table, let it be called ordr(ordr_id bigint, parent_ordr_id bigint, is_terminated boolean)
.
Need to get ordr
(basically a flat list of orders) which are met the condition is_terminated = true
. But if any entity from chain have is_terminated = false
full chain shouldn't be in result.
For example
INSERT INTO ordr_tst.ordr (id,parent_id, is_terminated) VALUES
(0, NULL, true),
(-1,NULL,true),
(-2,-1,true),
(-3,-2,true),
(-11,NULL,false),
(-12,-11,true),
(-13,-12,true),
(-21,NULL,true),
(-22,-21, false),
(-23,-22, true),
(-31,NULL, true),
(-32,-31, false),
(-33,-32, true),
(-34,-32, true),
(-41,NULL, true),
(-42,NULL, true),
(-43,NULL, false);
The result should be: entities with ids 0, -1, -2, -3
My approach on this only works for the assumption that parent ordrs
are always terminated only after child ordrs
but unfortunately, it's not true in my case.
WITH RECURSIVE r AS (
SELECT o.ordr_id as id
FROM ordr_tst.ordr o
WHERE o.parent_ordr_id is null
AND o.is_terminated = true
UNION
SELECT o.ordr_id as id
FROM ordr_tst.ordr o
JOIN r ON o.parent_ordr_id = r.id
WHERE o.is_terminated = true
)
SELECT * FROM ordr.ordr o WHERE o.id in (SELECT r.id FROM r);
I tried some obviously not working stuff like self-joining CTE results.
Making arrays in CTE like
...
select array[o.ordr_id]
...
UNION
select array[o.ordr_id] || cte.id
...
And I was trying to add second CTE but my brain started throttling.
The following query first finds all bad rows and their children, calculates the rest by eliminating these rows and then applies your recursive CTE to the rest:
WITH RECURSIVE bad AS (
SELECT o.id, o.parent_id
FROM ordr_tst.ordr AS o
WHERE NOT o.is_terminated
UNION ALL
SELECT o.id, o.parent_id
FROM ordr_tst.ordr AS o
JOIN bad ON o.id = bad.parent_id
), rest AS (
SELECT o.id, o.parent_id, o.is_terminated
FROM ordr_tst.ordr AS o
WHERE NOT EXISTS (SELECT FROM bad
WHERE bad.id = o.id)
), r AS (
SELECT rest.id
FROM rest
WHERE rest.parent_id IS NULL
AND rest.is_terminated
UNION
SELECT rest.id
FROM rest
JOIN r ON rest.parent_id = r.id
WHERE rest.is_terminated
)
SELECT * FROM ordr_tst.ordr AS o
WHERE EXISTS (SELECT FROM r WHERE o.id = r.id);