Reputation: 89
Having a table with two columns, PART_NUMBER
and PART_TYPE
. A type can have many parts. The requirement is to find the unique combinations of the part numbers using a sql procedure. We are using Oracle as our DB. Sample data is as below:
PART_NO PART_TYPE
Part1 10
Part2 10
Part3 10
Part4 20
Part5 30
Part6 30
Part7 40
I want the output to be as follows.
1: Part1 Part4 Part5 Part7
2: Part1 Part4 Part6 Part7
3: Part2 Part4 Part5 Part7
4: Part2 Part4 Part6 Part7
5: Part3 Part4 Part5 Part7
6: Part3 Part4 Part6 Part7
ie) Each part from each type forms a single combination.
Please throw some light on this either in the form of algorithm or sql procedure.
Upvotes: 3
Views: 214
Reputation: 167981
Use a hierarchical query:
Oracle 11g R2 Schema Setup:
CREATE TABLE parts AS
SELECT 'Part1' AS PART_NO, 10 AS PART_TYPE FROM DUAL
UNION ALL SELECT 'Part2' AS PART_NO, 10 AS PART_TYPE FROM DUAL
UNION ALL SELECT 'Part3' AS PART_NO, 10 AS PART_TYPE FROM DUAL
UNION ALL SELECT 'Part4' AS PART_NO, 20 AS PART_TYPE FROM DUAL
UNION ALL SELECT 'Part5' AS PART_NO, 30 AS PART_TYPE FROM DUAL
UNION ALL SELECT 'Part6' AS PART_NO, 30 AS PART_TYPE FROM DUAL
UNION ALL SELECT 'Part7' AS PART_NO, 40 AS PART_TYPE FROM DUAL;
Query 1:
WITH combinations AS (
SELECT SYS_CONNECT_BY_PATH( PART_NO, ' ' ) AS parts,
CONNECT_BY_ISLEAF AS leaf
FROM parts
START WITH PART_TYPE = 10
CONNECT BY PRIOR PART_TYPE + 10 = PART_TYPE
)
SELECT ROWNUM || ':' || parts AS output
FROM combinations
WHERE leaf = 1
| OUTPUT |
|----------------------------|
| 1: Part1 Part4 Part5 Part7 |
| 2: Part1 Part4 Part6 Part7 |
| 3: Part2 Part4 Part5 Part7 |
| 4: Part2 Part4 Part6 Part7 |
| 5: Part3 Part4 Part5 Part7 |
| 6: Part3 Part4 Part6 Part7 |
Edit Rob van Wijk: Since connect_by_isleaf is evaluated after the connect by, a slightly easier query is:
SQL> select rownum || ':' || sys_connect_by_path(part_no, ' ') as parts
2 from parts
3 where connect_by_isleaf = 1
4 connect by prior part_type + 10 = part_type
5 start with part_type = 10
6 /
PARTS
---------------------------------------------------------------------------------------
1: Part1 Part4 Part5 Part7
2: Part1 Part4 Part6 Part7
3: Part2 Part4 Part5 Part7
4: Part2 Part4 Part6 Part7
5: Part3 Part4 Part5 Part7
6: Part3 Part4 Part6 Part7
6 rows selected.
Edit - Non-incremental PART_TYPE
s
Query 3:
WITH part_types AS (
SELECT DISTINCT PART_TYPE
FROM parts
),
ordered_part_types AS (
SELECT PART_TYPE,
LEAD( PART_TYPE ) OVER ( ORDER BY PART_TYPE ) AS NEXT_PART_TYPE
FROM part_types
)
SELECT ROWNUM || ':' || SYS_CONNECT_BY_PATH( PART_NO, ' ' ) AS parts
FROM parts p
INNER JOIN
ordered_part_types t
ON ( p.PART_TYPE = t.PART_TYPE )
WHERE CONNECT_BY_ISLEAF = 1
START WITH p.PART_TYPE = ( SELECT MIN( PART_TYPE ) FROM parts )
CONNECT BY PRIOR NEXT_PART_TYPE = p.PART_TYPE
| PARTS |
|----------------------------|
| 1: Part3 Part4 Part6 Part7 |
| 2: Part3 Part4 Part5 Part7 |
| 3: Part2 Part4 Part6 Part7 |
| 4: Part2 Part4 Part5 Part7 |
| 5: Part1 Part4 Part6 Part7 |
| 6: Part1 Part4 Part5 Part7 |
Upvotes: 5
Reputation: 721
You don't need PL/SQL for this. Just SQL.
with TAB as (
select 'Part1' as PART_NO, 10 as PART_TYPE from dual union all
select 'Part2', 10 from dual union all
select 'Part3', 10 from dual union all
select 'Part4', 20 from dual union all
select 'Part5', 30 from dual union all
select 'Part6', 30 from dual union all
select 'Part7', 40 from dual
),
CONSTANTS as (
select /*+ MATERIALIZE */
min( PART_TYPE ) as PART_TYPE,
count( distinct PART_TYPE ) as CNT
from TAB
)
select rownum || ':' || ANSWER as ANSWER
from ( select sys_connect_by_path( PART_NO, ' ' ) as ANSWER,
connect_by_isleaf as IS_LEAF,
level as L
from TAB
start with PART_TYPE = ( select PART_TYPE
from CONSTANTS )
connect by PART_TYPE > prior PART_TYPE )
where L = ( select CNT
from CONSTANTS )
and IS_LEAF = 1
Upvotes: 1
Reputation: 794
This will give you the desired output but with caveats:
1: you have to add a table in the FROM
clause for each part_type
and add a "less than" line to the where clause.
2: Beware: if the base table is big the cartesian joins might make it run forever (more or less).
with tab as (
select 'Part1' part_no, 10 part_type from dual union all
select 'Part2', 10 from dual union all
select 'Part3', 10 from dual union all
select 'Part4', 20 from dual union all
select 'Part5', 30 from dual union all
select 'Part6', 30 from dual union all
select 'Part7', 40 from dual
)
select
t1.part_no||','||t2.part_no||','||t3.part_no||','||t4.part_no output
from
tab t1, tab t2, tab t3, tab t4
where
t1.part_type < t2.part_type and
t2.part_type < t3.part_type and
t3.part_type < t4.part_type
order by 1;
Upvotes: 1
Reputation: 326
You can use Oracle's listagg function (see https://docs.oracle.com/database/121/SQLRF/functions100.htm#SQLRF30030) like this:
select part_type, listagg(part_no, ' ') within group (order by part_no)
from d
group by part_type;
Upvotes: -1