Sum of recursive tree in postgres - sql

have a tree structure that looks like this:
root
A B
A1 A2 B1 B2
A1.1 A1.2 A2.1 B1.1
the table looks something like this:
id | name |value | parent_id
1 root null null
2 A null 1
3 B null 1
4 A1 null 2
5 A1.1 2 4
6 A1.2 3 4
7 A2 null 2
8 A2.1 5 7
9 B1 null 3
10 B2 1 3
11 B1.1 10 9
.........................
All the non leaf nodes must contain the sum of their children, not only the root node. For example, this is how my desired output looks like:
id | name |value | parent_id
1 root 21 null
2 A 10 1
3 B 11 1
4 A1 5 2
5 A1.1 2 4
6 A1.2 3 4
7 A2 5 2
8 A2.1 5 7
9 B1 10 3
10 B2 1 3
11 B1.1 10 9
how can i achieve this with a fast Postgres query

You need a recursive CTE in the UPDATE statement:
UPDATE tablename AS t
SET "value" = c.value
FROM (
WITH RECURSIVE cte AS(
SELECT t1.id, t1.name, t1.value, t1.parent_id
FROM tablename t1
WHERE NOT EXISTS (SELECT 1 FROM tablename t2 WHERE t2.parent_id = t1.id)
UNION ALL
SELECT t.id, t.name, c.value, t.parent_id
FROM tablename t INNER JOIN cte c
ON c.parent_id = t.id
)
SELECT id, SUM("value") "value"
FROM cte
GROUP BY id
) c
WHERE c.id = t.id;
See the demo.

Using a recursive cte:
with recursive cte(id, name, val, p) as (
select t.id, t.name, t.value, '.'||(select t1.id from tbl t1 where t1.parent_id is null)||'.'||t.id||'.' from tbl t where t.parent_id = (select t1.id from tbl t1 where t1.parent_id is null)
union all
select t.id, t.name, t.value, c.p||t.id||'.' from cte c join tbl t on c.id = t.parent_id
)
select t.id, sum(case when c.val is null then 0 else c.val end) from tbl t
join cte c on c.p like '%.'||t.id||'.%' group by t.id order by t.id
To update your original table with the summed child node values, you can use a subquery in update:
update tbl set value = t1.val from (
select t.id tid, sum(case when c.val is null then 0 else c.val end) val from tbl t
join cte c on c.p like '%.'||t.id||'.%' group by t.id) t1
where t1.tid = id

Related

SELECT all rows where same value in column 'a' and 'b' but do not the same value 'c'

I have a table:
ID | a | b | c |
1 1 1 1
2 1 1 2
3 2 2 1
4 1 2 3
5 2 2 1
I need to select all duplicate rows where I have the same value in column 'a' and 'b' but do not have the same in 'c'
So in this case I should get rows with ID=1,2
I can use GROUP BY to group value 'a' and 'b' and then select it if it count more than 1, but I dont know how to include this check about column 'c'.
Could you help me, please.
I think you want exists:
select t.*
from t
where exists (select 1
from t t2
where t2.a = t.a and t2.b = t.b and
t2.c <> t.c
);
If you just wanted the a/b values, then you could use aggregation:
select a, b
from t
group by a, b
having min(c) <> max(c);
you can also use :
select t1.id from table t1 cross join table t2
where s1.a=s2.a and s1.b=s2.b and s1.c!=s2.c

Hierarchical query to return all children with root parent

In SQL Server, I have a table with sample data as shown below:
SELECT parent_id,child_id FROM tab a
parent_id child_id
--------------------
1 2
2 3
3 4
1 5
5 6
12 13
And I want to fetch data with parent_id for all children like this:
Parent_id Child_id
--------------------
1 2
1 3
1 4
1 5
1 6
12 13
Where first parent is shown for all relevant children. I have tried below,
WITH cte_Recursive( Parent, Child, Level ) AS
(
SELECT T.Parent_id, T.Child_id, 1 AS Level
FROM tab AS T
WHERE NOT EXISTS(SELECT * FROM tab AS TI
WHERE T.Child_id = TI.Parent_id)
UNION ALL
SELECT TR.Parent_id, TR.Child_id, Level + 1
FROM tab AS TR
INNER JOIN cte_Recursive CR ON TR.Child_id = CR.Parent
)
SELECT
Parent, Child
FROM
(SELECT
CR.Parent, CR.Level, iTVF.Child,
max_level = MAX(CR.Level) OVER (PARTITION BY NULL)
FROM
cte_Recursive CR
CROSS APPLY
(SELECT
CRI.Parent, CRI.Child
FROM
cte_Recursive CRI
WHERE
CR.Level >= CRI.Level) iTVF
) AS S
WHERE
Level = max_level
But it does not show the expected result
A query like below will help
See working demo here
; with cte as
(
select t1.parent_id, t1.child_id
from tab t1
left join tab t2 on t1.parent_id = t2.child_id
where t2.parent_id is null
union all
select c.parent_id, t2.child_id
from cte c
join tab t2 on t2.parent_id = c.child_id
)
select *
from cte
order by child_id

SQL Server 2008, how to join tables on different attributes [duplicate]

This question already has answers here:
How to do an inner join on row number in sql server
(2 answers)
Closed 6 years ago.
I have two tables like this:
Table A:
ID VAL
1 10
2 20
3 30
4 40
5 50
Table B:
ID VAL2
sd 50
gh 80
dv 90
bf 100
ww 45
Joined table:
ID Val VAL2
1 10 50
2 20 100
3 30 45
4 40 80
5 50 90
So, ID in table A is matched to an ID in table B
1 - sd, 2 - bf, 3 - ww, 4 - gh, 5 - dv
How can I join these two?
select A.ID, A.VAL, B.Val
from tableA A
**left join tableB B on A.ID = B.ID ??**
Thank you!
One option here, which does not require the use of a temporary table, would be to create an inline table containing the mappings of table A's ID values to table B's ID values.
SELECT t1.ID AS ID
t1.VAL AS Val,
t3.VAL2 AS Val2
FROM tableA t1
INNER JOIN
(
SELECT 1 AS IDA, 'sd' AS IDB
UNION ALL
SELECT 2 AS IDA, 'bf' AS IDB
UNION ALL
SELECT 3 AS IDA, '22' AS IDB
UNION ALL
SELECT 4 AS IDA, 'gh' AS IDB
UNION ALL
SELECT 5 AS IDA, 'dv' AS IDB
) t2
ON t1.ID = t2.IDA
INNER JOIN tableB t3
ON t2.IDB = t3.ID
SELECT a.ID, a.VAL, B.VAL2 FROM
(
SELECT CASE WHEN ID = 'sd' THEN 1
WHEN ID = 'bf' THEN 2
WHEN ID = 'ww' THEN 3
WHEN ID = 'gh' THEN 4
WHEN ID = 'dv' THEN 5
END AS ID, VAL2 FROM tableB
) AS t INNER JOIN tableA a ON a.ID = t.Id

Oracle pl/sql permutation and combination

I am not sure what is the correct search term for this, please let me know if there's already an answer for this.
eg:
I have these data
A
B
C
D
E
What is the best way to calculate the addition of each possible combination? Such as:
A
A+B
A+C
A+D
A+E
A+B+C
A+B+D
A+B+E
A+C+D
A+C+E
A+C+D+E
A+B+C+D
A+B+C+E
A+B+C+D+E
B
B+C
B+D
B+E
B+C+D
B+C+E
B+C+D+E
C
C+D
C+E
...
The list goes on.......
Is there any way to achieve this?
The 5 data are not fixed. I might have 10.. 20 or 50 or 1000 :(
Thank you.
In SQL, you can almost do this with this set of left joins:
select (t1.col + coalesce(t2.col, 0) + coalesce(t3.col, 0) +
coalesce(t4.col, 0) + coalesce(t5.col, 0)
) as sumcombo
from t t1 left join
t t2
on t1.col < t2.col left join
t t3
on t2.col < t3.col left join
t t4
on t3.col < t4.col left join
t t5
on t4.col < t5.col;
It doesn't quite work, because you can never get just "A" for instance. Instead:
with t as (
select col
from table
union all
select NULL
from dual
)
select (t1.col + coalesce(t2.col, 0) + coalesce(t3.col, 0) +
coalesce(t4.col, 0) + coalesce(t5.col, 0)
) as sumcombo
from table t1 left join
t t2
on t1.col < t2.col or t2.col is null left join
t t3
on t2.col < t3.col or t3.col is null left join
t t4
on t3.col < t4.col or t4.col is null left join
t t5
on t4.col < t5.col or t5.col is null;
This can be solved by a hierarchical query. First of all, build a further child column col2 for connection:
-- your test data set
with testdata as
(select 'A' as col from dual
union
select 'B' from dual
union
select 'C' from dual
union
select 'D' from dual
union
select 'E' from dual),
-- create child column
testdata2 as
(select t.col as col1, t.col as col2 from testdata t)
select level, sys_connect_by_path(col1, '/') path
from testdata2 t
connect by prior col1 < col2
order by level, sys_connect_by_path(col1, '/');
result:
1 /A
1 /B
1 /C
1 /D
1 /E
2 /A/B
2 /A/C
2 /A/D
2 /A/E
2 /B/C
2 /B/D
2 /B/E
2 /C/D
2 /C/E
2 /D/E
3 /A/B/C
3 /A/B/D
3 /A/B/E
3 /A/C/D
3 /A/C/E
3 /A/D/E
3 /B/C/D
3 /B/C/E
3 /B/D/E
3 /C/D/E
4 /A/B/C/D
4 /A/B/C/E
4 /A/B/D/E
4 /A/C/D/E
4 /B/C/D/E
5 /A/B/C/D/E

Need sql query for matching with three values

I have a table like below
CAccountID CID NetworkID
1 1 1
2 1 2
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3
9 4 1
10 4 2
I need a query to select all CID having all 3 NetworkID(1,2,3) and don't need to display only 1 and 2 NetworkID.
Output should be like below,
CAccountID CID NetworkID
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3
You can use GROUP BY with JOIN :
select t.*
from table t inner join
( select cid
from table
where NetworkID in (1,2,3)
group by cid
having count(distinct NetworkID) = 3
) tt
on tt.cid = t.cid;
Try this:
select * from my_table t
where exists(select 1 from my_table
where CID = t.CID and NetworkID in (1,2,3)
group by CID
having count(*) = 3)
Try this:
select * from <<tablename>> where cid in(select cid from <<tablename>> group by cid having count(*)=3).
Here the subquery will return you all thouse cid which have 3 rows in your table.
Or if you have more network ids then use of INTERSECT operator can be helpful:
select * from <<tablename>> where cid in (
select cid from <<tablename>> where NetworkID=1
INTERSECT
select cid from <<tablename>> where NetworkID=2
INTERSECT
select cid from <<tablename>> where NetworkID=3
);
INTERSECT operator basically returns all the rows common in the queries. Thus, your data unpredicatbility can be handled in this way
Try xml path.
SELECT *
FROM Table_Name B
WHERE (SELECT [text()] = A.Network FROM Table_Name A WHERE A.CID = B.CID
ORDER BY CID, CAAccount FOR XML PATH('')) = 123
CTE Demo:
; WITH CTE(CAAccount, CID, Network) AS
(
SELECT 1 , 1, 1 UNION ALL
SELECT 2 , 1, 2 UNION ALL
SELECT 3 , 2, 1 UNION ALL
SELECT 4 , 2, 2 UNION ALL
SELECT 5 , 2, 3 UNION ALL
SELECT 6 , 3, 1 UNION ALL
SELECT 7 , 3, 2 UNION ALL
SELECT 8 , 3, 3 UNION ALL
SELECT 9 , 4, 1 UNION ALL
SELECT 10, 4, 2
) SELECT *
FROM CTE B
WHERE (SELECT [text()] = A.Network FROM CTE A WHERE A.CID = B.CID ORDER BY CID, CAAccount FOR XML PATH('')) = 123
Output:
CAAccount CID Network
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3