Usage of combinatorics in Oracle SQL - 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.

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

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.

Join two tables with a column with multiple entries for the other table

I have the following problem.
I want to join two tables.
The first table has entries like the following:
T1
PK Info
1 one
2 two
3 three
The second table is build like this:
T2
PK FKT1
1 1,3
2 1,2,3
3 2
My Result should show the following
PK2 FKT1 InfoT1
1 1,3 One,Three
2 1,2,3 One,two,Three
3 2 Two
I just cant get an idea how to solve this.
Is this possible only using sql selects or is a function needed?
kind regards
It's not that difficult, but - as you were told, you'd rather NOT do that.
SQL> with
2 t1 (pk, info) as
3 (select 1, 'one' from dual union
4 select 2, 'two' from dual union
5 select 3, 'three' from dual
6 ),
7 t2 (pk, fkt1) as
8 (select 1, '1,3' from dual union
9 select 2, '1,2,3' from dual union
10 select 3, '2' from dual
11 ),
12 t2rows as
13 (select pk, regexp_substr(fkt1, '[^,]+', 1, column_value) fkt1, column_value rn
14 from t2,
15 table(cast(multiset(select level from dual
16 connect by level <= regexp_count(fkt1, ',') + 1
17 ) as sys.odcinumberlist))
18 )
19 select t2r.pk,
20 listagg(t2r.fkt1, ',') within group (order by t2r.rn) fkt1,
21 listagg(t1.info, ',') within group (order by t2r.rn) infot1
22 from t2rows t2r join t1 on t2r.fkt1 = t1.pk
23 group by t2r.pk
24 order by t2r.pk;
PK FKT1 INFOT1
---------- -------------------- --------------------
1 1,3 one,three
2 1,2,3 one,two,three
3 2 two
SQL>

How can I find unoccupied id numbers in a table?

In my table I want to see a list of unoccupied id numbers in a certain range.
For example there are 10 records in my table with id's: "2,3,4,5,10,12,16,18,21,22" and say that I want to see available ones between 1 and 25. So I want to see a list like:
1,6,7,89,11,13,14,15,17,19,20,23,24,25
How should I write my sql query?
Select the numbers form 1 to 25 and show only those that are not in your table
select n from
( select rownum n from dual connect by level <= 25)
where n not in (select id from table);
Let's say you a #numbers table with three numbers -
CREATE TABLE #numbers (num INT)
INSERT INTO #numbers (num)
SELECT 1
UNION
SELECT 3
UNION
SELECT 6
Now, you can use CTE to generate numbers recursively from 1-25 and deselect those which are in your #numbers table in the WHERE clause -
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 25
)
SELECT n FROM n
WHERE n NOT IN (select num from #numbers)
ORDER BY n
OPTION (MAXRECURSION 25);
You can try using the "NOT IN" clause:
select
u1.user_id + 1 as start
from users as u1
left outer join users as u2 on u1.user_id + 1 = u2.id
where
u2.id is null
see also SQL query to find Missing sequence numbers
You need LISTAGG to get the output in a single row.
SQL> WITH DATA1 AS(
2 SELECT LEVEL rn FROM dual CONNECT BY LEVEL <=25
3 ),
4 data2 AS(
5 SELECT 2 num FROM dual UNION ALL
6 SELECT 3 FROM dual UNION ALL
7 SELECT 4 from dual union all
8 SELECT 5 FROM dual UNION ALL
9 SELECT 10 FROM dual UNION ALL
10 SELECT 12 from dual union all
11 SELECT 16 from dual union all
12 SELECT 18 FROM dual UNION ALL
13 SELECT 21 FROM dual UNION ALL
14 SELECT 22 FROM dual)
15 SELECT listagg(rn, ',')
16 WITHIN GROUP (ORDER BY rn) num_list FROM data1
17 WHERE rn NOT IN(SELECT num FROM data2)
18 /
NUM_LIST
----------------------------------------------------
1,6,7,8,9,11,13,14,15,17,19,20,23,24,25
SQL>

Correlated row-generating query in Oracle

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.