NOCYCLE in Postgres - sql

I have a Oracle query with a NOCYCLE clause which I have to translate to Postgres:
SELECT FG_ID,CONNECT_BY_ROOT FG_ID as Parent_ID
FROM FG t
START WITH t.Parent_filter_group_id is null
CONNECT BY NOCYCLE PRIOR t.FILTER_GROUP_ID = t.PARENT_FILTER_GROUP_ID
I have converted this one with the help of the question and answer in
connect_by_root equivalent in postgres
as
with recursive fg_tree as (
select FG_ID,
FG_ID as fg
from FG
where Parent_filter_group_id is null
union all
select c.FG_ID,
p.fg
from FG c join fg_tree p on p.FG_ID = PARENT_FILTER_GROUP_ID
)
select * from fg_tree
order by FG_ID
but in this there is no clause for NOCYCLE if the parent is also one of the children then this query will return error.

You can collect the IDs for each level and then join on the condition that the "current" id is not contained in the path:
with recursive fg_tree as (
select FG_ID,
FG_ID as fg,
array[fg_id] as path
from FG
where Parent_filter_group_id is null
union all
select c.FG_ID,
p.fg,
p.fg||c.fg_id
from FG c
join fg_tree p on p.FG_ID and c.fg_id <> ALL (p.path)
)
select fg_id, fg
from fg_tree
order by filter_group_id

oracle version:
SELECT o.object_id, p.plugin_id AS plugin_id, LEVEL, CONNECT_BY_ISCYCLE "Cycle"
FROM sst_cycle_obj o LEFT JOIN sst_cycle_devplug p ON p.device_id = o.object_id
WHERE CONNECT_BY_ISCYCLE = 1
CONNECT BY NOCYCLE o.object_id = PRIOR p.plugin_id
START WITH o.object_id = 11
create table sst_cycle_obj
(object_id numeric(10))
create table sst_cycle_devplug
(device_id numeric(10)
,plugin_id numeric(10))
insert into sst_cycle_obj (object_id)
(select 11 from dual
union all
select 12 from dual
union all
select 13 from dual)
insert into sst_cycle_devplug (device_id,plugin_id)
(select 11, 12 from dual
union all
select 12, 13 from dual
union all
select 13,11 from dual)
-->NOCYCLE2 CONNECT_BY_ISCYCLE "Cycle" Postgresql version
WITH recursive ncot
AS
(SELECT a.device_id,a.plugin_id,('{'||a.device_id||'}')::numeric[] as PATH, 0 AS cycle1, 1::int as level
FROM ( select o.object_id device_id, p.plugin_id as plugin_id
from sst_cycle_obj o
left join sst_cycle_devplug p on p.device_id=o.object_id
) a
WHERE a.device_id = 11
UNION ALL
SELECT objt.device_id,objt.plugin_id, ncot.path||objt.device_id::numeric as PATH, CASE WHEN objt.plugin_id = any(ncot.path) THEN 1 else 0 END AS cycle1, ncot.level + 1 as level
FROM ( select o.object_id device_id, p.plugin_id as plugin_id
from sst_cycle_obj o
left join sst_cycle_devplug p on p.device_id=o.object_id
) objt
JOIN ncot ON objt.device_id = ncot.plugin_id and objt.device_id <> ALL (ncot.path)
)
SELECT device_id,plugin_id,PATH,cycle1,level
FROM ncot
WHERE cycle1=1
--<

Related

Getting invalid identifier even though column exists

create table test_tab1(
seq_id NUMBER(10),
e_id NUMBER(10),
jira_key VARCHAR2(20),
stage_code NUMBER(10)
);
INSERT INTO test_tab1 VALUES(1,11,'JIRA_A',2);
INSERT INTO test_tab1 VALUES(2,12,'JIRA_B',3);
COMMIT;
create table test_tab2(
seq_id NUMBER(10),
e_id NUMBER(10),
jira_key VARCHAR2(20),
stage_code NUMBER(10)
);
Can anyone tell me why I am getting the n.stage_code invalid identifier error in the below code even though this column exists?
Expected should be that this query should not give this error since this column exist in the WITH sub query clause.
WITH got_new_code AS
(
SELECT m.e_id,m.jira_key
, m.stage_code AS new_code
, c.code
FROM test_tab1 m
CROSS APPLY (
SELECT LEVEL - 1 AS code
FROM dual
CONNECT BY LEVEL <= m.stage_code + 1
) c
WHERE m.stage_code IS NOT NULL
ORDER BY m.e_id,m.stage_code
)
SELECT *
FROM got_new_code n
FULL JOIN test_tab2 t USING (e_id,jira_key,stage_code);
Database version: Oracle 18c.
You have a USING clause which specifies stage_code as a column to join the left and right tables on and there is no stage_code from the got_new_code sub-query factoring clause as it has been aliased to new_code.
Either don't use USING and specify the join conditions manually or ensure that both sides of the join have all the columns in the USING clause:
WITH got_new_code (e_id, jira_key, new_code, code) AS (
SELECT m.e_id,m.jira_key
, m.stage_code
, c.code
FROM test_tab1 m
CROSS APPLY (
SELECT LEVEL - 1 AS code
FROM dual
CONNECT BY LEVEL <= m.stage_code + 1
) c
WHERE m.stage_code IS NOT NULL
ORDER BY m.e_id,m.stage_code
)
SELECT *
FROM got_new_code n
FULL JOIN test_tab2 t
ON ( t.e_id = n.e_id
AND t.jira_key = n.jira_key
AND t.stage_code = n.new_code );
or
WITH got_new_code (e_id, jira_key, stage_code, code) AS (
SELECT m.e_id,m.jira_key
, m.stage_code
, c.code
FROM test_tab1 m
CROSS APPLY (
SELECT LEVEL - 1 AS code
FROM dual
CONNECT BY LEVEL <= m.stage_code + 1
) c
WHERE m.stage_code IS NOT NULL
ORDER BY m.e_id,m.stage_code
)
SELECT *
FROM got_new_code n
FULL JOIN test_tab2 t USING (e_id,jira_key,stage_code);
db<>fiddle here
You can either remove the new_code alias:
WITH got_new_code AS
(
SELECT m.e_id,m.jira_key
, m.stage_code
, c.code
FROM test_tab1 m
CROSS APPLY (
SELECT LEVEL - 1 AS code
FROM dual
CONNECT BY LEVEL <= m.stage_code + 1
) c
WHERE m.stage_code IS NOT NULL
ORDER BY m.e_id,m.stage_code
)
SELECT *
FROM got_new_code n
FULL JOIN test_tab2 t USING (e_id,jira_key,stage_code);
or change the join from using to on:
WITH got_new_code AS
(
SELECT m.e_id,m.jira_key
, m.stage_code AS new_code
, c.code
FROM test_tab1 m
CROSS APPLY (
SELECT LEVEL - 1 AS code
FROM dual
CONNECT BY LEVEL <= m.stage_code + 1
) c
WHERE m.stage_code IS NOT NULL
ORDER BY m.e_id,m.stage_code
)
SELECT *
FROM got_new_code n
FULL JOIN test_tab2 t
ON t.e_id = n.e_id AND t.jira_key = n.jira_key AND t.stage_code = n.new_code;
... which gives the joining columns twice; so you probably want to coalesce those back to a single occurrence.
db<>fiddle

PostgreSQL recursive query for calculation parent value

Here is entry's hierarchy.
_________Milky Way (30)________
/ | \
Alpha(10) Beta(20) Delta(null)
/ \ |
Mars(7) Jupiter(3) Delta-child(44)
Parents value is a sum of it's children's values.
Ex.
Alpha = Mars + Jupiter = 7 + 3 = 10
Milky Way = Alpha + Beta + Delta = 10 + 20 + null = 30
The task: recalculate parents up to the root in case any child is updated. Let's even simplify the task: select all entries up to the root with recalculated values.
Imagine that Mars is updated. Now Mars value is 2.
_________Milky Way (?)________
/ | \
Alpha(?) Beta(20) Delta(null)
/ \ |
Mars(2) Jupiter(3) Delta-child(44)
It means that all parents should be updated:
Alpha = Mars + Jupiter = 2 + 3 = 5
Milky Way = Alpha + Beta + Delta = 5 + 20 + null = 25.
Note: Delta -> Delta-child coupling is broken and it's fine. It can happen lets leave it out of scope here. 've added this sample just to be sure that it won't be counted during calculation as hierarchy can be huge enough and tehre is no task to recalculate all children leaf, just parents up to the root.
As a result of some "select .. from hierarchy.."
I'd like to receive recalculated parents' values.
Ex.
id
name
value
1
Milky Way
25
2
Alpha
5
Code samples with already updated Mars (sqlfiddle links are below):
Schema
CREATE TABLE hierarchy
(
id int4,
parent_id int4,
name varchar(255),
value int4
);
Values
insert into hierarchy
values
(1, null, 'Milky Way', 30),
(2, 1, 'Alpha', 10),
(3, 1, 'Beta', 20),
(4, 1, 'Delta', null),
(5, 2, 'Mars', 2),
(6, 2, 'Jupiter', 3),
(7, 4, 'Delta-child', 44);
What I have tried:
I was able to list all leafs which should be used in calculation
sqlfiddle 1
WITH RECURSIVE cte AS (
SELECT h1.id, h1.parent_id, h1.name , h1.value from hierarchy h1
where h1.id = 5
UNION
SELECT h2.id, h2.parent_id, h2.name , h2.value from hierarchy h2
JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id )
where cte.id != h2.id
) select * from cte
order by id
When I tried to sum values, query goes in infinite loop for some reason
sqlfiddle 2
WITH RECURSIVE cte AS (
SELECT h1.id, h1.parent_id, h1.name , h1.value from hierarchy h1
where h1.id = 5
UNION
SELECT h2.id, h2.parent_id, h2.name , (h2.value + cte.value) as value from hierarchy h2
JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id )
where cte.id != h2.id
) select * from cte
order by id
There is one more query that I have tried, unfortunately it doesn't count sibling of parents.
sqlfiddle 3
WITH RECURSIVE cte AS (
SELECT h1.id, h1.parent_id, h1.name , h1.value from hierarchy h1
where h1.parent_id = (select parent_id from hierarchy where id = 5)
UNION
SELECT h2.id, h2.parent_id, h2.name , cte.value as value from hierarchy h2
JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id )
where cte.id != h2.id
) select id, parent_id, name, sum(value) from cte
group by id, parent_id, name
order by id
I'd appreciate any assistance. :-)
It took me a while, but good work on your trials.
What i did is that I split the problem in parts.
go down the hierarchy with the recursive query
with recursive base_qry as (
select id,
parent_id,
value,
ARRAY[id] as id_array
from hierarchy
union all
select h.id,
h.parent_id,
h.value,
id_array || h.id as id_array
from hierarchy h join base_qry b on h.parent_id = b.id and h.value is not null
)
Understand the nodes affected filtering the last id of the array containing all the nodes with id_array[array_length(id_array,1)] = 5
nodes_affected as (
select * from base_qry
where id_array[array_length(id_array,1)] = 5
order by array_length(id_array,1) desc
LIMIT 1)
Find all the tree breanches than contribute to changed nodes (check here the filter for node id=5)
all_combinations as (
select b.id, b.parent_id, b.value, b.id_array from
base_qry b join nodes_affected n
on ARRAY[n.id_array] && ARRAY[b.id_array]
where (b.id_array[array_length(b.id_array,1)] = 5
or b.id_array #> ARRAY[5] = false)
)
aggregating up
select id_array[1]::int id, sum(value)
from all_combinations where id not in (select parent_id from all_combinations where parent_id is not null)
group by id_array[1]::int
order by 1
Whole query
with recursive base_qry as (
select id,
parent_id,
value,
ARRAY[id] as id_array
from hierarchy
union all
select h.id,
h.parent_id,
h.value,
id_array || h.id as id_array
from hierarchy h join base_qry b on h.parent_id = b.id and h.value is not null
),
nodes_affected as (
select * from base_qry
where id_array[array_length(id_array,1)] = 5
order by array_length(id_array,1) desc
LIMIT 1),
all_combinations as (
select b.id, b.parent_id, b.value, b.id_array from
base_qry b join nodes_affected n
on ARRAY[n.id_array] && ARRAY[b.id_array]
where (b.id_array[array_length(b.id_array,1)] = 5
or b.id_array #> ARRAY[5] = false)
)
select id_array[1]::int id, sum(value)
from all_combinations where id not in (select parent_id from all_combinations where parent_id is not null)
group by id_array[1]::int
order by 1
;
Start from the leafs and traverse all the nodes the leaf contributes to up the hierachy. Then just sum all contributions to a node.
WITH RECURSIVE cte AS (
SELECT id, parent_id, value
FROM hierarchy h1
WHERE not exists (select 1 from hierarchy h2 where h2.parent_id = h1.id)
UNION ALL
SELECT h.id, h.parent_id, case when h.value is null then 0 else cte.value end
FROM hierarchy h
JOIN cte ON (cte.parent_id = h.id)
)
select h.id, h.name, v.value
from (
select id, sum(value) as value
from cte
group by id
) v
join hierarchy h on h.id = v.id
order by h.id;
db<>fiddle
You can use a recursive CTE to get all nodes and their paths, and then for every non-leaf node in hierarchy, you can join the CTE back to hierarchy on the condition that the current hierarchy row id exists in the path and sum the values:
with recursive cte(id, p, n, v, t) as (
select h.*, concat('[', h.id::text) from hierarchy h where h.parent_id is null
union all
select h.id, h.parent_id, h.name, h.value, concat(c.t, ', ', h.id)
from cte c join hierarchy h on c.id = h.parent_id
)
select h.id, sum(c.v)
from hierarchy h join cte c on c.id != h.id and exists (select 1 from jsonb_array_elements(concat(c.t, ']')::jsonb) v where v.value::text = h.id::text)
group by h.id order by h.id
Output:
id sum
1 79
2 5
4 44

SQL join and Bring in 1 column into 2 columns

I have two tables shown as above. I want to create a select statement that would have the following result:
Basically, I want to join on 'Model' and 'Num' columns and bring 'Val' vales but break it into two columns based on 'Ver'.
SELECT 'a' as Model ,'1' as Num ,'v1' as Ver ,'9' as val INTO #RefTbl UNION ALL
SELECT 'a','2','v1','10' UNION ALL
SELECT 'a','3','v1','11' UNION ALL
SELECT 'a','1','v2','5' UNION ALL
SELECT 'a','2','v2','6' UNION ALL
SELECT 'a','3','v2','7' UNION ALL
SELECT 'b','1','v1','20' UNION ALL
SELECT 'b','1','v2','21' UNION ALL
SELECT 'b','2','v1','25' UNION ALL
SELECT 'b','2','v2','26'
SELECT '519' as ID,'a' as Model,'1' as Num INTO #OrderTbl UNION ALL
SELECT '5616','a','3' UNION ALL
SELECT '871','b','1'
-- failed attempt
SELECT o.*, '' as v1_val, '' as v2_val FROM #OrderTbl as o left join #RefTbl as r on o.Model = r.Model and o.Num = r.Num
You can join and do conditional aggregation:
select
d.ID,
d.Model,
d.Num,
max(case when r.Ver = 'val1' then Val end) V1_Val,
max(case when r.Ver = 'val2' then Val end) V2_Val
from DataTbl d
inner join ReferenceTbl r
on r.Model = d.Model
and r.Num = d.Num
group by
d.ID,
d.Model,
d.Num
Alternatively, you can join twice - depending on your dataset, this might (or might not) perform better:
select
d.ID,
d.Model,
d.Num,
r1.Val V1_Val,
r2.Val V2_Val
from DataTbl d
left join ReferenceTbl r1
on r1.Model = d.Model
and r1.Num = d.Num
and r1.Ver = 'val1'
left join ReferenceTbl r2
on r2.Model = d.Model
and r2.Num = d.Num
and r2.Ver = 'val2'
Case + Group by works on most db system which is nice.
Alternative answer using SQL Server PIVOT (for reference)
WITH RefTbl AS (
SELECT 'a' as Model ,'1' as Num ,'v1' as Ver ,'9' as val UNION ALL
SELECT 'a','2','v1','10' UNION ALL
SELECT 'a','3','v1','11' UNION ALL
SELECT 'a','1','v2','5' UNION ALL
SELECT 'a','2','v2','6' UNION ALL
SELECT 'a','3','v2','7' UNION ALL
SELECT 'b','1','v1','20' UNION ALL
SELECT 'b','1','v2','21' UNION ALL
SELECT 'b','2','v1','25' UNION ALL
SELECT 'b','2','v2','26'),
OrderTbl AS (
SELECT '519' as ID,'a' as Model,'1' as Num UNION ALL
SELECT '5616','a','3' UNION ALL
SELECT '871','b','1'
)
SELECT Id, Model, Num AS Model, [v1] AS v1_Val, [v2] as v2_Val
FROM
(SELECT o.id, r.Model, r.Num, r.val, r.ver from OrderTbl as o
left join RefTbl as r on o.Model = r.Model and o.Num = r.Num) AS SourceTable
PIVOT
(MAX(val) FOR ver IN ([v1], [v2])) AS PivotTable;
Id Model Model v1_Val v2_Val
519 a 1 9 5
5616 a 3 11 7
871 b 1 20 21

get last node given the full path of all ancestor's node attributes using cte

Given the following PostgreSQL table:
items
integer id
integer parent_id
string name
unique key on [parent_id, name]
parent_id is null for all root nodes
Currently I build the sql query manually, doing a join for every path element. But is seems quite ugly to me and of course it limits the possible depth.
Example:
path: holiday,images,spain
SELECT i3.*
FROM items AS i1
, items AS i2
, items AS i3
WHERE i1.parent_id IS NULL AND i1.name = 'holiday'
AND i2.parent_id=i1.id AND i2.name = 'images'
AND i3.parent_id=i2.id AND i3.name = 'spain'
I wonder if there's a better way, probably using CTE?
You can see how my current code works and what the expected output is here:
http://sqlfiddle.com/#!1/4537c/2
update2 here's a function, it peforms well, because search goes only within the path, starting from parent:
create or replace function get_item(path text[])
returns items
as
$$
with recursive cte as (
select i.id, i.name, i.parent_id, 1 as level
from items as i
where i.parent_id is null and i.name = $1[1]
union all
select i.id, i.name, i.parent_id, c.level + 1
from items as i
inner join cte as c on c.id = i.parent_id
where i.name = $1[level + 1]
)
select c.id, c.parent_id, c.name
from cte as c
where c.level = array_length($1, 1)
$$
language sql;
sql fiddle demo
update I think you can do recursive traversal. I've written sql version of this, so it's a bit messy because of cte, but it's possible to write a function:
with recursive cte_path as (
select array['holiday', 'spain', '2013'] as arr
), cte as (
select i.id, i.name, i.parent_id, 1 as level
from items as i
cross join cte_path as p
where i.parent_id is null and name = p.arr[1]
union all
select i.id, i.name, i.parent_id, c.level + 1
from items as i
inner join cte as c on c.id = i.parent_id
cross join cte_path as p
where i.name = p.arr[level + 1]
)
select c.*
from cte as c
cross join cte_path as p
where level = array_length(p.arr, 1)
sql fiddle demo
or you can build path for all of the elements using recursive cte for that and accumuate your path into array or string:
with recursive cte as (
select i.id, i.name, i.parent_id, i.name::text as path
from items as i
where i.parent_id is null
union all
select i.id, i.name, i.parent_id, c.path || '->' || i.name::text as path
from items as i
inner join cte as c on c.id = i.parent_id
)
select *
from cte
where path = 'holiday->spain->2013';
or
with recursive cte as (
select i.id, i.name, i.parent_id, array[i.name::text] as path
from items as i
where i.parent_id is null
union all
select i.id, i.name, i.parent_id, c.path || array[i.name::text] as path
from items as i
inner join cte as c on c.id = i.parent_id
)
select *
from cte
where path = array['holiday', 'spain', '2013']
sql fiddle demo
This should perform very well, as it eliminates impossible paths immediately:
WITH RECURSIVE cte AS (
SELECT id, parent_id, name
,'{holiday,spain,2013}'::text[] AS path -- provide path as array here
,2 AS lvl -- next level
FROM items
WHERE parent_id IS NULL
AND name = 'holiday' -- being path[1]
UNION ALL
SELECT i.id, i.parent_id, i.name
,cte.path, cte.lvl + 1
FROM cte
JOIN items i ON i.parent_id = cte.id AND i.name = path[lvl]
)
SELECT id, parent_id, name
FROM cte
ORDER BY lvl DESC
LIMIT 1;
Assuming you provide a unique path (only 1 result).
->SQLfiddle demo
Too late to post my answer (very equivalent to Roman's and Erwin's) But an improvement on the table definition instead:
CREATE TABLE items
( id integer NOT NULL PRIMARY KEY
, parent_id integer REFERENCES items(id)
, name varchar
, UNIQUE (parent_id,name) -- I don't actually like this one
); -- ; UNIQUE on a NULLable column ...
INSERT INTO items (id, parent_id, name) values
(1, null, 'holiday')
, (2, 1, 'spain'), (3, 2, '2013')
, (4, 1, 'usa'), (5, 4, '2013')
;

SQL recursive query that gets all ancestors of an item

ID parent_id name
---------------------
1 2 first
2 4 second
3 3 third
4 5 fourth
5 - fifth
Ancestors list of first should be (2, 4, 5)
with name_tree as (
select id, parent_id, name
from the_unknown_table
where id = 1 -- this is the starting point you want in your recursion
union all
select c.id, c.parent_id, c.name
from the_unknown_table c
join name_tree p on p.parent_id = c.id -- this is the recursion
)
select *
from name_tree
where id <> 1; -- exclude the starting point from the overall result
SQLFiddle: http://sqlfiddle.com/#!3/87d0c/1
You can use something like this:
with parents as
(
select ID, parent_ID
from t
where parent_ID is not null
union all
select p.ID, t.parent_ID
from parents p
inner join t on p.parent_ID = t.ID
and t.parent_ID is not null
and t.ID <> t.parent_ID
)
select *
, parents = '(' + stuff
(
(
select ', ' + cast(p.parent_ID as varchar(100))
from parents p
where t.ID = p.ID
for xml path('')
), 1, 2, ''
) + ')'
from t
order by ID
SQL Fiddle with demo.
This combines two very common T-SQL techniques - using a CTE to get a hierarchy and using FOR XML PATH to get a CSV list.