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