Correlated row-generating query in Oracle - sql

Given this starting CTE:
WITH Sections AS (
SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
UNION ALL SELECT 2, 0, 2 FROM DUAL
UNION ALL SELECT 3, 1, 1 FROM DUAL
),
How do I generate a result set that has as many rows per row in Section as there are numbers between StartUnit and EndUnit (inclusive), with values ascending?
That is, I'd like to see a result set of:
Section Unit
1 1
1 2
1 3
1 4
1 5
2 0
2 1
2 2
3 1
Note that some of the values in the Sections CTE will be parameters, so it's not as simple as extending my UNIONs to the right number.
UPDATE
I've thought about this a little more and have another guideline. I'll take any answer that's correct, but was particularly hoping for someone to possibly show how to do this with CONNECT BY PRIOR and without an extra CTE in the middle...
I realized I could change the CTE to this:
WITH Sections AS (
SELECT 1 Section, LEVEL Unit FROM DUAL CONNECT BY LEVEL <= 5
UNION ALL SELECT 2, LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 3
UNION ALL SELECT 3, 1 FROM DUAL CONNECT BY LEVEL <= 1
)
But I'm leaning away from that here because it may come from a table rather than be selected from DUAL. So let's assume the Sections CTE is in fact a simple query from a table, something like:
SELECT Section, StartUnit, EndUnit FROM SectionData WHERE CallerID = 7
And the original question still stands.

Try this:
WITH Sections AS (
SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
UNION ALL SELECT 2, 0, 2 FROM DUAL
UNION ALL SELECT 3, 1, 1 FROM DUAL
),
Numbers AS (
SELECT ROWNUM-1 n
FROM DUAL
CONNECT BY LEVEL < 1000
)
select section, n
from sections, numbers
where n between startunit and endunit
order by section, n;
You may want to adjust the value of 1000 I used.

Related

How to hide rows where aggregate functions are used

let’s imagine here is my report:
select 1 from dual
union all
select 2 from dual
union all
select 3 from dual
union all
select 4 from dual
Output:
1
2
3
4
If e.g. i want to hide part of the report in an easy way I simply add 1 = 2 to where clause:
select 1 from dual
union all
select 2 from dual
union all
select 3 from dual where 1 = 2
union all
select 4 from dual
Output:
1
2
4
Cool! However, when select uses aggregate functions, this trick does not help. The row is generated anyway:
select 1 from dual
union all
select 2 from dual
union all
select max(3) from dual where 1 = 2
union all
select 4 from dual
Output:
1
2
(null)
4
Someone maybe know a simple, easy way to hide such rows?
An aggregation query with no group by always returns exactly one row -- even if no rows are being aggregated.
You want to filter after the aggregation, so use having:
select max(3) from dual having 1 = 2

Select parent of parent when is not null in oracle

I have these rows in the specific parent child table:
Id - parentId
1 - null
2 - null
3 - 2
4 - 2
5 - 4
6 - 1
Indeed for example i want to enter 3 and get 2, enter 5 and get 2, and enter 6 and get 1, how can i do that?
You need a hierarchical query (connect by ...) Something like this:
with
tbl (id, parentid) as (
select 1, null from dual union all
select 2, null from dual union all
select 3, 2 from dual union all
select 4, 2 from dual union all
select 5, 4 from dual union all
select 6, 1 from dual
)
select id
from tbl
where connect_by_isleaf = 1
start with id = 5
connect by id = prior parentid
;
ID
-----
2
"In real life" you would have a table tbl with columns id and parentid (I simulated that in the with clause, but that is just for convenience; remove it first.) And you wouldn't hard-code the starting node; you would likely use a bind variable in the start with clause.
The key here is the where clause, using the connect_by_isleaf pseudocolumn.

Flag individuals that share common features with Oracle SQL

Consider the following table:
ID Feature
1 1
1 2
1 3
2 3
2 4
2 6
3 5
3 10
3 12
4 12
4 18
5 10
5 30
I would like to group the individuals based on overlapping features. If two of these groups again have overlapping features, I would consider both as one group. This process should be repeated until there are no overlapping features between groups. The result of this procedure on the table above would be:
ID Feature Flag
1 1 A
1 2 A
1 3 A
2 3 A
2 4 A
2 6 A
3 5 B
3 10 B
3 12 B
4 12 B
4 18 B
5 10 B
5 30 B
So actually the problem I am trying to solve is finding connected components in a graph. Here [1,2,3] is the graph with ID 1 (see https://en.wikipedia.org/wiki/Connectivity_(graph_theory)). The problem is equivalent to this problem, however I would like to solve it with Oracle SQL.
Here is one way to do this, using a hierarchical ("connect by") query. The first step is to extract the initial relationships from the base data; the hierarchical query is built on the result from this first step. I added one more row to the inputs to illustrate a node that is a connected component by itself.
You marked the connected components as A and B - of course, that won't work if you have, say, 30,000 connected components. In my solution, I use the minimum node name as the marker for each connected component.
with
sample_data (id, feature) as (
select 1, 1 from dual union all
select 1, 2 from dual union all
select 1, 3 from dual union all
select 2, 3 from dual union all
select 2, 4 from dual union all
select 2, 6 from dual union all
select 3, 5 from dual union all
select 3, 10 from dual union all
select 3, 12 from dual union all
select 4, 12 from dual union all
select 4, 18 from dual union all
select 5, 10 from dual union all
select 5, 30 from dual union all
select 6, 40 from dual
)
-- select * from sample_data; /*
, initial_rel(id_base, id_linked) as (
select distinct s1.id, s2.id
from sample_data s1 join sample_data s2
on s1.feature = s2.feature and s1.id <= s2.id
)
-- select * from initial_rel; /*
select id_linked as id, min(connect_by_root(id_base)) as id_group
from initial_rel
start with id_base <= id_linked
connect by nocycle prior id_linked = id_base and id_base < id_linked
group by id_linked
order by id_group, id
;
Output:
ID ID_GROUP
------- ----------
1 1
2 1
3 3
4 3
5 3
6 6
Then, if you need to add the ID_GROUP as a FLAG to the base data, you can do so with a trivial join.

Usage of combinatorics in Oracle SQL

Could use some help or insights, cause I'm going nuts..
Situation: I have a table of players ID's, with values 1,2,3.. to 7.
Objective: Want to create a roster of 4 players, from available players (in our case there are 7 of them). Its a classical combinatoric task, we need to calculate C(k,n). In our case C(4,7)=840/24=35. So, there are possible 35 ways to build a roster. I want to create a table of rosters with player ID's. Here's the current script, that builds the current roster table:
with comb_tbl as(
select
tmp_out.row_num,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,1) plr_id_1,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,2) plr_id_2,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,3) plr_id_3,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,4) plr_id_4
from(
select
rownum row_num,
substr(tmp.combinations,2) comb_sets
from(
select
sys_connect_by_path(plr.plr_id, ',') combinations
from(
select 1 plr_id from dual union
select 2 plr_id from dual union
select 3 plr_id from dual union
select 4 plr_id from dual union
select 5 plr_id from dual union
select 6 plr_id from dual union
select 7 plr_id from dual) plr
connect by nocycle prior plr.plr_id != plr.plr_id) tmp
where
length(substr(tmp.combinations,2)) = 7) tmp_out)
select
tmp1.*
from
comb_tbl tmp1
Problem:Its creates 840 possibilities, but I need to remove the "identical" ones, for example roster (1,2,3,4) is "identical" to roster (2,1,3,4). Any insights/comments/critics are welcome. Maybe the approach itself is wrong?
There is a 1-1 correspondence between possible rosters and ORDERED subsets of four elements of the seven-element set.
In your CONNECT BY clause, you only check that the player id's be different, rather than that they be in increasing order. Change != to < in CONNECT BY and it will work. (Also, you won't need NOCYCLE anymore.)
This could be a join way:
with plr(plr_id) as
( select level from dual connect by level <= 7)
select p1.plr_id, p2.plr_id, p3.plr_id, p4.plr_id
from plr p1
inner join plr p2
on(p1.plr_id < p2.plr_id)
inner join plr p3
on(p2.plr_id < p3.plr_id)
inner join plr p4
on(p3.plr_id < p4.plr_id)
For example, with n=5 you would have:
PLR_ID PLR_ID PLR_ID PLR_ID
---------- ---------- ---------- ----------
1 2 3 4
1 2 3 5
1 2 4 5
1 3 4 5
2 3 4 5
This is not an answer; it's merely a continuation of my earlier comment:
By making those additional changes, your query could end up something like:
SELECT regexp_substr(tmp_out.comb_sets,'[^,]+',1,1) plr_id_1,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,2) plr_id_2,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,3) plr_id_3,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,4) plr_id_4
FROM (SELECT sys_connect_by_path(plr.plr_id, ',') comb_sets,
LEVEL lvl
FROM (select 1 plr_id from dual union all
select 2 plr_id from dual union all
select 3 plr_id from dual union all
select 4 plr_id from dual union all
select 5 plr_id from dual union all
select 6 plr_id from dual union all
select 7 plr_id from dual) plr
CONNECT BY PRIOR plr.plr_id < plr.plr_id
AND LEVEL <= 4) tmp_out
WHERE lvl = 4;
which is, IMO, easier to read and understand than your original query.

Retrieve Parents and Child Rows In Single Row

I have a lot of SQL experience but this one is beyond my understanding. I have a table such as:
ID NAME PARENT_ID
1 A 0
2 B 1
3 C 1
4 D 0
5 E 4
6 F 4
7 G 4
And I need to write a single SQL statement that will return all the parents and their children in a single row:
ID PARENT CHILD_1 CHILD_2 CHILD_3 ... CHILD_N
1 A B C
4 D E F G
I do not know how many children each parent has before hand - it is variable.
Is there anyway to do this in a single SQL statement?
Thanks.
You can use some of the very cool "dynamic pivot" PL/SQL solutions out there (which I don't recommend for production code -- they work 99% but fail for some odd-ball cases, in my experience).
Otherwise, you need to tell Oracle ahead of time which columns you expect your SQL to output. That means, you can only do what you're looking to do if you implement a hard cap on the maximum number of child columns you'll include.
If you can live with having to do that, then this should work. I took some guesses about how you would want it to work if your data had a hierarchy with multiple levels. (Take a look at row "H" in the sample data and think about how you would want that displayed.)
WITH d AS (
SELECT 1 id, 'A' name, 0 parent_id
FROM DUAL UNION ALL
SELECT 2, 'B', 1
FROM DUAL UNION ALL
SELECT 3, 'C', 1
FROM DUAL UNION ALL
SELECT 4, 'D', 0
FROM DUAL UNION ALL
SELECT 5, 'E', 4
FROM DUAL UNION ALL
SELECT 6, 'F', 4
FROM DUAL UNION ALL
SELECT 7, 'G', 4
FROM DUAL UNION ALL
SELECT 8, 'H', 7
FROM DUAL
),
h as (
select prior d.name parent,
level lvl,
case when level = 1 then null else d.name end child_name,
case when level = 1 then null else row_number() over ( partition by prior d.name, level order by d.name) end child_Number
from d
connect by parent_id = prior id
start with parent_id = 0 )
select * from h
pivot ( max(child_name) for (child_number) in (1 AS "CHILD_1",2 AS "CHILD_2",3 AS "CHILD_3",4 AS "CHILD_4",5 AS "CHILD_5") )
where lvl > 1
order by parent;