Oracle sql split up rows to fill maxquantity - sql

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

Related

Oracle sql split up rows to fill maxquantity with reference articles

this is an extended question to this already answered Thread
say i have a list of articles, which i want to split to fill maxvalues including addon-articles (no. 7), which refer to other positions:
id | ref | name | quantity | maxquantity
1 | null | name_a| 3 | 5
2 | null | name_a| 1 | 5
3 | null | name_a| 3 | 5
4 | null | name_a| 5 | 5
5 | null | name_b| 7 | 4
6 | null | name_b| 2 | 4
7 | 5 | add_1 | 14 | null
i want to create packages grouped by name, filled up to the maxvalues, keeping the reference-relationship and the ratio of referenced-articles to referencing-articles to get the following results:
1 | null | name_a| 3 | 5 | name_a_part1 | 3
2 | null | name_a| 1 | 5 | name_a_part1 | 1
3 | null | name_a| 3 | 5 | name_a_part1 | 1
^- sum() = maxquantity
3 | null | name_a| 3 | 5 | name_a_part2 | 2
4 | null | name_a| 5 | 5 | name_a_part2 | 3
^- sum() = maxquantity
4 | null | name_a| 5 | 5 | name_a_part3 | 2
^- sum() = maxquantity or the rest of name_a
5 | null | name_b| 7 | 4 | name_b_part1 | 4
^- sum() = maxquantity
5 | null | name_b| 7 | 4 | name_b_part2 | 3
6 | null | name_b| 2 | 4 | name_b_part2 | 1
^- sum() = maxquantity
6 | null | name_b| 2 | 4 | name_b_part3 | 1
^- sum() = maxquantity or the rest of name_b
7 | 5 | add_1| 14| null | name_b_part1 | 8
7 | 5 | add_1| 14| null | name_b_part2 | 6
ratio of pos5 to pos7 is 1:2
the name or the number of the final bins should match between referenced-articles and referencing-articles
I managed to get solve this issue.
create the table via
CREATE TABLE articles (pos, ref_pos, article, quantity, maxquantity ) AS
SELECT 0, NULL, 'prod1', 3, 6 FROM DUAL UNION ALL
SELECT 1, NULL, 'prod1', 3, 6 FROM DUAL UNION ALL
SELECT 2, NULL, 'prod1', 8, 6 FROM DUAL UNION ALL
SELECT 7, 2, 'addon_for_pos2', 16, NULL FROM DUAL
and this sql will get the correct Results:
WITH split_bins (pos, ref_pos, article, quantity, maxquantity, bin_tag, bin_tag2, effective_quantity, prev_quantity,effective_name, ratio) AS (
-- ################### the first static iteration
SELECT pos,
ref_pos,
article,
quantity,
-- ################### calculate the max-quantity
COALESCE(
maxquantity, CONNECT_BY_ROOT maxquantity * quantity / CONNECT_BY_ROOT quantity
) AS maxquantity,
-- ################### calculate the bin_tag for grouping
FLOOR(
COALESCE(
SUM(quantity) OVER (
PARTITION BY article
ORDER BY pos
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
)
/ COALESCE(
maxquantity, CONNECT_BY_ROOT maxquantity * quantity / CONNECT_BY_ROOT quantity
)
) + 1 as bin_tag,
-- ################### calculate the bin_tag for grouping supplements to correct bin
FLOOR(
COALESCE(
SUM(quantity) OVER (
PARTITION BY article, pos
ORDER BY pos
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
)
/ COALESCE(
maxquantity, CONNECT_BY_ROOT maxquantity * quantity / CONNECT_BY_ROOT quantity
)
) + 1 as bin_tag2,
-- ################### calculate the effective quantity
LEAST(
COALESCE(
maxquantity, CONNECT_BY_ROOT maxquantity * quantity / CONNECT_BY_ROOT quantity
)
- MOD(
COALESCE(
SUM(quantity) OVER (
PARTITION BY article
ORDER BY pos
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
),
COALESCE(
maxquantity, CONNECT_BY_ROOT maxquantity * quantity / CONNECT_BY_ROOT quantity
)
),
quantity
) AS effective_quantity,
-- ################### previously used quantity (start with zero)
0 AS prev_quantity,
-- ################### propagate the referenced article to the referencing articles
CONNECT_BY_ROOT article AS effective_name,
-- ################### calculate the ratio of main articles and addons (just dev)
quantity / CONNECT_BY_ROOT quantity AS ratio
FROM
articles START WITH ref_pos IS NULL CONNECT BY PRIOR pos = ref_pos
-- ################### the 2nd to n iteration
UNION ALL
--(pos, ref_pos, article, quantity, maxquantity, bin_tag, effective_quantity, prev_quantity,effective_name, ratio)
SELECT pos,
ref_pos,
article,
quantity,
maxquantity,
-- ################### increase the identifier
bin_tag + 1 as bin_tag,
bin_tag2 + 1 as bin_tag2,
-- ################### calculate the current effective_quantity
LEAST(
quantity - prev_quantity - effective_quantity,
maxquantity
) as effective_quantity,
-- ################### calculate the prev_quantity for next iteration
prev_quantity + effective_quantity as prev_quantity,
effective_name,
ratio
FROM split_bins
WHERE prev_quantity + effective_quantity < quantity
)
-- ################### final select data from with-clause
SELECT pos, ref_pos, article, quantity, maxquantity, bin_tag, bin_tag2,effective_quantity, prev_quantity,effective_name, ratio,effective_name||'_limit_'||connect_by_root bin_tag as id
FROM split_bins START WITH ref_pos IS NULL CONNECT BY PRIOR pos = ref_pos and PRIOR bin_tag2=bin_tag2
order by pos, bin_tag;
fiddle

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;

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

SQL: Combinations by Type

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 |

Create custom Function or Stored Procedure

I have a Hierarchy table with Master_id and Sub_id.
sub_id Master_id
2 1
3 2
4 1
5 3
6 7
I want to create an iterative function or stored procedure (I am not sure I never used any of them before) to create one more column which gives me the primary_master_Column (PMC)
sub_id Master_id PMC
2 1 1
3 2 1
4 1 1
5 3 1
6 7 7
select
Master_id, sub_id,
max(PMC) keep(dense_rank first order by lev desc) as PMC
from
(
select
sub_id as PMC, level lev,
connect_by_root(Master_id) as Master_id,
connect_by_root(sub_id) as sub_id
from your_table
connect by prior sub_id = Master_id
)
group by Master_id, sub_id
fiddle
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE test (sub_id, Master_id) AS
SELECT 2, 1 FROM DUAL
UNION ALL SELECT 3, 2 FROM DUAL
UNION ALL SELECT 4, 1 FROM DUAL
UNION ALL SELECT 5, 3 FROM DUAL
UNION ALL SELECT 6, 7 FROM DUAL;
Query 1:
SELECT t.sub_id,
t.master_id,
CONNECT_BY_ROOT( t.master_id ) AS PMC
FROM test t
LEFT OUTER JOIN
test x
ON ( t.master_id = x.sub_id )
START WITH x.sub_id IS NULL
CONNECT BY PRIOR t.sub_id = t.master_id
Results:
| SUB_ID | MASTER_ID | PMC |
|--------|-----------|-----|
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 5 | 3 | 1 |
| 4 | 1 | 1 |
| 6 | 7 | 7 |
Query 2:
SELECT t.sub_id,
t.master_id,
CONNECT_BY_ROOT( t.master_id ) AS PMC
FROM test t
START WITH NOT EXISTS ( SELECT 'x' FROM test x WHERE t.master_id = x.sub_id )
CONNECT BY PRIOR t.sub_id = t.master_id
Results:
| SUB_ID | MASTER_ID | PMC |
|--------|-----------|-----|
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 5 | 3 | 1 |
| 4 | 1 | 1 |
| 6 | 7 | 7 |