Oracle query for selecting sampling of number values from a table - sql

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

Related

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

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;

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 |

Select a row X times

I have a very specific sql problem.
I have a table given with order positions (each position belongs to one order, but this isn't a problem):
| Article ID | Amount |
|--------------|----------|
| 5 | 3 |
| 12 | 4 |
For the customer, I need an export with every physical item that is ordered, e.g.
| Article ID | Position |
|--------------|------------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |
How can I build my select statement to give me this results? I think there are two key tasks:
1) Select a row X times based on the amount
2) Set the position for each physical article
You can do it like this
SELECT ArticleID, n.n Position
FROM table1 t JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
) n
ON n.n <= t.amount
ORDER BY ArticleID, Position
Note: subquery n generates a sequence of numbers on the fly from 1 to 100. If you do a lot of such queries you may consider to create persisted tally(numbers) table and use it instead.
Here is SQLFiddle demo
or using a recursive CTE
WITH tally AS (
SELECT 1 n
UNION ALL
SELECT n + 1 FROM tally WHERE n < 100
)
SELECT ArticleID, n.n Position
FROM table1 t JOIN tally n
ON n.n <= t.amount
ORDER BY ArticleID, Position
Here is SQLFiddle demo
Output in both cases:
| ARTICLEID | POSITION |
|-----------|----------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |
Query:
SQLFIDDLEExample
SELECT t1.[Article ID],
t2.number
FROM Table1 t1,
master..spt_values t2
WHERE t1.Amount >= t2.number
AND t2.type = 'P'
AND t2.number <= 255
AND t2.number <> 0
Result:
| ARTICLE ID | NUMBER |
|------------|--------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |