Adding a sequence of cells chained by id reference in postgresql - sql

I have a table foo with columns id, number_of_foos, and parent_foo. number_of_foos is an integer and parent_foo is a reference to another row's id. Each row will have an integer in parent_foo or null if it has no parent.
id | number_of_foos | parent_foo
---+----------------+-----------
1 | 10 | null
2 | 7 | null
3 | 6 | null
4 | 13 | 1
5 | 9 | 3
6 | 1 | 4
Given an id number, I want to find the total amount of "foo"s in the "foo chain", i.e. the count of foos for that id and its parent (and its parent, and its parent...). So for example, the total amount of foos WHEN id = 6 is 1 + 13 + 10 = 24.
Okay.
BUT, I also want to subtract 1 for each parent. So WHEN id = 6 is actually (1 + 13 + 10) - 2 = 22.
Is this possible?

demo: db<>fiddle
WITH RECURSIVE rec AS (
SELECT
parent_foo,
number_of_foos
FROM foo
WHERE id = 6
UNION
SELECT
f.parent_foo,
r.number_of_foos + f.number_of_foos - 1
FROM foo f
JOIN rec r ON f.id = r.parent_foo
)
SELECT number_of_foos
FROM rec
WHERE parent_foo IS NULL;
Using a WITH RECURSIVE CTE you could run through your data structure. A recursive CTE consists of two parts:
Starting point: Selecting the first row. In your case the parent of the first value and its number_of_foos value.
The recursion part where you select the row with id == parent_foo of last recursion. In this step you can integrate the adding part as well: Just adding the new numbers_of_foo - 1 to the last one.
Finally you can give out the row without any parent which is the grandmost parent of your starting value and it contains the expected sum.

Related

PostgreSQL finding nearest parent value of a certain level

*I apologize, my tables displayed correctly when I was writing this question and after publishing the formatting looks off. trying to fix that now
I am trying to write a query in postgresql that would return, for any given child value, the nearest parent value that has reached a certain rank. Currently, I have this query, which displays the entire hierarchical path for any given child value-
WITH RECURSIVE tree AS (
SELECT "ChildDisplayID",
"ParentID",
"Rank",
1 as level
FROM table1
WHERE "ChildDisplayID" = {{some ChildID}}
UNION ALL
SELECT t1."ChildDisplayID",
t1."ParentID",
t1."Rank",
t.level + 1
FROM table1 t1
JOIN tree t ON t."ParentID" = t1."ChildDisplayID"
)
SELECT *
FROM tree
What I want to do is have in a single row that displays the child ID and the parent ID of the nearest parent whose rank is "Partner". For example, here is the output I am currently getting:
| ChildID | ParentID | Rank | Level |
|---------|----------|------|-------|
| 6 | 5 |Associate Manager| 1 |
| 5 | 4 |Manager| 2 |
| 4 | 3 |Associate Partner| 3 |
| 3 | 2 |Partner| 4 |
| 2 | 1 |Partner| 5 |
| 1 | |CEO| 6 |
Here is the output I want:
|ChildID | Nearest Partner | Rank |
|--------|----------|------|
|6 |3 | Partner |
What is the best way to do this?
You can put a stop condition on the first matching partner in the recursion, then filter the result:
WITH RECURSIVE tree AS (
SELECT "ChildDisplayID" as initialid, "ChildDisplayID", "ParentID", "Rank", 1 as level
FROM table1
WHERE "ChildDisplayID" = {{some ChildID}}
UNION ALL
SELECT t.initialid, t1."ChildDisplayID", t1."ParentID", t1."Rank", t.level + 1
FROM table1 t1
INNER JOIN tree t ON t."ParentID" = t1."ChildDisplayID"
WHERE t."Rank" <> 'Partner'
)
SELECT *
FROM tree
WHERE "Rank" = 'Partner'
It seems like you have a hierarchy where each child has just one parent, so there should be only one match, or no match at all.

How to write a CTE to aggregate hierarchical values

I want to write expressions in sqlite to process a tree of items, starting with the leaf nodes (the bottom) and proceeding back to their parents all the way to the root node (the top), such that each parent node is updated based on the content of its children. I've been able to write a CTE that does something similar, but isn't yet totally correct.
I have a simple table "test1" containing some nested values:
id | parent | value | total
---+--------+--------------
1 | NULL | NULL | NULL
2 | 1 | NULL | NULL
3 | 2 | NULL | NULL
4 | 3 | 50 | NULL
5 | 3 | 50 | NULL
6 | 2 | 60 | NULL
7 | 6 | 90 | NULL
8 | 6 | 60 | NULL
Rows may have children who reference their parent via their parent field. Rows may have a value of their own as well as child rows, or they may simply be parents without values (ie. "wrappers"). The leafs would be the rows without any children.
For each row I'd like to calculate the total, as the average or the row's value (if not null) AND its children's totals. This should start with the leaf nodes and proceed up the tree to their parents, all the way to the root node at the top of the data hierarchy.
I've tried a number of variations of CTE's but am having difficulty writing one that will recursively calculate these totals from the bottom up.
Currently, I have:
UPDATE test1 SET total = (
WITH RECURSIVE cte(cte_id,cte_parent,cte_value,cte_total) AS (
SELECT test1.id, test1.parent, test1.value, test1.total
UNION ALL
select t.id, t.parent, t.value, t.total from test1 t, cte
WHERE cte.cte_id=t.parent
) SELECT AVG(cte_value) FROM cte
);
which produces:
id | parent | value | total
---+--------+-------+------
1 | NULL | NULL | 62
2 | 1 | NULL | 62
3 | 2 | NULL | 50
4 | 3 | 50 | 50
5 | 3 | 50 | 50
6 | 2 | 60 | 70
7 | 6 | 90 | 90
8 | 6 | 60 | 60
Looking at the top-most rows, this is not quite right, since it's taking an average of not only the row's immediate children, but of all the row's descendants. This causes row 2 for example to have a total of 62 instead of 60. The expected results should set rows 2's total to 60, as the average of its immediate child rows 3 and 6. Row 1's total would be 60 as well.
How can I calculate a "total" value for each row based on an average of the row's value and the values of it's immediate children only, while ensuring the upper levels of the hierarchy are correctly populated based on the calculated totals of their children?
It turns out that a very similar question and solution was posted here:
How can I traverse a tree bottom-up to calculate a (weighted) average of node values in PostgreSQL?
Since sqlite3 doesn't let you create functions, the example using a recursive CTE applies:
with recursive cte(id, parent, value, level, total) as (
select
t.id, t.parent, t.value,
0,
t.value as total
from test1 t
where not exists (
select id
from test1
where parent = t.id)
union all
select
t.id, t.parent, t.value,
c.level+1,
case when t.value is null then c.total else t.value end
from test1 t
join cte c on t.id=c.parent
)
select id, parent, value, avg(total) total from (
select
id, parent, value, level, avg(total) total
from cte
group by id,parent,level
)
group by id, parent
order by id

SQL Insert when not exist Update if exist with mutliple rows in Target-Table

I have two tables, where table A has to be Updated or insert a row base on existing. I tried this by using JOINS EXCEPT and MERGE statement but I have one problem i can't solve. so here is an example :
Table A (Attribut-Table)
attr | attrValue | prodID
--------------------------
4 | 2 | 1
--------------------------
3 | 10 | 2
--------------------------
1 | 7 | 2
--------------------------
3 | 10 | 3
--------------------------
6 | 9 | 3
--------------------------
1 | 4 | 3
--------------------------
Table P(Product-Table)
prodID | stock |
------------------
1 | 1
------------------
2 | 0
------------------
3 | 1
------------------
4 | 1
------------------
Now what i would like to do the following in SQL:
All products, that has Stock > 0 should have an entry in Table A with attr = 6 and attrValue = 9
All products, that has Stock < 1 should have an entry in Table A with attr = 6 and attrValue = 8
i need a SQL Query to do that because my problem is that there are multiple entries for a prodID in Table A
That is what i am thinking of:
Fist check if any entry for the prodID(in Table B) exist in Table A, if not INSERT INTO Table A ( attr=6 and, attrValue = 8/9 (depends on Stock), prodID
If there is already an entry for the prodID in Table A with the attr = 6, then Update this row and set attrValue to 8/9 (depending on stock)
so I am looking for a translation of "my thoughts" to a sqlQuery
thanks for helping.
(using: SQL SERVER Express 2012 and HEIDI SQL for management)
Since your "attr 6" row is 100 % derivable from the state of the P table, it is a poor idea to store that row redundantly in A.
This is better :
(1) Define a first view ATTR6_FOR_P as SELECT prodID, 6 as attr, CASE (...) as attrValue from P. The CASE expression chooses the value 8 or 9 according to stock value in P.
(2) Define a second view A_EXT as A UNION ATTR6_FOR_P. (***)
Now changes in stock will always immediately be reflected in A_EXT without having to update explicitly.
(***) but beware of column ordering because SQL UNION does not match columns by name but by ordinal position instead.

Recursive SELECT with stop condition in SQL?

My table called element looks like this:
id | successor | important
----------------------------
1 | NULL | 0
2 | 4 | 1
3 | 5 | 0
4 | 8 | 0
5 | 6 | 1
6 | 7 | 0
7 | NULL | 0
8 | 10 | 1
9 | 10 | 0
10 | NULL | 0
I start with an element’s ID. Each element may or may not have a succeeding element. So given any element ID I may build a chain of elements from 0..n elements depending on its successors and successor-successors, and so on.
Let’s say my starting ID is 2. This results in the following chain:
2 -> 4 -> 8 -> 10
Now I want to ask this question: Does a specific element chain contain at least one element where important == 1?
In pseudo-code a function realizing this without unneccessary checks may look like this:
boolean chainIsImportant(element)
{
if (element.important == 1) {
return true;
}
if (element.successor != NULL) {
return chainIsImportant(element.successor);
}
return false;
}
I guess this can be realized with WITH RECURSIVE, right? How can I stop recursion, once an element with important == 1 was found?
This is typically done by aggregating the columns in question and adding a condition on the join in the recursive part of the CTE:
with recursive all_elements as (
select id, successor, important, array[important] as important_elements
from elements
where successor is null
union all
select c.id, c.successor, c.important, p.important_elements||c.important
from elements c
join all_elements p on c.successor = p.id
where 1 <> all(p.important_elements)
)
select *
from all_elements;
Note that the condition is "flipped" because the where clause defines those rows that should be included.
CREATE TABLE booltree
( id INTEGER NOT NULL PRIMARY KEY
, successor INTEGER REFERENCES booltree(id)
, important Boolean NOT NULL
);
INSERT INTO booltree(id , successor , important) VALUES
( 1, NULL , False)
,(2, 4 , True)
,(3, 5 , False)
,(4, 8 , False)
,(5, 6 , True)
,(6, 7 , False)
,(7, NULL , False)
,(8, 10 , True)
,(9, 10 , False)
,(10, NULL , False)
;
-- SELECT * FROM booltree;
WITH RECURSIVE rec AS (
SELECT id, important
FROM booltree
WHERE successor IS NULL
UNION ALL
SELECT bt.id, GREATEST(rec.important, bt.important) AS important
FROM booltree bt
JOIN rec ON bt.successor = rec.id
)
SELECT id, important
FROM rec
ORDER BY important, id;
Result:
CREATE TABLE
INSERT 0 10
id | important
----+-----------
1 | f
6 | f
7 | f
9 | f
10 | f
2 | t
3 | t
4 | t
5 | t
8 | t
(10 rows)
Note: IMHO the recursion cannot be stopped once a True importance is found (basically, because LEFT JOINS are not allowed in RECURSIVE UNIONS)
But if you are looking for exactly one given id (or a set of them) then maybe you could use that as the start condition, and search the tree upwards.

SQL Order random rows based on 2 columns

How to sort this table in Oracle9:
START | END | VALUE
A | F | 1
D | H | 9
F | C | 8
C | D | 12
To make it look like this?:
START | END | VALUE
A | F | 1
F | C | 12
C | D | 8
D | H | 9
Goal is to start every next row with the end from the previous row.
This cannot be done with the order by clause alone, as it would have to find the record without a predecessor first, then find the next record comparing end and start column of the two records etc. This is an iterative process for which you need a recursive query.
That recursive query would find the first record, then the next and so on, giving them sequence numbers. Then you'd use the result and order by those generated numbers.
Here is how to do it in standard SQL. This is supported from Oracle 11g onwards only, however. In Oracle 9 you'll have to use CONNECT BY with which I am not familiar. Hopefully you or someone else can convert the query for you:
with chain(startkey, endkey, value, pos) as
(
select startkey, endkey, value, 1 as pos
from mytable
where not exists (select * from mytable prev where prev.endkey = mytable.startkey)
union all
select mytable.startkey, mytable.endkey, mytable.value, chain.pos + 1 as pos
from chain
join mytable on mytable.startkey = chain.endkey
)
select startkey, endkey, value
from chain
order by pos;
UPDATE: As you say the data is cyclic, you'd have to change above query so as to start with an arbitrarily chosen row and stop when through:
with chain(startkey, endkey, value, pos) as
(
select startkey, endkey, value, 1 as pos
from mytable
where rownum = 1
union all
select mytable.startkey, mytable.endkey, mytable.value, chain.pos + 1 as pos
from chain
join mytable on mytable.startkey = chain.endkey
)
cycle startkey set cycle to 1 default 0
select startkey, endkey, value
from chain
where cycle = 0
order by pos;