Postgres - How to achieve UNION behaviour with UNION ALL? - sql

I have a table with parent and child ids.
create table if not exists stack (
parent int,
child int
)
Each parent can have multiple children and each child can have multiple children again.
insert into stack (parent, child) values
(1,2),
(2,3),
(3,4),
(4,5),
(5,6),
(6,7),
(7,8),
(8,9),
(9,null),
(1,7),
(7,8),
(8,9),
(9,null);
The data looks like this.
|parent|child|
|------|-----|
|1 |2 |
|2 |3 |
|3 |4 |
|4 |5 |
|5 |6 |
|6 |7 |
|7 |8 |
|8 |9 |
|9 |NULL |
|1 |7 |
|7 |8 |
|8 |9 |
|9 |NULL |
I'd like to find all children. I can use a recursive cte with a UNION ALL.
with recursive cte as (
select
child
from
stack
where
stack.parent = 1
union
select
stack.child
from
cte
left join stack on
cte.child = stack.parent
where
cte.child is not null
)
select * from cte;
This gives me the result I'd like to achieve.
|child|
|-----|
|2 |
|7 |
|3 |
|8 |
|4 |
|9 |
|5 |
|NULL |
|6 |
However I'd like to include the depth / level and also the path for each node. I can do this using a different recursive cte.
with recursive cte as (
select
parent,
child,
0 as level,
array[parent,
child] as path
from
stack
where
stack.parent = 1
union all
select
stack.parent,
stack.child,
cte.level + 1,
cte.path || stack.child
from
cte
left join stack on
cte.child = stack.parent
where
cte.child is not null
)
select * from cte;
That gives me this data.
|parent|child|level|path |
|------|-----|-----|--------------------|
|1 |2 |0 |{1,2} |
|1 |7 |0 |{1,7} |
|2 |3 |1 |{1,2,3} |
|7 |8 |1 |{1,7,8} |
|7 |8 |1 |{1,7,8} |
|3 |4 |2 |{1,2,3,4} |
|8 |9 |2 |{1,7,8,9} |
|8 |9 |2 |{1,7,8,9} |
|8 |9 |2 |{1,7,8,9} |
|8 |9 |2 |{1,7,8,9} |
|4 |5 |3 |{1,2,3,4,5} |
|9 | |3 |{1,7,8,9,} |
|9 | |3 |{1,7,8,9,} |
|9 | |3 |{1,7,8,9,} |
|9 | |3 |{1,7,8,9,} |
|9 | |3 |{1,7,8,9,} |
|9 | |3 |{1,7,8,9,} |
|9 | |3 |{1,7,8,9,} |
|9 | |3 |{1,7,8,9,} |
|5 |6 |4 |{1,2,3,4,5,6} |
|6 |7 |5 |{1,2,3,4,5,6,7} |
|7 |8 |6 |{1,2,3,4,5,6,7,8} |
|7 |8 |6 |{1,2,3,4,5,6,7,8} |
|8 |9 |7 |{1,2,3,4,5,6,7,8,9} |
|8 |9 |7 |{1,2,3,4,5,6,7,8,9} |
|8 |9 |7 |{1,2,3,4,5,6,7,8,9} |
|8 |9 |7 |{1,2,3,4,5,6,7,8,9} |
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
|9 | |8 |{1,2,3,4,5,6,7,8,9,}|
My problem is that I have a lot of duplicate data. I'd like to get the same result as the UNION query but with the level and the path.
I tried something like
where
cte.child is not null
and stack.parent not in (cte.parent)
or
where
cte.child is not null
and not exists (select parent from cte where cte.parent = stack.parent)
but the first does not change anything and the second returns an error.
ERROR: recursive reference to query "cte" must not appear within a subquery
Any ideas? Thank you very much!

Your problem is inappropriate table data. Your table contains the information that 8 is a direct child to 7 twice for instance. I suggest you remove the duplicate data and implement a unique constraint on the pairs.
If you cannot do so for some reason, make the rows distinct in your query:
with recursive
good_stack as (select distinct * from stack)
,cte as
(
select
parent,
child,
0 as level,
array[parent,
child] as path
from good_stack
where good_stack.parent = 1
union all
select
good_stack.parent,
good_stack.child,
cte.level + 1,
cte.path || good_stack.child
from cte
left join good_stack on cte.child = good_stack.parent
where cte.child is not null and good_stack.child is not null
)
select * from cte;
Demo: https://dbfiddle.uk/?rdbms=postgres_13&fiddle=acb1d7a1a1d26c3fd9caf0e7dedc12b2
(You may also make the columns not nullable. The entries 9|null add no information. If the table were lacking these entries, 9 would still be without a child.)

Related

SQL for Selecting Data with Codition

in need a fast SQL for selcting my Data.
I have a Table a which look like the following:
And another Table b which look like the following:
In au can specify which data from b i need. Its the Flag a.kba_inkl (I = Inclusiv, E = Excluisiv)
The Key From both Tables are the first four Columns.
The First Row from a means all Artikel from b which has b.art_be = a.kba_be.
The Second Row from a means without Artickles from b where b.art_be = a.kba_be and b.art_wg = a.kba_wg. And so on.
In a: 0 means ALL (From 1-99)
In b can't appear a 0
Table a:
|------|------|------|-------|--------|
|kba_be|kba_wg|kba_ag|kba_anr|kba_inkl|
|------|------|------|-------|--------|
|10 |0 |0 |0 |I |
|------|------|------|-------|--------|
|10 |10 |0 |0 |E |
|------|------|------|-------|--------|
|10 |20 |30 |0 |E |
|------|------|------|-------|--------|
|20 |10 |0 |0 |I |
|------|------|------|-------|--------|
|20 |0 |0 |0 |E |
|------|------|------|-------|--------|
Table b:
|------|------|------|-------|
|art_be|art_wg|art_ag|art_anr|
|------|------|------|-------|
|10 |20 |30 |40 |
|------|------|------|-------|
|10 |10 |1 |5 |
|------|------|------|-------|
|10 |5 |30 |20 |
|------|------|------|-------|
|10 |10 |80 |50 |
|------|------|------|-------|
|10 |60 |30 |60 |
|------|------|------|-------|
|20 |10 |50 |50 |
|------|------|------|-------|
|20 |60 |30 |60 |
|------|------|------|-------|
Result:
|------|------|------|-------|
|art_be|art_wg|art_ag|art_anr|
|------|------|------|-------|
|10 |5 |30 |20 |
|------|------|------|-------|
|10 |60 |30 |60 |
|------|------|------|-------|
|20 |10 |50 |50 |
|------|------|------|-------|
Does anybody has an Idea?
Many thanks in advance.
EDIT: Maybe the Table a should be grouped like this:
|------|------|------|-------|--------|
|kba_be|kba_wg|kba_ag|kba_anr|kba_inkl|
|------|------|------|-------|--------|
|10 |0 |0 |0 |I |
|------|------|------|-------|--------|
|20 |0 |0 |0 |E |
|------|------|------|-------|--------|
|10 |10 |0 |0 |E |
|------|------|------|-------|--------|
|20 |10 |0 |0 |I |
|------|------|------|-------|--------|
|10 |20 |30 |0 |E |
|------|------|------|-------|--------|
And then loop through the rows.
So there are 2 parts of the query inclusive and exclusive.
The exclusive can be implemented using minus operator.
with a as
(
select 10 kba_be,0 kba_wg,0 kba_ag,0 kba_anr,'I' kba_incl from dual union all
select 10 kba_be,10 kba_wg,0 kba_ag,0 kba_anr,'E' kba_incl from dual union all
select 10 kba_be,20 kba_wg,30 kba_ag,0 kba_anr,'E' kba_incl from dual
),
b as
(
select 10 art_be,20 art_wg,30 art_ag,0 art_anr from dual union all
select 10 art_be,10 art_wg,1 art_ag,5 art_anr from dual union all
select 10 art_be,5 art_wg,30 art_ag,20 art_anr from dual union all
select 10 art_be,10 art_wg,80 art_ag,50 art_anr from dual union all
select 10 art_be,60 art_wg,30 art_ag,60 art_anr from dual
)
select b.* from
a,b
where
a.kba_incl = 'I'
and b.art_be = a.kba_be
and (b.art_wg = a.kba_wg or a.kba_wg=0)
and (b.art_ag = a.kba_ag or a.kba_ag=0)
minus
select b.* from
a,b
where
a.kba_incl = 'E'
and b.art_be = a.kba_be
and (b.art_wg = a.kba_wg or a.kba_wg=0)
and (b.art_ag = a.kba_ag or a.kba_ag=0)
result
10 5 30 20
10 60 30 60

Oracle: Recursively self referential join with nth level record

I have self referential table like this:
id |level | parent_id
----------------------
1 |1 |null
2 |1 |null
3 |2 |1
4 |2 |1
5 |2 |2
6 |3 |5
7 |3 |3
8 |4 |7
9 |4 |6
------------------------
I need nth level parent in result. for example 2nd level parent
id |level | parent_id| second_level_parent_id
------------------------------------------------
1 |1 |null |null
2 |1 |null |null
3 |2 |1 |null
4 |2 |1 |null
5 |2 |2 |null
6 |3 |5 |5
7 |3 |3 |3
8 |4 |7 |3
9 |4 |6 |5
-------------------------------------------------
this works for me.
SELECT m.*,
CONNECT_BY_ROOT id AS second_level_parent_id
FROM my_table m
WHERE CONNECT_BY_ROOT level =2
CONNECT BY prior id = parent_id;
thanks #Jozef DĂșc

SQL: Need to SUM column for each type

How can I find the SUM of all scores for the minimum date of each lesson_id please:
-----------------------------------------------------------
|id |uid |group_id |lesson_id |game_id |score |date |
-----------------------------------------------------------
|1 |145 |1 |1 |0 |40 |1391627323 |
|2 |145 |1 |1 |0 |80 |1391627567 |
|3 |145 |1 |2 |0 |40 |1391627323 |
|4 |145 |1 |3 |0 |30 |1391627323 |
|5 |145 |1 |3 |0 |90 |1391627567 |
|6 |145 |1 |4 |0 |20 |1391628000 |
|7 |145 |1 |5 |0 |35 |1391628000 |
-----------------------------------------------------------
I need output:
-------------------
|sum_first_scores |
-------------------
|165 |
-------------------
I have this so far, which lists the score for each minimum date, per lesson, but I need to sum those results as above:
SELECT lesson_id, MIN(date), score AS first_score FROM cdu_user_progress
WHERE cdu_user_progress.uid = 145
GROUP BY lesson_id
You can identify the first score as the one where no earlier record exists. Then just take the sum:
select sum(score)
from edu_user_progress eup
where cdu_user_progress.uid = 145 and
not exists (select 1
from edu_user_progress eup2
where eup2.uid = eup.uid and
eup2.lesson_id = eup.lesson_id and
eup2.date < eup.date
);
This assumes that the minimum date for the lesson id has only one score.

SQL: triple-nested many to many query

I'm trying to fix my nested query, I have these tables:
cdu_groups_blocks
------------------------
|id |group_id |block_id|
------------------------
|1 |1 |1 |
|2 |1 |2 |
|3 |1 |3 |
------------------------
cdu_blocks: cdu_blocks_sessions:
-------------------------- ---------------------------
|id |name |enabled | |id |block_id |session_id |
-------------------------- ---------------------------
|1 |block_1 |1 | |1 |1 |1 |
|2 |block_2 |1 | |2 |1 |2 |
|3 |block_3 |1 | |3 |2 |3 |
-------------------------- |4 |2 |4 |
|5 |3 |5 |
|6 |3 |6 |
---------------------------
cdu_sessions: cdu_sessions_lessons
-------------------------- ----------------------------
|id |name |enabled | |id |session_id |lesson_id |
-------------------------- ----------------------------
|1 |session_1 |1 | |1 |1 |1 |
|2 |session_2 |1 | |2 |1 |2 |
|3 |session_3 |1 | |3 |2 |3 |
|4 |session_4 |0 | |4 |4 |4 |
|5 |session_5 |1 | |5 |4 |5 |
|6 |session_6 |0 | |6 |5 |6 |
-------------------------- ----------------------------
cdu_lessons:
--------------------------
|id |name |enabled |
--------------------------
|1 |lesson_1 |1 |
|2 |lesson_2 |1 |
|3 |lesson_3 |1 |
|4 |lesson_4 |1 |
|5 |lesson_5 |0 |
|6 |lesson_6 |0 |
--------------------------
It's a many-to-many which links to another many-to-many which links to another many-to-many.
Essentially I want to get all lesson_id(s) associated with a particular group_id.
So far I have this, but it's throwing up various SQL errors:
SELECT b.* FROM
(
SELECT block_id, group_id FROM cdu_groups_blocks
JOIN cdu_blocks ON cdu_blocks.id = cdu_groups_blocks.block_id
WHERE group_id = $group_id
AND enabled = 1
) AS b
INNER JOIN
(
SELECT l.* FROM
(
SELECT session_id, block_id FROM cdu_blocks_sessions
JOIN cdu_sessions ON cdu_sessions.id = cdu_blocks_sessions.session_id
AND enabled = 1
) AS s
INNER JOIN
(
SELECT lesson_id, session_id FROM cdu_sessions_lessons
JOIN cdu_lessons ON cdu_lessons.id = cdu_sessions_lessons.lesson_id
WHERE enabled = 1
) AS l
WHERE s.session_id = l.session_id
) AS sl
WHERE sl.block_id = g.block_id
Any help would be much appreciated!
sl.block_id is from s table in your first select inside sl subselect.
Just get it. Change:
SELECT l.* FROM ...
to
SELECT l.*, s.block_id FROM ...

SQL - get rows where column is greater than certain amount

I need to get the sum of the scores for the first of each lesson_id, but I also need the overall min and max scores for all lesson_ids as well as some other info:
cdu_groups:
----------------
|id |name |
----------------
|1 |group_1 |
|2 |group_2 |
----------------
cdu_user_progress145:
-----------------------------------------------------------
|id |uid |group_id |lesson_id |game_id |score |date |
-----------------------------------------------------------
|1 |145 |1 |1 |0 |40 |1391627323 |
|2 |145 |1 |1 |0 |80 |1391627567 |
|3 |145 |1 |2 |0 |40 |1391627323 |
|4 |145 |1 |3 |0 |30 |1391627323 |
|5 |145 |1 |3 |0 |90 |1391627567 |
|6 |145 |1 |4 |0 |20 |1391627323 |
|7 |145 |1 |5 |0 |35 |1391627323 |
-----------------------------------------------------------
I need this output:
-----------------------------------------------------------------
|name |group_id |min_score |max_score |... |sum_first_scores |
-----------------------------------------------------------------
|group_1 |1 |20 |90 |... |165 |
-----------------------------------------------------------------
SELECT
cdu_groups.*,
MAX(score) AS max_score,
MIN(score) AS min_score,
COUNT(DISTINCT(lesson_id)) AS scored_lesson_count,
COUNT(DISTINCT CASE WHEN score >= 75 then lesson_Id ELSE NULL END) as passed_lesson_count,
SUM(first_scores.first_score) AS sum_first_scores
FROM cdu_user_progress
JOIN cdu_groups ON cdu_groups.id = cdu_user_progress.group_id
JOIN
(
SELECT lesson_id, MIN(date), score AS first_score FROM cdu_user_progress
WHERE cdu_user_progress.uid = 145
GROUP BY lesson_id
) AS first_scores ON first_scores.lesson_id = cdu_user_progress.lesson_id
WHERE cdu_user_progress.uid = 145
I'm getting this error though:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SUM(first_scores.first_score) AS sum_first_scores FROM cdu_user_progress ' at line 7