sql function to create sequential for repeated item- [duplicate] - sql

This question already has an answer here:
Add a sequence column in a query
(1 answer)
Closed 7 days ago.
I would like to know if there is an easy sql function to do a sequential classification for each duplicate record as shown in the image:
enter image description here

Looks like one of analytic functions, such as row_number (or perhaps rank).
Sample data:
SQL> with test (product, value) as
2 (select 'A', 100 from dual union all
3 select 'B', 100 from dual union all
4 select 'C', 100 from dual union all
5 select 'A', 100 from dual union all
6 select 'B', 100 from dual union all
7 select 'A', 100 from dual union all
8 select 'D', 100 from dual union all
9 select 'D', 100 from dual union all
10 select 'F', 100 from dual union all
11 select 'F', 100 from dual union all
12 select 'F', 100 from dual
13 )
Query:
14 select product, value,
15 row_number() over (partition by product order by null) classification
16 from test
17 order by product;
PRODUCT VALUE CLASSIFICATION
---------- ---------- --------------
A 100 1
A 100 2
A 100 3
B 100 1
B 100 2
C 100 1
D 100 1
D 100 2
F 100 1
F 100 2
F 100 3
11 rows selected.
SQL>

Related

Oracle SQL Grouping In Ranges

I am looking for ideas on how to group numbers into low and high ranges in Oracle SQL. I looking to to avoid cursors...any ideas welcome
Example input
ID
LOW
HIGH
A
0
2
A
2
3
A
3
5
A
9
11
A
11
13
A
13
15
B
0
1
B
1
4
B
7
9
B
11
12
B
12
17
B
17
18
Which would result in the following grouping into ranges
ID
LOW
HIGH
A
0
5
A
9
15
B
0
4
B
7
9
B
11
18
This is a Gaps & Islands problem. You can use the traditional solution.
For example:
select max(id) as id, min(low) as low, max(high) as high
from (
select x.*, sum(i) over(order by id, low) as g
from (
select t.*,
case when low = lag(high) over(partition by id order by low)
and id = lag(id) over(partition by id order by low)
then 0 else 1 end as i
from t
) x
) y
group by g
Result:
ID LOW HIGH
--- ---- ----
A 0 5
A 9 15
B 0 4
B 7 9
B 11 18
See running example at db<>fiddle.
From Oracle 12, you should use MATCH_RECOGNIZE for row-by-row pattern matching:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY id
ORDER BY low, high
MEASURES
FIRST(low) AS low,
MAX(high) AS high
PATTERN (overlapping* last_row)
DEFINE
overlapping AS NEXT(low) <= MAX(high)
)
Which, for the sample data:
CREATE TABLE table_name (id, low, high) AS
SELECT 'A', 0, 2 FROM DUAL UNION ALL
SELECT 'A', 2, 3 FROM DUAL UNION ALL
SELECT 'A', 3, 5 FROM DUAL UNION ALL
SELECT 'A', 9, 11 FROM DUAL UNION ALL
SELECT 'A', 11, 13 FROM DUAL UNION ALL
SELECT 'A', 13, 15 FROM DUAL UNION ALL
SELECT 'B', 0, 1 FROM DUAL UNION ALL
SELECT 'B', 1, 4 FROM DUAL UNION ALL
SELECT 'B', 7, 9 FROM DUAL UNION ALL
SELECT 'B', 11, 12 FROM DUAL UNION ALL
SELECT 'B', 12, 17 FROM DUAL UNION ALL
SELECT 'B', 17, 18 FROM DUAL UNION ALL
SELECT 'C', 0, 10 FROM DUAL UNION ALL
SELECT 'C', 1, 3 FROM DUAL UNION ALL
SELECT 'C', 5, 8 FROM DUAL UNION ALL
SELECT 'C', 9, 15 FROM DUAL UNION ALL
SELECT 'C', 10, 14 FROM DUAL UNION ALL
SELECT 'C', 11, 13 FROM DUAL;
Outputs:
ID
LOW
HIGH
A
0
5
A
9
15
B
0
4
B
7
9
B
11
18
C
0
15
fiddle

Compute "Amount" using values from tables

I have the following tables already in my DB
EMP
E_N E_NAM E_RATE E_DEP
--- ----- ---------- -----
1 A 400
2 B 200 1
3 C 150 2
4 D 150 3
5 E 120 1
6 F 100 1
7 G 100 2
8 H 50 2
9 I 50 3
10 J 50 3
11 K 150 3
WORKS
E_NO PR_NO HRS
--- --- ----------
2 1 10
3 2 20
5 1 20
5 2 20
5 3 20
6 1 10
6 2 10
I have to compute the amount billed to each project as AMOUNT, and that is the sum of the amount billed to the project by all employees who work on said project. The amount billed being E_RATE*HRS (product of HRS and E_RATE).
There are only 3 PR_NO: 1, 2 and 3.
I've tried this multiple times with no avail, I know that it has to be a nested query and the calculation to be shown AS AMOUNT, but no clue on how exactly to only display the 3 projects with the calculation already made.
Sounds like simple join and aggregation:
select w.pr_no,
sum(w.hrs * e.e_rate) as amount
from works w
join emp e on w.e_no = e.e_n
group by w.pr_no;
simple aggregate SUM() function after joining the tables
--test data
with EMP(e_no, e_name, e_rate, e_dep) as
(select 1, 'A', 400, null from dual union all
select 2, 'B', 200, 1 from dual union all
select 3, 'C', 150, 2 from dual union all
select 4, 'D', 150, 3 from dual union all
select 5, 'E', 120, 1 from dual union all
select 6, 'F', 100, 1 from dual union all
select 7, 'G', 100, 2 from dual union all
select 8, 'H', 50, 2 from dual union all
select 9, 'I', 50, 3 from dual union all
select 10, 'J', 50, 3 from dual union all
select 11, 'K', 150, 3 from dual),
WORKS(e_no, pr_no, hrs) as
(select 2, 1, 10 from dual union all
select 3, 2, 20 from dual union all
select 5, 1, 20 from dual union all
select 5, 2, 20 from dual union all
select 5, 3, 20 from dual union all
select 6, 1, 10 from dual union all
select 6, 2, 10 from dual)
-- actual query starts here
select w.pr_no, sum(w.hrs*e.e_rate) as amount
from works w
inner join emp e on (w.e_no = e.e_no)
group by w.pr_no;
"PR_NO"|"AMOUNT"
1|5400
2|6400
3|2400

SQL How to align ranges of data points in rows?

Suppose having the data set:
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
)
select * from data_table;
TITLE | X
-----------
a 1
a 3
a 4
a 5
b 1
b 2
b 3
b 6
Wee see that points related to a and b are different.
How to align values in X column so both groups have the same points, filling the gaps with NULL?
Expected result is:
TITLE | X
-----------
a 1
a NULL
a 3
a 4
a 5
a NULL
b 1
b 2
b 3
b NULL
b NULL
b 6
Straightforward solution I got is
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
),
all_points(x) AS (
select distinct x from data_table
),
all_titles(title) AS (
select distinct title from data_table
),
aligned_data(title, x) as (
select t.title, p.x from all_points p cross join all_titles t
)
select ad.title, dt.x
from aligned_data ad
left join data_table dt on dt.title = ad.title and dt.x = ad.x
order by ad.title, ad.x;
As wee see cross join in aligned_data definition is bottleneck and can kill performance on valuable data sets.
I wonder if this task could be solved more elegantly. Maybe a trick with window functions can be proposed.

How to sort alphanumeric String in oracle?

Input is:
Section1
Section2
Section3
Section10
Section11
Section1A
Section1B
Section12
Section11A
Section11B
And I want output like:
Section1
Section1A
Section1B
Section2
Section3
Section10
Section11
Section11A
Section11B
Section12
I tried query :
select section_name
from sections
order by length(section_name),section_name
Assuming that the structure of your strings is fixed, as in your example, this could be a way:
SQL> select x,
2 to_number(regexp_substr(x, '[0-9]+')) numericPart,
3 regexp_substr(x, '([0-9]+)([A-Z])', 1, 1, '', 2) optionalChar
4 from (
5 select 'Section1' x from dual union all
6 select 'Section2' from dual union all
7 select 'Section3' from dual union all
8 select 'Section10' from dual union all
9 select 'Section11' from dual union all
10 select 'Section1A' from dual union all
11 select 'Section1B' from dual union all
12 select 'Section12' from dual union all
13 select 'Section11A' from dual union all
14 select 'Section11B' from dual
15 )
16 order by numericPart,
17 optionalChar nulls first
18 ;
X NUMERICPART OPTIONALCHAR
---------- ----------- ----------------------------------------
Section1 1
Section1A 1 A
Section1B 1 B
Section2 2
Section3 3
Section10 10
Section11 11
Section11A 11 A
Section11B 11 B
Section12 12
Here you first order by the numeric part, treating it as number, and then consider the (optional) character after the number.

Oracle SQL query previous row result

I am just stuck now, now getting the logic to solve this query. Find the below tables and the output. There is Table A and table B which matches two column ID and DATE. If Date got matched then it should multiply the qty with percent else it should pick previous percent.
Table A Table B
ID Date Percent ID Date Qty
A 01/01/17 0.5 A 01/01/17 10
A 04/01/17 1 A 02/01/17 20
A 06/01/17 2 A 03/01/17 30
B 02/01/17 5 A 05/01/17 40
B 05/01/17 10 A 06/01/17 50
A 07/01/17 60
A 08/01/17 40
B 01/01/17 10
B 02/01/17 50
============================================
column column column comment comment comment
ID Date Qty Previous percent if row not matched
A 01/01/17 0.5 * 10 0.5 got new percent
A 02/01/17 0.5 * 20 0.5
A 03/01/17 0.5 * 30 0.5
A 04/01/17 1* 0 1 got new percent but no qty found
A 05/01/17 1 * 40 1
A 06/01/17 2 * 50 2 got new percent
B 01/01/17 10 * 0 0 no percent found
B 02/02/17 5 * 10 5 got new percent
B 5/1/17 10 * 0 10 got new percent
I interpreted the problem to mean "add a few more columns to Table B" - to show the "most current" percentage, showing its date from Table A, and the "gross quantity" from Table B and the "net quantity" by multiplying by the proper percentage.
If you also need one row for every row in Table A, simply delete the WHERE clause from the outermost query (towards the bottom of the query).
with table_a ( id, dt, pct ) as (
select 'A', to_date('01/01/17', 'mm/dd/rr'), 0.5 from dual union all
select 'A', to_date('04/01/17', 'mm/dd/rr'), 1 from dual union all
select 'A', to_date('06/01/17', 'mm/dd/rr'), 2 from dual union all
select 'B', to_date('02/01/17', 'mm/dd/rr'), 5 from dual union all
select 'B', to_date('05/01/17', 'mm/dd/rr'), 10 from dual
), table_b ( id, dt, qty ) as (
select 'A', to_date('01/01/17', 'mm/dd/rr'), 10 from dual union all
select 'A', to_date('02/01/17', 'mm/dd/rr'), 20 from dual union all
select 'A', to_date('03/01/17', 'mm/dd/rr'), 30 from dual union all
select 'A', to_date('05/01/17', 'mm/dd/rr'), 40 from dual union all
select 'A', to_date('06/01/17', 'mm/dd/rr'), 50 from dual union all
select 'A', to_date('07/01/17', 'mm/dd/rr'), 60 from dual union all
select 'A', to_date('08/01/17', 'mm/dd/rr'), 40 from dual union all
select 'B', to_date('01/01/17', 'mm/dd/rr'), 10 from dual union all
select 'B', to_date('02/01/17', 'mm/dd/rr'), 50 from dual
)
-- end of test data (not part of the SQL query); query begins BELOW THIS LINE
select id, qty_date, pct_date, qty as gross_qty, pct, qty * pct as net_qty
from ( select id, dt as qty_date,
last_value(case flag when 0 then dt end ignore nulls)
over (partition by id order by dt) as pct_date,
last_value(pct ignore nulls)
over (partition by id order by dt, flag) as pct,
qty, flag
from ( select id, dt, pct, null as qty, 0 as flag
from table_a
union all
select id, dt, null as pct, qty, 1 as flag
from table_b
)
)
where flag = 1
order by id, qty_date -- if needed
;
Output:
ID QTY_DATE PCT_DATE GROSS_QTY PCT NET_QTY
-- ---------- ---------- ---------- ---------- ----------
A 2017-01-01 2017-01-01 10 .5 5
A 2017-02-01 2017-01-01 20 .5 10
A 2017-03-01 2017-01-01 30 .5 15
A 2017-05-01 2017-04-01 40 1 40
A 2017-06-01 2017-06-01 50 2 100
A 2017-07-01 2017-06-01 60 2 120
A 2017-08-01 2017-06-01 40 2 80
B 2017-01-01 10
B 2017-02-01 2017-02-01 50 5 250
9 rows selected.