Fetching one to many relationship from same table - sql

I have to pull one to many relationship data from the same table.
The structure of the table is like
____________________________________
| CUSTOMER_ID | SUBSCRIPTION_NAME |
-------------------------------------
| 1 | ABC |
| 1 | TNT |
| 1 | AMC |
| 2 | ABC |
| 2 | USA |
| 3 | TNT |
| 3 | AMC |
-------------------------------------
I would like to get the output like
| CUSTOMER_ID | SUBSCRIPTION_NAME |
| 1 | ABC,TNT,AMC |
| 2 | ABC,USA |
| 3 | TNT,AMC |

SELECT customer_id,
LISTAGG(subscription_name, '; ') WITHIN GROUP (ORDER BY subscription_name) AS subscription_name
FROM subscription
GROUP BY customer_id
ORDER BY customer_id;
Order by is optional

Just few alternatives to LISTAGG:
SQL> with t (CUSTOMER_ID, SUBSCRIPTION_NAME)
2 as (
3 select 1, 'ABC' from dual union all
4 select 1, 'TNT' from dual union all
5 select 1, 'AMC' from dual union all
6 select 2, 'ABC' from dual union all
7 select 2, 'USA' from dual union all
8 select 3, 'TNT' from dual union all
9 select 3, 'AMC' from dual
10 )
11 SELECT CUSTOMER_ID,
12 RTRIM(
13 XMLAGG (XMLELEMENT(e, SUBSCRIPTION_NAME||',') ORDER BY SUBSCRIPTION_NAME).EXTRACT('//text()')
14 ,',') AS x
15 FROM t
16 GROUP BY CUSTOMER_ID
17 /
CUSTOMER_ID X
----------- ----------------------------------------
1 ABC,AMC,TNT
2 ABC,USA
3 AMC,TNT
SQL> with t (CUSTOMER_ID, SUBSCRIPTION_NAME)
2 as (
3 select 1, 'ABC' from dual union all
4 select 1, 'TNT' from dual union all
5 select 1, 'AMC' from dual union all
6 select 2, 'ABC' from dual union all
7 select 2, 'USA' from dual union all
8 select 3, 'TNT' from dual union all
9 select 3, 'AMC' from dual
10 )
11 select customer_id,
12 max(substr(sys_connect_by_path( SUBSCRIPTION_NAME, ','),2)) x from
13 (
14 select customer_id, SUBSCRIPTION_NAME, row_number() over(partition by customer_id order by null) rn
15 from t
16 )
17 start with rn=1
18 connect by prior customer_id = customer_id and rn = prior rn+1
19 group by customer_id
20 /
CUSTOMER_ID X
----------- ----------------------------------------
1 ABC,TNT,AMC
2 ABC,USA
3 TNT,AMC

Related

How to avoid duplicate row where one column value will get first table?

I have two tables. And I want to avoid duplicate values where one column value will get from any table.
table a
id | value | name | pid
----+-------+-------+-----
1 | 55 | a | 27
2 | 56 | b | 23
3 | 57 | c | 22
table b
id | value | name | pid
----+-------+-------+-----
1 | 55 | a | 29
5 | 58 | d | 23
6 | 59 | e | 22
expected result
id | value | name | pid
----+-------+-------+-----
1 | 55 | a | 27
2 | 56 | b | 23
3 | 57 | c | 22
5 | 58 | d | 23
6 | 59 | e | 22
here
1 | 55 | a | 29
the row will be removed.
Use UNION ALL to concatenate the two tables with an additional priority column and then use the ROW_NUMBER analytic function to find each row with the highest priority:
SELECT id,
value,
name,
pid
FROM (
SELECT id,
value,
name,
pid,
ROW_NUMBER() OVER (PARTITION BY id, value, name ORDER BY priority) AS rn
FROM (
SELECT id, value, name, pid, 1 AS priority FROM a UNION ALL
SELECT id, value, name, pid, 2 AS priority FROM b
)
)
WHERE rn = 1;
Which, for the sample data:
CREATE TABLE a (id, name, value, pid) AS
SELECT 1, 'a', 55, 27 FROM DUAL UNION ALL
SELECT 2, 'b', 56, 23 FROM DUAL UNION ALL
SELECT 3, 'c', 57, 22 FROM DUAL;
CREATE TABLE b (id, name, value, pid) AS
SELECT 1, 'a', 55, 29 FROM DUAL UNION ALL
SELECT 5, 'd', 58, 23 FROM DUAL UNION ALL
SELECT 6, 'e', 59, 22 FROM DUAL;
Outputs:
ID
VALUE
NAME
PID
1
55
a
27
2
56
b
23
3
57
c
22
5
58
d
23
6
59
e
22
fiddle
You can use the UNION operator with SELECT DISTINCT to achieve the desired result:
SELECT DISTINCT id, value, name, pid
FROM (
SELECT id, value, name, pid
FROM table_a
UNION
SELECT id, value, name, pid
FROM table_b
) result;

Sorting the result based on 2 columns Oracle

I have two columns Date and Number with null values in date. Can I sort with date and number col together checking if date is null then sort with number.
dt num
3/20/2022 1
3/16/2022 3
3/17/2022 4
3/18/2022 5
NULL 6
NULL 7
3/19/2022 8
*Expected Output*
dt num
3/16/2022 3
3/17/2022 4
3/18/2022 5
NULL 6
NULL 7
3/19/2022 8
3/20/2022 1
We need to sort by the date if there is one and if there is not we search the previous row where the date is not null.
This does mean that we are running a sub-query per line so it will be slow for large queries.
create table d(
dt date,
num int);
insert into d (dt, num)
select to_date('2022-03-20','YYYY-MM-DD'),1 from dual union all
select to_date('2022-03-16','YYYY-MM-DD'),3 from dual union all
select to_date ('2022-03-17','YYYY-MM-DD'), 4 from dual union all
select to_date('2022-03-17','YYYY-MM-DD'),5 from dual union all
select to_date('2022-03-18','YYYY-MM-DD'),6 from dual union all
select to_date('2022-03-16','YYYY-MM-DD'),10 from dual union all
select to_date('2022-03-19','YYYY-MM-DD'),9 from dual;
insert into d ( num)
select 7 from dual union all
select 8 from dual ;
select
dt,
num,
( select dt
from d
where num <= d1.num and dt is not null
order by num desc
fetch next 1 rows only
) as dt_plus
from d d1
order by dt_plus,num;
DT | NUM | DT_PLUS
:-------- | --: | :--------
16-MAR-22 | 3 | 16-MAR-22
16-MAR-22 | 10 | 16-MAR-22
17-MAR-22 | 4 | 17-MAR-22
17-MAR-22 | 5 | 17-MAR-22
18-MAR-22 | 6 | 18-MAR-22
null | 7 | 18-MAR-22
null | 8 | 18-MAR-22
19-MAR-22 | 9 | 19-MAR-22
20-MAR-22 | 1 | 20-MAR-22
db<>fiddle here

Oracle sql split up rows to fill maxquantity

say i have a list of articles, which i want to split to fill maxvalues:
id | name | quantity | maxquantity
1 | name_a| 3 | 5
2 | name_a| 1 | 5
3 | name_a| 3 | 5
4 | name_a| 5 | 5
5 | name_b| 7 | 4
6 | name_b| 2 | 4
i want to create packages grouped by name, filled up to the maxvalues to get the following results:
id | name | quantity | maxquantity | tag | effective_quantity
1 | name_a| 3 | 5 | name_a_part1 | 3
2 | name_a| 1 | 5 | name_a_part1 | 1
3 | name_a| 3 | 5 | name_a_part1 | 1
^- sum() = maxquantity
3 | name_a| 3 | 5 | name_a_part2 | 2
4 | name_a| 5 | 5 | name_a_part2 | 3
^- sum() = maxquantity
4 | name_a| 5 | 5 | name_a_part3 | 2
^- sum() = maxquantity or the rest of name_a
5 | name_b| 7 | 4 | name_b_part1 | 4
^- sum() = maxquantity
5 | name_b| 7 | 4 | name_b_part2 | 3
6 | name_b| 2 | 4 | name_b_part2 | 1
^- sum() = maxquantity
6 | name_b| 2 | 4 | name_b_part3 | 1
^- sum() = maxquantity or the rest of name_b
One pretty simple method is to explode the data into a separate row for each item, calculate the bins at that level, and then reaggregate:
with cte (id, name, quantity, maxquantity, n) as (
select id, name, quantity, maxquantity, 1 as n
from t
union all
select id, name, quantity, maxquantity, n + 1
from cte
where n < quantity
)
select id, name, quantity, maxquantity,
count(*) as number_in_bin,
ceil(bin_counter / maxquantity) as bin_number
from (select cte.*,
row_number() over (partition by name order by id, n) as bin_counter
from cte
) cte
group by id, name, quantity, maxquantity, ceil(bin_counter / maxquantity)
order by id, bin_number;
Here is a db<>fiddle.
You can do it with a single recursive query using analytic functions:
WITH split_bins (id, name, quantity, maxquantity, tag, effective_quantity, prev_quantity) AS (
SELECT id,
name,
quantity,
maxquantity,
FLOOR(
COALESCE(
SUM(quantity) OVER (
PARTITION BY name
ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
)
/ maxquantity
) + 1,
LEAST(
maxquantity
- MOD(
COALESCE(
SUM(quantity) OVER (
PARTITION BY name
ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
),
maxquantity
),
quantity
),
0
FROM articles
UNION ALL
SELECT id,
name,
quantity,
maxquantity,
tag + 1,
LEAST(
quantity - prev_quantity - effective_quantity,
maxquantity
),
prev_quantity + effective_quantity
FROM split_bins
WHERE prev_quantity + effective_quantity < quantity
)
SEARCH DEPTH FIRST BY id SET id_order
SELECT id,
name,
quantity,
maxquantity,
name || '_part' || tag AS tag,
effective_quantity
FROM split_bins;
Which, for the sample data:
CREATE TABLE articles (id, name, quantity, maxquantity ) AS
SELECT 1, 'name_a', 3, 5 FROM DUAL UNION ALL
SELECT 2, 'name_a', 1, 5 FROM DUAL UNION ALL
SELECT 3, 'name_a', 3, 5 FROM DUAL UNION ALL
SELECT 4, 'name_a', 5, 5 FROM DUAL UNION ALL
SELECT 5, 'name_b', 7, 4 FROM DUAL UNION ALL
SELECT 6, 'name_b', 2, 4 FROM DUAL UNION ALL
SELECT 7, 'name_c', 6, 2 FROM DUAL UNION ALL
SELECT 8, 'name_c', 2, 2 FROM DUAL;
Outputs:
ID
NAME
QUANTITY
MAXQUANTITY
TAG
EFFECTIVE_QUANTITY
1
name_a
3
5
name_a_part1
3
2
name_a
1
5
name_a_part1
1
3
name_a
3
5
name_a_part1
1
3
name_a
3
5
name_a_part2
2
4
name_a
5
5
name_a_part2
3
4
name_a
5
5
name_a_part3
2
5
name_b
7
4
name_b_part1
4
5
name_b
7
4
name_b_part2
3
6
name_b
2
4
name_b_part2
1
6
name_b
2
4
name_b_part3
1
7
name_c
6
2
name_c_part1
2
7
name_c
6
2
name_c_part2
2
7
name_c
6
2
name_c_part3
2
8
name_c
2
2
name_c_part4
2
db<>fiddle here

Can Hierarchical queries be used in a condition?

I am going to build a Hierarchial Tree in Oracle Forms using the Hiearchical queries. As far as I know, the HQ works only for single table, can they be used in a condition where we have Master Details scenario.
Or can it be based on a view?
For example; I want to display an hierarchical with 3 of options from various tables. For example a Customer Order can have have various Items. First List of All CO, on clicking a specific CO, All the Items therein, An Item may have Sub Parts, and so on. Spanning multiple connected tables
If you have the test data:
CREATE TABLE customer_orders ( id, name ) AS
SELECT 1, 'Cust. Ord. XYZ' FrOM DUAL UNION ALL
SELECT 2, 'Cust. Ord. ABC' FrOM DUAL UNION ALL
SELECT 3, 'Cust. Ord. MNO' FrOM DUAL;
CREATE TABLE customer_order_items ( id, order_id, name ) AS
SELECT 1, 3, 'MNO_NNN' FROM DUAL UNION ALL
SELECT 2, 3, 'MNO_OOO' FROM DUAL UNION ALL
SELECT 3, 3, 'MNO_MMM' FROM DUAL UNION ALL
SELECT 4, 2, 'ABC_AAA' FROM DUAL;
CREATE TABLE customer_order_item_parts ( id, item_id, name ) AS
SELECT 1, 1, 'Part_2_N' FROM DUAL UNION ALL
SELECT 2, 1, 'Part_1_N' FROM DUAL UNION ALL
SELECT 3, 2, 'Part_1_O' FROM DUAL UNION ALL
SELECT 4, 2, 'Part_3_O' FROM DUAL UNION ALL
SELECT 5, 4, 'Part_5_A' FROM DUAL UNION ALL
SELECT 6, 4, 'Part_3_A' FROM DUAL UNION ALL
SELECT 7, 4, 'Part_2_A' FROM DUAL UNION ALL
SELECT 8, 4, 'Part_4_A' FROM DUAL;
Then you can use UNION ALL (and JOINs as required) to concatenate the data and once it is all compiled then you can apply a hierarchical query:
SELECT *
FROM (
SELECT id AS order_id,
NULL AS item_id,
NULL AS part_id,
name
FROM customer_orders
UNION ALL
SELECT order_id,
id,
NULL,
name
FROM customer_order_items
UNION ALL
SELECT i.order_id,
p.item_id,
p.id,
p.name
FROM customer_order_items i
INNER JOIN
customer_order_item_parts p
ON ( i.id = p.item_id )
)
START WITH item_id IS NULL
CONNECT BY NOCYCLE
PRIOR order_id = order_id
AND ( ( PRIOR item_id IS NULL
AND part_id IS NULL )
OR ( PRIOR part_id IS NULL
AND part_id IS NOT NULL
AND PRIOR item_id = item_id )
)
ORDER SIBLINGS BY name
Which outputs:
ORDER_ID | ITEM_ID | PART_ID | NAME
-------: | ------: | ------: | :-------------
2 | null | null | Cust. Ord. ABC
2 | 4 | null | ABC_AAA
2 | 4 | 7 | Part_2_A
2 | 4 | 6 | Part_3_A
2 | 4 | 8 | Part_4_A
2 | 4 | 5 | Part_5_A
3 | null | null | Cust. Ord. MNO
3 | 3 | null | MNO_MMM
3 | 1 | null | MNO_NNN
3 | 1 | 2 | Part_1_N
3 | 1 | 1 | Part_2_N
3 | 2 | null | MNO_OOO
3 | 2 | 3 | Part_1_O
3 | 2 | 4 | Part_3_O
1 | null | null | Cust. Ord. XYZ
db<>fiddle here

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