SQL: Combinations by Type - sql

I have sets organized by type. I want to find all unique combinations of sets, taking one set from each type. So I start with this:
table1:
row_id type set
1 a 1
2 a 2
3 a 3
4 b 4
5 b 5
6 c 6
and want to get this:
table2:
row_id combo_id type set
1 1 a 1
2 1 b 4
3 1 c 6
4 2 a 2
5 2 b 4
6 2 c 6
7 3 a 3
8 3 b 4
9 3 c 6
10 4 a 1
11 4 b 5
12 4 c 6
13 5 a 2
14 5 b 5
15 5 c 6
16 6 a 3
17 6 b 5
18 6 c 6
The first idea might be to use CROSS JOIN and get something like this:
table3:
row_id combo_id a_set b_set c_set
1 1 1 4 6
2 2 2 4 6
3 3 3 4 6
4 4 1 5 6
5 5 2 5 6
6 6 3 5 6
However, my real data has thousands of types with no upper bound on that number, so I think the setup in table2 is necessary.
I see there are many Stack Overflow questions about SQL combinations. However, none that I found addressed sorting by type, let alone an unbounded number of types.
I'm using PLSQL Developer with Oracle 10g. Thanks!

One method to arrive at your table2 representation would be to use an Up-and-Down hierarchical query:
with table1(row_id, type, val) as (
select 1, 'a', 1 from dual union all
select 2, 'a', 2 from dual union all
select 3, 'a', 3 from dual union all
select 4, 'b', 4 from dual union all
select 5, 'b', 5 from dual union all
select 6, 'c', 6 from dual
), t2 as (
select row_id
, type
, DENSE_RANK() OVER (ORDER BY type desc) type_id
, val
from table1
), Up as (
select row_number() over (partition by CONNECT_BY_ISLEAF
order by SYS_CONNECT_BY_PATH(row_id, ',')) combo_id
, SYS_CONNECT_BY_PATH(row_id, ',') path_id
, prior SYS_CONNECT_BY_PATH(row_id, ',') parent_path_id
, t2.*
, CONNECT_BY_ISLEAF leaf
from t2
connect by type_id = prior type_id+1
start with type_id = 1
), Down as (
select row_number() over (order by CONNECT_BY_ROOT combo_id, type) row_id
, CONNECT_BY_ROOT combo_id combo_id
, type
, val
from Up
connect by path_id = prior parent_path_id
start with leaf = 1
)
select * from Down;
In this solution subquery t2 adds a sequential id for each unique type I've ordered them in reverse order so that the first traversal will end up with type 'a' as the leaf nodes.
In the first tree traversal (subquery Up) I add a unique combo code to all the leaf records for grouping purposes and add the path_id and parent_path_id columns used in the next tree traversal. This is also the stage where all the new rows are generated.
In the second tree traversal (subquery Down) I start at the leaf nodes from the Up traversal and climb back down to the root keeping the root (leaf?) combo_id generated in the prior traversal. No additional rows are generated in this stage since it's a straight shot from the leaf back to the root of the tree.
The final result:
ROW_ID COMBO_ID T VAL
-------- ---------- - ----------
1 1 a 1
2 1 b 4
3 1 c 6
4 2 a 2
5 2 b 4
6 2 c 6
7 3 a 3
8 3 b 4
9 3 c 6
10 4 a 1
11 4 b 5
12 4 c 6
13 5 a 2
14 5 b 5
15 5 c 6
16 6 a 3
17 6 b 5
18 6 c 6
18 rows selected
If you want your Table3 representation you can change the select * from Down to this:
select row_number() over (order by combo_id) row_id
, pvt.*
from (select combo_id, type, val from Down)
pivot (max(val) "SET"
for (type) in ('a' A
,'b' B
,'c' C)) pvt;
Yielding the following result:
ROW_ID COMBO_ID A_SET B_SET C_SET
---------- ---------- ---------- ---------- ----------
1 1 1 4 6
2 2 2 4 6
3 3 3 4 6
4 4 1 5 6
5 5 2 5 6
6 6 3 5 6

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table1 ( "rowid", "type", "set" ) AS
SELECT 1, 'a', 1 FROM DUAL
UNION ALL SELECT 2, 'a', 2 FROM DUAL
UNION ALL SELECT 3, 'a', 3 FROM DUAL
UNION ALL SELECT 4, 'b', 4 FROM DUAL
UNION ALL SELECT 5, 'b', 5 FROM DUAL
UNION ALL SELECT 6, 'c', 6 FROM DUAL
//
CREATE TYPE combo_sets AS OBJECT(
"type" CHAR(1),
idx NUMBER(5,0),
sets SYS.ODCINUMBERLIST
);
//
CREATE TYPE t_combo_sets AS TABLE OF combo_sets;
//
CREATE TYPE combo AS OBJECT(
"rowid" NUMBER(8,0),
"comboid" NUMBER(8,0),
"type" CHAR(1),
"set" NUMBER(5,0)
);
//
CREATE TYPE t_combos AS TABLE of combo;
//
CREATE OR REPLACE FUNCTION get_combos
RETURN t_combos PIPELINED
AS
v_combo_sets t_combo_sets;
i NUMBER(5,0);
r NUMBER(5,0) := 1;
c NUMBER(5,0) := 1;
BEGIN
SELECT combo_sets(
"type",
1,
CAST( COLLECT( "set" ORDER BY "set" ) AS SYS.ODCINUMBERLIST )
)
BULK COLLECT INTO v_combo_sets
FROM table1
GROUP BY "type";
i := 1;
WHILE i <= v_combo_sets.COUNT LOOP
FOR j IN 1 .. v_combo_sets.COUNT LOOP
PIPE ROW(
combo(
r,
c,
v_combo_sets(j)."type",
v_combo_sets(j).sets( v_combo_sets(j).idx )
)
);
r := r + 1;
END LOOP;
c := c + 1;
i := 1;
WHILE i <= v_combo_sets.COUNT AND v_combo_sets(i).idx = v_combo_sets(i).sets.COUNT LOOP
v_combo_sets(i).idx := 1;
i := i + 1;
END LOOP;
IF i <= v_combo_sets.COUNT THEN
v_combo_sets(i).idx := v_combo_sets(i).idx + 1;
END IF;
END LOOP;
RETURN;
END;
//
Query 1:
SELECT *
FROM TABLE( get_combos )
Results:
| rowid | comboid | type | set |
|-------|---------|------|-----|
| 1 | 1 | a | 1 |
| 2 | 1 | b | 4 |
| 3 | 1 | c | 6 |
| 4 | 2 | a | 2 |
| 5 | 2 | b | 4 |
| 6 | 2 | c | 6 |
| 7 | 3 | a | 3 |
| 8 | 3 | b | 4 |
| 9 | 3 | c | 6 |
| 10 | 4 | a | 1 |
| 11 | 4 | b | 5 |
| 12 | 4 | c | 6 |
| 13 | 5 | a | 2 |
| 14 | 5 | b | 5 |
| 15 | 5 | c | 6 |
| 16 | 6 | a | 3 |
| 17 | 6 | b | 5 |
| 18 | 6 | c | 6 |

Related

How can I generate below sequence using Oracle SQL? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
Sequence
1
2
2
3
3
3
4
4
4
4
5
5
5
5
5
and so on ……
Alternatively:
SQL> with temp (col) as
2 (select level
3 from dual
4 connect by level <= &n
5 )
6 select col
7 from temp cross join table(cast(multiset(select level from dual
8 connect by level <= col
9 ) as sys.odcinumberlist))
10 order by col;
Enter value for n: 5
old 4: connect by level <= &n
new 4: connect by level <= 5
COL
----------
1
2
2
3
3
3
4
4
4
4
5
5
5
5
5
15 rows selected.
SQL>
I am thinking two recursive CTEs. One to generate the counts and the other to generate the rows:
with cte(n) as (
select 1
from dual
union all
select n + 1
from cte
where n < 6
),
dups(nn, n) as (
select 1 as nn, n
from cte
union all
select nn + 1, n
from dups
where nn < n
)
select *
from dups;
With Oracle 12 and above you can use LATERAL join. Example:
with gen as (
select level as l
from dual
connect by level < 5
)
select l
from gen,
lateral (
select null
from dual
connect by level <= gen.l
)
You can do it with a single recursive sub-query factoring clause (a.k.a. a CTE) without any joins:
WITH items ( id, seq, max_seq ) AS (
SELECT 1, 1, 6 FROM DUAL
UNION ALL
SELECT CASE WHEN id < seq THEN id + 1 ELSE 1 END,
CASE WHEN id < seq THEN seq ELSE seq + 1 END,
max_seq
FROM items
WHERE id < max_seq
OR seq < max_seq
)
SELECT seq
FROM items;
Which outputs:
| SEQ |
| --: |
| 1 |
| 2 |
| 2 |
| 3 |
| 3 |
| 3 |
| 4 |
| 4 |
| 4 |
| 4 |
| 5 |
| 5 |
| 5 |
| 5 |
| 5 |
| 6 |
| 6 |
| 6 |
| 6 |
| 6 |
| 6 |
db<>fiddle here

Extract all rows of a column based on the value of another column (SQL query)

I have table
a | b
-----
1 | 3
3 | 2
3 | 4
2 | 5
3 | 6
2 | 7
how to write sql query if a = 1 then result 3 2 4 5 6 7, if a = 3 then 2 4 5 6 7, if 2 then 5 7
here is my query
select *
from table
where a in (select b from table where a = 1) or a = 1
but the result only 3 2 4 6 because 3 has 2 in col b so i want also to have 5 7
thanks
I suspect that you have a hierarchical structure, where a is the parent and b is the child, and that you are looking for all descendents of a given node.
One common way to walk such structure is a hierarchical query. In SQL Server:
with cte as (
select a, b from mytable where a = #your_parameter
union all
select t.a, t.b from mytable t inner join cte c on t.a = c.b
)
select * from cte
Demo on DB Fiddle - when given 3 as parameter:
a | b
-: | -:
3 | 2
3 | 4
3 | 6
2 | 5
2 | 7

Adding a row number respecting the order of each row

I have a table like this
id, period, tag
1 1 A
1 2 A
1 3 B
1 4 A
1 5 A
1 6 A
2 1 A
2 2 B
2 3 B
2 4 B
2 5 B
2 6 A
I would like to add a new column with a ranking, respecting the order of the row given my column 'period' to obtain something like this
id, period, tag rank
1 1 A 1
1 2 A 1
1 3 B 2
1 4 A 3
1 5 A 3
1 6 A 3
2 1 A 1
2 2 B 2
2 3 B 2
2 4 B 2
2 5 B 2
2 6 A 3
What can I do?
I try rank and dense_rank function without any success
And another candidate for CONDITIONAL_CHANGE_EVENT()
less code, and quite effective, too ...!
WITH
input(id,period,tag) AS (
SELECT 1,1,'A'
UNION ALL SELECT 1,2,'A'
UNION ALL SELECT 1,3,'B'
UNION ALL SELECT 1,4,'A'
UNION ALL SELECT 1,5,'A'
UNION ALL SELECT 1,6,'A'
UNION ALL SELECT 2,1,'A'
UNION ALL SELECT 2,2,'B'
UNION ALL SELECT 2,3,'B'
UNION ALL SELECT 2,4,'B'
UNION ALL SELECT 2,5,'B'
UNION ALL SELECT 2,6,'A'
)
SELECT
*
, CONDITIONAL_CHANGE_EVENT(tag) OVER(PARTITION BY id ORDER BY period) + 1 AS rank
FROM input;
-- out id | period | tag | rank
-- out ----+--------+-----+------
-- out 1 | 1 | A | 1
-- out 1 | 2 | A | 1
-- out 1 | 3 | B | 2
-- out 1 | 4 | A | 3
-- out 1 | 5 | A | 3
-- out 1 | 6 | A | 3
-- out 2 | 1 | A | 1
-- out 2 | 2 | B | 2
-- out 2 | 3 | B | 2
-- out 2 | 4 | B | 2
-- out 2 | 5 | B | 2
-- out 2 | 6 | A | 3
-- out (12 rows)
-- out
-- out Time: First fetch (12 rows): 14.823 ms. All rows formatted: 14.874 ms
One method is a cumulative sum based on a lag():
select t.*,
sum(case when prev_tag = tag then 0 else 1 end) over (partition by id order by period) as rank
from (select t.*, lag(tag) over (partition by id order by period) as prev_tag
from t
) t;

Vertica - Union with Group by is possible?

I have two tables like this :
Nu
i j
1 2
1 3
1 4
Nv
i j
2 1
2 5
3 1
3 6
What I want to do is I need to find :
Select j From Nu UNION Select j from Nv) for every Distinct (Nu.i , Nv.i)
Like this :
Nu.i Nv.i v
1 2 2
1 2 3
1 2 4
1 2 1
1 2 5
1 3 2
1 3 3
1 3 4
1 3 1
1 3 6
Is there any way I can query this with Vertica SQL?
I tried :
Select
Nu.i,
Nv.i,
(Select j from Nu UNION Select j from Nv group by Nv.j) as v
from Nu, Nv;
Error :
ERROR 4840: Subquery used as an expression returned more than one row
And :
Select
Nu.i,
Nv.i,
(Select j from Nu UNION Select j from Nv) as v
from Nu, Nv
group by Nu.i, Nv.i;
Error:
Subqueries in the SELECT or ORDER BY are not supported if the subquery
is not part of the GROUP BY
Please, let me know you suggestion.
Your result table is somehow unexpected to me - I can't figure out the rules on how you would want it to be generated.
Nu has 3 rows. Nv has 4 rows. I was thinking of a CROSS JOIN between the two tables, but that would lead to 3 x 4, that's 12 rows. Like so:
WITH
Nu (i,j) AS (
SELECT 1,2
UNION ALL SELECT 1,3
UNION ALL SELECT 1,4
)
,
Nv(i,j) AS (
SELECT 2,1
UNION ALL SELECT 2,5
UNION ALL SELECT 3,1
UNION ALL SELECT 3,6
)
SELECT
Nu.i AS "Nu.i"
, Nv.i AS "Nv.i"
, Nu.j AS "Nu.j"
, Nv.j AS "Nv.j"
FROM Nu CROSS JOIN Nv;
-- out Nu.i | Nv.i | Nu.j | Nv.j
-- out ------+------+------+------
-- out 1 | 2 | 2 | 1
-- out 1 | 2 | 3 | 1
-- out 1 | 2 | 4 | 1
-- out 1 | 2 | 2 | 5
-- out 1 | 2 | 3 | 5
-- out 1 | 2 | 4 | 5
-- out 1 | 3 | 2 | 1
-- out 1 | 3 | 3 | 1
-- out 1 | 3 | 4 | 1
-- out 1 | 3 | 2 | 6
-- out 1 | 3 | 3 | 6
-- out 1 | 3 | 4 | 6
-- out (12 rows)
-- out
-- out Time: First fetch (12 rows): 14.037 ms. All rows formatted: 14.086 ms
But could it be that, seeing this result table, you can figure out the rest by yourself?

Oracle query for selecting sampling of number values from a table

I have a data field in my Oracle DB Table whose datatype is NUMBER. I have tried a query below using order by.
SELECT Value
FROM Table
ORDER BY value;
I am getting the result as
Value |
------|
1 |
1 |
2 |
2 |
3 |
3 |
4 |
4 |
5 |
5 |
6 |
6 |
Instead I want a result as
Value |
------|
1 |
2 |
3 |
4 |
5 |
6 |
1 |
2 |
3 |
4 |
5 |
6 |
You can use the row_number to evaluate if an occurrence of a value is the first one, the second, and so on; an order by based on this value and then for the value in the table will do the work.
For example:
/* a test case */
with someTable(value) as (
select 1 from dual union all
select 2 from dual union all
select 3 from dual union all
select 4 from dual union all
select 5 from dual union all
select 6 from dual union all
select 1 from dual union all
select 2 from dual union all
select 3 from dual union all
select 4 from dual union all
select 5 from dual union all
select 6 from dual
)
/* the query */
select value
from someTable
order by row_number() over ( partition by value order by null), value
How it works:
select value, row_number() over ( partition by value order by null) rowNumber
from someTable
order by row_number() over ( partition by value order by null), value
gives:
VALUE ROWNUMBER
---------- ----------
1 1
2 1
3 1
4 1
5 1
6 1
1 2
2 2
3 2
4 2
5 2
6 2
Please try this. I'm using ROW_NUMBER() to arrange the values based on their occurrences,
SELECT VALUE
FROM (
SELECT VALUE
, ROW_NUMBER() OVER (PARTITION BY VALUE ORDER BY VALUE ASC) RNK
FROM MY_TABLE
)
ORDER BY RNK
, VALUE;
1 Value | ------| 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 | 5 | 6 | 6 |
SELECT ROW_NUMBER() OVER (PARTITION BY VALUE ORDER BY VALUE) AS RN, TABLE .* FROM TABLE
2 Value | ------| 1 | 2 | 3 | 4 | 5 | 6 | 1 | 2 | 3 | 4 | 5 | 6 |
SELECT ROWNUM,TABLE.* FROM TABLE