How to change rownum only if the next row is distinct - oracle

I have this
RowNum
Value
1
X
2
X
3
Y
4
Z
5
Z
6
Z
7
V
and I want something like this
RowNum
Value
1
X
1
X
2
Y
3
Z
3
Z
3
Z
4
V
How can I do that in Oracle?
Thanks

Here is one way - using the match_recognize clause, available since Oracle 12.1. Note that rownum is a reserved keyword, so it can't be a column name; I changed it to rnum in the input and rn in the output, adapt as needed.
The with clause is not part of the query - it's there just to include your sample data. Remove it before using the query on your actual data.
with
inputs (rnum, value) as (
select 1, 'X' from dual union all
select 2, 'X' from dual union all
select 3, 'Y' from dual union all
select 4, 'Z' from dual union all
select 5, 'Z' from dual union all
select 6, 'Z' from dual union all
select 7, 'V' from dual
)
select rn, value
from inputs
match_recognize (
order by rnum
measures match_number() as rn
all rows per match
pattern ( a+ )
define a as value = first(value)
);
RN VALUE
-- -----
1 X
1 X
2 Y
3 Z
3 Z
3 Z
4 V
In Oracle 11.2 and earlier, you can use the start-of-group method (the flags created in the subquery and counted in the outer query):
select count(flag) over (order by rnum) as rn, value
from (
select rnum, value,
case lag(value) over (order by rnum)
when value then null else 1 end as flag
from inputs
)
;

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

Row data multiplication in Oracle

Consider following table where I am doing row data multiplication:
with v1 (member_id, the_number) as
(
select 1, 3 from dual union all
select 1, 5 from dual union all
select 2, 2 from dual union all
select 2, 3 from dual union all
select 2, 4 from dual union all
select 3, 9 from dual union all
select 3, 3 from dual union all
select 3, 2 from dual
)
select member_id, EXP(SUM(LN(the_number))) from v1
GROUP BY member_id;
It gives the correct result as:
MEMBER_ID EXP(SUM(LN(THE_NUMBER)))
1 15
2 24
3 54
The moment I put a negative value in the the_number column, I get the following Oracle error: ORA-01428: argument 'x' is out of range This is because the range for LN () argument is > 0.
How can I modify the query so that I can have negative values as well in the_number column? I am using Oracle 11g.
Get the product of the absolute values of the numbers and finally multiply by -1 or 1 depending on whether there is an odd or even number of negative numbers:
select
member_id,
CASE WHEN MOD(SUM(CASE WHEN the_number < 0 THEN 1 ELSE 0 END), 2) = 1 THEN -1 ELSE 1 END *
EXP(SUM(LN(ABS(the_number)))) from v1
GROUP BY member_id;
See the demo.

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 SQL Create Corresponding Labels for Paired Right Left Sequential Numbers with Identifiers

I am trying to pair left right parts in a table. I received an answer for a very similar question pairing left right parts but I now need to create a new column stating the corresponding paired part.
For example I have
ID LR Identifier
1 L B15A
2 R A15C
3 L A15C
4 R A15C
5 L A15C
6 R D5A2
9 R D5A2
10 L E5A6
11 R E5A6
12 L E5A6
13 R E5A6
14 R H9S5
17 L EE5A
18 R EE5A
and I need the query to return
ID LR Identifier Counterhand Counterpart
2 R A15C L 3
3 L A15C R 2
4 R A15C L 5
5 L A15C R 4
10 L E5A6 R 11
11 R E5A6 L 10
12 L E5A6 R 13
13 R E5A6 L 12
17 L EE5A R 18
18 R EE5A L 17
The link to the previous question is Here. Very helpfully answered by #mathguy
You can modify #mathguys previous solution and check if each result is the first or second in a pair:
mod(row_number() over (partition by identifier order by id), 2)
will give you either 0 or 1; and then you can then choose to either lead or lag to get the previous or next hand/ID value:
case mod(row_number() over (partition by identifier order by id), 2)
when 0 then lag(lr) over (partition by identifier order by id)
else lead(lr) over (partition by identifier order by id) end as counterhand
Blatantly copying and extending #mathguy's previous code:
with
test_data ( id, lr, identifier ) as (
select 001, 'L', 'B15A' from dual union all
select 002, 'R', 'A15C' from dual union all
select 003, 'L', 'A15C' from dual union all
select 004, 'R', 'A15C' from dual union all
select 005, 'L', 'A15C' from dual union all
select 006, 'R', 'D5A2' from dual union all
select 009, 'R', 'D5A2' from dual union all
select 010, 'L', 'E5A6' from dual union all
select 011, 'R', 'E5A6' from dual union all
select 012, 'L', 'E5A6' from dual union all
select 013, 'R', 'E5A6' from dual union all
select 014, 'R', 'H9S5' from dual union all
select 017, 'L', 'EE5A' from dual union all
select 018, 'R', 'EE5A' from dual
)
-- end of test data, the solution (SQL query) begins below this line
select id, lr, identifier,
case mod(row_number() over (partition by identifier order by id), 2)
when 0 then lag(lr) over (partition by identifier order by id)
else lead(lr) over (partition by identifier order by id) end as counterhand,
case mod(row_number() over (partition by identifier order by id), 2)
when 0 then lag(id) over (partition by identifier order by id)
else lead(id) over (partition by identifier order by id) end as counterpart
from ( select id, lr, identifier,
row_number() over (partition by identifier, lr order by id) as rn,
least( count(case when lr = 'L' then 1 end) over (partition by identifier),
count(case when lr = 'R' then 1 end) over (partition by identifier)
) as least_count
from test_data
)
where rn <= least_count
order by id -- ORDER BY is optional
;
gives you:
ID L IDEN C COUNTERPART
---------- - ---- - -----------
2 R A15C L 3
3 L A15C R 2
4 R A15C L 5
5 L A15C R 4
10 L E5A6 R 11
11 R E5A6 L 10
12 L E5A6 R 13
13 R E5A6 L 12
17 L EE5A R 18
18 R EE5A L 17
10 rows selected.
The same two case statements can be added to the second version in #mathguy's previous answer, and gives the same result.
Continuing to build on prior answer... #mathguy did the hard work, and after reading Alex's answer I think his is more compact, i'm using a union that he's not, we both used ROw_number and mod.
with cte2(ID, LR, Identifier) as (
SELECT 1,'L','B15A' FROM DUAL UNION ALL
SELECT 2,'R','A15C' FROM DUAL UNION ALL
SELECT 3,'L','A15C' FROM DUAL UNION ALL
SELECT 4,'R','A15C' FROM DUAL UNION ALL
SELECT 5,'L','A15C' FROM DUAL UNION ALL
SELECT 6,'R','D5A2' FROM DUAL UNION ALL
SELECT 9,'R','D5A2' FROM DUAL UNION ALL
SELECT 10,'L','E5A6' FROM DUAL UNION ALL
SELECT 11,'R','E5A6' FROM DUAL UNION ALL
SELECT 12,'L','E5A6' FROM DUAL UNION ALL
SELECT 13,'R','E5A6' FROM DUAL UNION ALL
SELECT 14,'R','H9S5' FROM DUAL UNION ALL
SELECT 17,'L','EE5A' FROM DUAL UNION ALL
SELECT 18,'R','EE5A' FROM DUAL),
cte1 as (select id, lr, identifier
from ( select id, lr, identifier,
row_number() over (partition by identifier, lr order by id) as rn,
least( count(case when lr = 'L' then 1 end) over (partition by identifier),
count(case when lr = 'R' then 1 end) over (partition by identifier)
) as least_count
from Cte2
)
where rn <= least_count
order by id),
cte as (Select A.*, Row_number() over (order by ID) RN from cte1 A)
SELECT A.Id
, A.LR
, A.Identifier
, case when A.LR = 'R' then 'L'
when A.LR = 'L' then 'R' end as CounterHand
, B.ID as CounterPart
FROM Cte A
INNER JOIN cte B
on A.RN+1 = B.RN
WHERE mod(A.RN,2)=1
UNION
SELECT B.Id
, B.LR
, B.Identifier
, case when B.LR = 'R' then 'L'
when B.LR = 'L' then 'R' end as CounterHand
, A.ID as CounterPart
FROM cte A
INNER JOIN cte B
on A.RN = B.RN-1
WHERE mod(B.RN,2)=0

Build dynamic query in oracle

Could you help me building a query as below?
TABLE_A
id brand color size model yn_buy
1 A blue M A -
2 A grey X C -
3 B red X B -
4 C blue S C -
TABLE_B
brand critery
A color=grey and size=X
B color=red
C size=M
I want to build a query to update table A and the answer should be:
TABLE_A
id brand color size model yn_buy
1 A blue M A N
2 A grey X C Y
3 B red X B Y
4 C blue S C N
As you can see the data on the column "critery" should be the decisor to buy or not
I'd like to use a single merge, like the following
merge into TABLE_A a
using
(
select id, brand, CASE WHEN critery THEN 'Y' ELSE 'N' END yn_buy
TABLE_A a
left join TABLE_B b ON a.brand = b.brand
) b
ON (a.id = b.id)
WHEN MATCHED THEN UPDATE set a.yn_buy = b.yn_buy
Is it possible to do something like this? maybe using execute immediate, some kind of bind...?
thank you
If I don't miss something in your particular case you don't need dynamic SQL - you can simply change TABLE_B structure and use static MERGE (because I don't know how complex your criteria can be this is just an illustration):
SQL> create table table_a (id, brand, color, size#, model#, tn_buy) as
2 select 1, 'A', 'blue', 'M', 'A', cast(null as varchar2(3)) from dual union all
3 select 2, 'A', 'grey', 'X', 'C', cast(null as varchar2(3)) from dual union all
4 select 3, 'B', 'red', 'X', 'B', cast(null as varchar2(3)) from dual union all
5 select 4, 'C', 'blue', 'S', 'C', cast(null as varchar2(3)) from dual
6 /
Table screated.
SQL> create table TABLE_B (brand, color, size#)
2 as
3 select 'A', 'grey', 'X' from dual union all
4 select 'B', 'red', null from dual union all
5 select 'C', null, 'M' from dual
6 /
Table created.
SQL> merge into TABLE_A a USING (
2 select a.id, a.brand, CASE
3 WHEN a.color = nvl(a.color, a.color) and a.size# = nvl(b.size#,a.size#)
4 THEN 'Y' ELSE 'N' END yn_buy FROM
5 TABLE_A a
6 left join TABLE_B b ON a.brand = b.brand
7 ) b
8 ON (a.id = b.id)
9 WHEN MATCHED THEN UPDATE set a.yn_buy = b.yn_buy
10 /
4 rows merged.
SQL> select * from table_a order by id;
ID B COLO S M YN_
---------- - ---- - - ---
1 A blue M A N
2 A grey X C Y
3 B red X B Y
4 C blue S C N
But of your criteria are difficult enough to be implemented using simple static condition, then you can use dynamic SQL:
SQL> create table TABLE_B1 (brand, criteria)
2 as
3 select 'A', q'[color='grey' and size# in ('X','M')]' from dual union all
4 select 'B', q'[color = 'red']' from dual union all
5 select 'C', q'[size#='M']' from dual
6 /
Table created.
SQL> update table_a set yn_buy = null;
4 rows updated.
SQL> commit;
Committed.
SQL> begin
2 for cur in (select brand, criteria from table_b1) loop
3 execute immediate
4 'update table_a set yn_buy = case when '||cur.criteria||
5 q'[ then 'Y' else 'N' end where brand = :1]' using cur.brand;
6 end loop;
7 end;
8 /
PL/SQL procedure completed.
SQL> select * from table_a;
ID B COLO S M YN_
---------- - ---- - - ---
1 A blue M A N
2 A grey X C Y
3 B red X B Y
4 C blue S C N

Resources