Ranking with Duplicates - oracle

I am using Oracle Apex and I am ranking call speed. I am almost getting the results I am after. The only issue that I have faced is that when the rank function comes across duplicate values, by default they both get assigned the lowest Rank. e.g.
Rank Call Speed
1 65
2 72
3 92
4 102
4 102
4 102
4 102
4 102
9 113
10 154
11 201
12 352
Is there anyway to have the 4's represent as 8's (the highest rank of the duplicates)
One way of doing this is by using the ranking Descending and then subtracting this from the highest rank + 1. this works but seems like an unnecessary step.
Any help will be much appreciated

Kind of a strange thing to do, but I would do something like:
with data as (
select 65 call_speed from dual union all
select 72 call_speed from dual union all
select 92 call_speed from dual union all
select 102 call_speed from dual connect by level <= 5 union all
select 113 call_speed from dual union all
select 154 call_speed from dual union all
select 201 call_speed from dual union all
select 352 call_speed from dual
)
select
rank() over (order by call_speed) + count(*) over (partition by call_speed) - 1 rank,
call_speed
from data;
Which gives you:
RANK CALL_SPEED
---------- ----------
1 65
2 72
3 92
8 102
8 102
8 102
8 102
8 102
9 113
10 154
11 201
12 352

Just an alternative, for no reason at all, except maybe to avoid any memory overhead (?) from doing a partitioned count:
with data as (
select 65 call_speed from dual union all
select 72 call_speed from dual union all
select 92 call_speed from dual union all
select 102 call_speed from dual connect by level <= 5 union all
select 113 call_speed from dual union all
select 154 call_speed from dual union all
select 201 call_speed from dual union all
select 352 call_speed from dual
)
select
count(*) over () + 1 - rank() over (order by call_speed desc) rank,
call_speed
from data
order by call_speed;

Related

oracle sql split string to rows (alphabet in sequential order) - Oracle SQL

I am looking for a oracle sql solution for the below problem:
Here's one option (as union of 3 different cases).
Sample data:
SQL> with test (col) as
2 (select 'A-D' from dual union all
3 select 'J-K' from dual union all
4 select 'X-20' from dual union all
5 select 'XX-20542' from dual union all
6 select 'A/B' from dual union all
7 select 'J/K' from dual
8 )
Query begins here:
9 -- A-D option:
10 select col,
11 chr(ascii(substr(col, 1, 1)) + column_value - 1) res
12 from test cross join
13 table(cast(multiset(select level from dual
14 connect by level <= ascii(substr(col, -1)) - ascii(substr(col, 1, 1)) + 1
15 ) as sys.odcinumberlist))
16 where regexp_like(col, '[[:alpha:]]-[[:alpha:]]')
17 -- XX-20542 option:
18 union all
19 select col, 'No action / ignore'
20 from test
21 where regexp_like(col, '[[:alpha:]]+-\d+')
22 -- A/B option:
23 union all
24 select col,
25 regexp_substr(col, '[[:alpha:]]', 1, column_value)
26 from test cross join
27 table(cast(multiset(select level from dual
28 connect by level <= ascii(substr(col, -1)) - ascii(substr(col, 1, 1)) + 1
29 ) as sys.odcinumberlist))
30 where regexp_like(col, '[[:alpha:]]/[[:alpha:]]');
Result:
COL RES
-------- ------------------
A-D A
A-D B
A-D C
A-D D
J-K J
J-K K
X-20 No action / ignore
XX-20542 No action / ignore
A/B A
A/B B
J/K J
J/K K
12 rows selected.
SQL>
You can use a single recursive query:
WITH ranges (value, min_value, skip, max_value) AS (
SELECT value,
REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 1),
CASE REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 2)
WHEN '-'
THEN 0
ELSE 1
END,
REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 3)
FROM table_name
UNION ALL
SELECT value,
CASE skip
WHEN 0
THEN CHR(ASCII(min_value) + 1)
ELSE max_value
END,
skip,
max_value
FROM ranges
WHERE min_value < max_value
)
SEARCH DEPTH FIRST BY value SET rn
SELECT value,
COALESCE(min_value, 'ignore') AS result
FROM ranges
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT 'A-D' FROM DUAL UNION ALL
SELECT 'J-K' FROM DUAL UNION ALL
SELECT 'XX-Y' FROM DUAL UNION ALL
SELECT 'Y-ZZ' FROM DUAL UNION ALL
SELECT 'B/C' FROM DUAL UNION ALL
SELECT 'E/E' FROM DUAL UNION ALL
SELECT 'F/H' FROM DUAL;
Outputs:
VALUE
RESULT
A-D
A
A-D
B
A-D
C
A-D
D
B/C
B
B/C
C
E/E
E
F/H
F
F/H
H
J-K
J
J-K
K
XX-Y
ignore
Y-ZZ
ignore
db<>fiddle here

Need output in below format in oracle. I have tried with lag function it worked if we have only 2 rows in table

Consider below table table.
Id balance
1 100
2 500
3 4000
I need output in below format.
Id balance begin_bal end_bal
1 100 0 100
2 500 100 600
3 4000 600 4600
A little bit of analytics, as you presumed:
SQL> with test (id, balance) as
2 (select 1, 100 from dual union all
3 select 2, 500 from dual union all
4 select 3, 4000 from dual
5 ),
6 temp as
7 (select id, balance, sum(balance) over (order by id) rsum
8 from test
9 )
10 select id,
11 balance,
12 nvl(lag(rsum) over (order by id), 0) begin_bal,
13 rsum end_bal
14 from temp
15 order by id;
ID BALANCE BEGIN_BAL END_BAL
---------- ---------- ---------- ----------
1 100 0 100
2 500 100 600
3 4000 600 4600
SQL>

Want to convert rows to columns

I have one table t as below
Customer _no | receipt_no
----------------------------
A | 123
A. | 234
A. | 345
B. | 465
B. | 675
I want result as
Customer _no | receipt_1 | receipt_2
A. | 123. | 234
A. | 345. | Null
B. | 465. | 675
Please suggest how to do this
If I understood it correctly, you want to have two columns for each customer. If that's so, here's one option.
SQL> with test (cno, rno) as
2 (select 'A', 123 from dual union all
3 select 'A', 234 from dual union all
4 select 'A', 345 from dual union all
5 select 'A', 444 from dual union all
6 select 'A', 555 from dual union all
7 select 'B', 456 from dual union all
8 select 'B', 675 from dual
9 ),
10 inter as
11 (select cno, rno,
12 row_number() over (partition by cno order by cno, rno) rn,
13 round(row_number() over (partition by cno order by cno, rno) /2) grp
14 from test
15 )
16 select cno,
17 max(decode(mod(rn, 2), 1, rno)) r1,
18 max(decode(mod(rn, 2), 0, rno)) r2
19 from inter
20 group by cno, grp
21 order by cno, grp;
C R1 R2
- ---------- ----------
A 123 234
A 345 444
A 555
B 456 675
SQL>

Preventing running total from going negative in Oracle

Column 'amount' has value 5 in first row, and value -10 in second row.
Is there a way to make oracle's sum(amount) over() function to return 0 instead of -5 for the second row?
Blatantly using Rajesh Chamarthi's example source: but altering to show more negative and positive... and showing how a case would change all the negative to zero while maintaining the other amounts...
with t as (
select 5 as x, 1 as id from dual
union all
select -10, 2 as id from dual
union all
select 7, 3 as id from dual
union all
select -5, 4 as id from dual
union all
select -2, 5 as id from dual
),
B as (select t.x,
case when sum(x) over (order by id) < 0 then 0
else sum(x) over (order by id)
end Amount
from t
order by id)
Select X, Case when amount < 0 then 0 else amount end as Amount from B;
T Amount
5 5
-10 0
7 2
-5 0
-2 0
----Attempt 2 (1st attempt preserved as comments below reference it)
I couldn't figure out how to interrupt the window function to reset the value to 0 when amount fell below 0... so I used a recursive CTE which gave me greater control.
If id's are not sequential, we could add a row_Number so we have an ID to join on... or we could use min() where > oldID. I assumed we have a single key unique ID or some way of "Sorting" the records in the order you want the sum to occur...
with aRaw as (
select 5 as x, 15 as id from dual
union all
select -10, 20 as id from dual
union all
select 7, 32 as id from dual
union all
select 2, 46 as id from dual
union all
select -15, 55 as id from dual
union all
select 3, 68 as id from dual
),
t as (Select A.*, Row_number() over (order by ID) rn from aRAW A),
CTE(RN, ID, x, SumX) AS (
Select T.RN, T.ID, x, X from t WHERE ID = (Select min(ID) from t)
UNION ALL
Select T.RN, T.ID, T.X, case when T.X+CTE.SumX < 0 then 0 else T.X+Cte.sumX end from T
INNER JOIN CTE
on CTE.RN+1=T.RN)
Select * from cte;
.
CTE: ARaw is just a sample data set
CTE: T adds a sequental row number incase there are gaps in the IDs allowing for a more simple joining approach on the recursive CTE.
CTE: CTE is the recursive CTE that keeps a running total and has a case statement to reset the running total to 0 when it falls below 0
You could use a case statement, but that would not be a true running total
with t as (
select 5 as x, 1 as id from dual
union all
select -10, 2 as id from dual
union all
select 20, 3 as id from dual
union all
select 30, 4 as id from dual
union all
select 10, 5 as id from dual
)
select t.x,
case when sum(x) over (order by id) < 0 then 0
else sum(x) over (order by id)
end running_total
from t
order by id;
X RUNNING_TOTAL
5 5
-10 0
20 15
30 45
10 55

Oracle: How to restart row_number function

I have a scenario where in I need to assign row_number for a data grouped by department, branch, amount and ordering by date.
If the amount repeats after some dates for the same department & branch, I would like to have the row_number as 1.
Could you please let me know how to achieve this?
When I tried with Row_number function the numbering is continuing after some dates.
Sample Data:
This is the row numbers I am getting if the following function is used row_number() over(Partition by Department, Branch, Amount order by Date)
Department Branch Amount Date Row_number()
Dep A Bran 1 51 25-Oct-12 1
Dep A Bran 1 45.5 26-Nov-12 1
Dep A Bran 1 45.5 05-Apr-13 2
Dep A Bran 1 45.5 06-May-13 3
Dep A Bran 1 65 07-May-13 1
Dep A Bran 1 51 26-Aug-13 2
Dep A Bran 1 51 11-Sep-13 3
But I am expecting output in the below order.
Department Branch Amount Date Row_number()
Dep A Bran 1 51 25-Oct-12 1
Dep A Bran 1 45.5 26-Nov-12 1
Dep A Bran 1 45.5 05-Apr-13 2
Dep A Bran 1 45.5 06-May-13 3
Dep A Bran 1 65 07-May-13 1
Dep A Bran 1 51 26-Aug-13 1
Dep A Bran 1 51 11-Sep-13 2
Could anyone help me on this?
You need to change how you identify the groups inside which you calculate row_number().
Something like:
SQL> with t (Department, Branch, Amount, Date#)
2 as (
3 select 'Dep A', 'Bran 1', 51, to_date('25-10-2012','DD-MM-YYYY') from dual union all
4 select 'Dep A', 'Bran 1', 45.5, to_date('26-11-2012','DD-MM-YYYY') from dual union all
5 select 'Dep A', 'Bran 1', 45.5, to_date('05-04-2013','DD-MM-YYYY') from dual union all
6 select 'Dep A', 'Bran 1', 45.5, to_date('06-05-2013','DD-MM-YYYY') from dual union all
7 select 'Dep A', 'Bran 1', 65, to_date('07-05-2013','DD-MM-YYYY') from dual union all
8 select 'Dep A', 'Bran 1', 51, to_date('26-08-2013','DD-MM-YYYY') from dual union all
9 select 'Dep A', 'Bran 1', 51, to_date('11-09-2013','DD-MM-YYYY') from dual
10 )
11 select department, branch, amount, date#, row_number() over(partition by grp order by date#) rn
12 from (
13 select department, branch, amount, date#, sum(st_grp) over(order by date#) grp from (
14 select department, branch, amount, date#, case when amount = lag(amount,1,amount) over(order by date#) then 0 else 1 end st_grp from t
15 )
16 )
17 order by date#
18 /
DEPAR BRANCH AMOUNT DATE# RN
----- ------ ---------- -------- ----------
Dep A Bran 1 51 25.10.12 1
Dep A Bran 1 45,5 26.11.12 1
Dep A Bran 1 45,5 05.04.13 2
Dep A Bran 1 45,5 06.05.13 3
Dep A Bran 1 65 07.05.13 1
Dep A Bran 1 51 26.08.13 1
Dep A Bran 1 51 11.09.13 2

Resources