oracle: Count occurrence of a status in consecutive rows - oracle

I have a table which stores status of a customer reply in oracle. I have to count last consecutive declines from the customer.
For ex:
Id Status
-----------------------------
1. Declined
2. Accepted
3. Declined
4. Declined
This will have count = 2.
As last two were declined.

NOTE: Original Poster clarified in a "comment" that she needed a different requirement - addressing it in a separate Answer. Keeping this since it shows one possible solution for a more complicated problem that the OP's.
Assuming you want the count of the most recent consecutive "Declines" (even if followed by "Accepted" - and you want to allow for more than one customer - here is one possible solution.
Input table (I called it "t"):
SQL> select * from t;
CUSTOMER_ID DECISION_ID STATUS
----------- ----------- --------------------
10 1 Accepted
10 2 Declined
10 3 Declined
10 4 Accepted
10 5 Accepted
10 6 Declined
10 7 Declined
30 1 Declined
30 2 Accepted
30 3 Declined
30 4 Accepted
30 5 Declined
30 6 Declined
30 7 Declined
30 8 Accepted
30 9 Accepted
Query:
with t1 as
(
select customer_id,
decision_id - row_number() over
(partition by customer_id order by decision_id) as idx
from t
where status = 'Declined'
),
t2 as (select customer_id, max(idx) as max_idx from t1 group by customer_id)
select t1.customer_id, count(1) as ct
from t1 join t2 on t1.customer_id = t2.customer_id
where t1.idx = t2.max_idx
group by t1.customer_id
order by t1.customer_id
/
Query output:
CUSTOMER_ID CT
----------- ----------
10 2
30 3

#Gurmeet - Then the problem is much easier. Here is one way to solve it. If you need the result ordered by customer_id, add order by customer_id right at the end. nvl in the definition of d_A in CTE t2 is needed in case that customer never had a transaction with "Accepted" status.
Input: Same as in my other Answer.
Query: (MODIFIED to meet OP additional requirement):
with t0 as (select customer_id, status, row_number() over
(partition by customer_id order by decision_id) rn from t),
t1 as (select customer_id, max(rn) as d_all from t0 group by customer_id),
t2 as (select customer_id, nvl(max(rn), 0) as d_A from t0
where status = 'Accept' group by customer_id)
select customer_id, d_all - d_A as ct from t1 natural join t2
/
Result:
CUSTOMER_ID CT
----------- ----------
30 0
10 2
2 rows selected.

Related

PL SQL function that includes multiple tables

I'm new to PL SQL and have to write a function, which has customer_id as an input and has to output a product_name of the best selling product for that customer_id.
The schema looks like this:
I found a lot of simple examples where it includes two tables, but I can't seem to find one where you have to do multiple joins and use a function, while selecting only the best selling product.
I could paste a lot of very bad code here and how I tried to approach this, but this seems to be a bit over my head for current knowledge, since I've been learning PL SQL for less than 3 days now and got this task.
With some sample data (minimal column set):
SQL> select * from products order by product_id;
PRODUCT_ID PRODUCT_NAME
---------- ----------------
1 BMW
2 Audi
SQL> select * From order_items;
PRODUCT_ID CUSTOM QUANTITY UNIT_PRICE
---------- ------ ---------- ----------
1 Little 100 1
1 Little 200 2
2 Foot 300 3
If we check some totals:
SQL> select o.product_id,
2 o.customer_id,
3 sum(o.quantity * o.unit_price) total
4 from order_items o
5 group by o.product_id, o.customer_id;
PRODUCT_ID CUSTOM TOTAL
---------- ------ ----------
2 Little 400
1 Little 100
2 Foot 900
SQL>
It says that
for customer Little, product 2 was sold with total = 400 - that's our choice for Little
for customer Little, product 1 was sold with total = 100
for customer Foot, product 2 was sold with total = 900 - that's our choice for Foot
Query might then look like this:
temp CTE calculates totals per each customer
rank_them CTE ranks them in descending order per each customer; row_number so that you get only one product, even if there are ties
finally, select the one that ranks as the highest
SQL> with
2 temp as
3 (select o.product_id,
4 o.customer_id,
5 sum(o.quantity * o.unit_price) total
6 from order_items o
7 group by o.product_id, o.customer_id
8 ),
9 rank_them as
10 (select t.customer_id,
11 t.product_id,
12 row_number() over (partition by t.customer_id order by t.total desc) rn
13 from temp t
14 )
15 select * From rank_them;
CUSTOM PRODUCT_ID RN
------ ---------- ----------
Foot 2 1 --> for Foot, product 2 ranks as the highest
Little 2 1 --> for Little, product 1 ranks as the highest
Little 1 2
SQL>
Moved to a function:
SQL> create or replace function f_product (par_customer_id in order_items.customer_id%type)
2 return products.product_name%type
3 is
4 retval products.product_name%type;
5 begin
6 with
7 temp as
8 (select o.product_id,
9 o.customer_id,
10 sum(o.quantity * o.unit_price) total
11 from order_items o
12 group by o.product_id, o.customer_id
13 ),
14 rank_them as
15 (select t.customer_id,
16 t.product_id,
17 row_number() over (partition by t.customer_id order by t.total desc) rn
18 from temp t
19 )
20 select p.product_name
21 into retval
22 from rank_them r join products p on p.product_id = r.product_id
23 where r.customer_id = par_customer_id
24 and r.rn = 1;
25
26 return retval;
27 end;
28 /
Function created.
SQL>
Testing:
SQL> select f_product ('Little') result from dual;
RESULT
--------------------------------------------------------------------------------
Audi
SQL> select f_product ('Foot') result from dual;
RESULT
--------------------------------------------------------------------------------
Audi
SQL>
Now, you can improve it so that you'd care about no data found issue (when customer didn't buy anything), ties (but you'd then return a collection or a refcursor instead of a scalar value) etc.
[EDIT] I'm sorry, ORDERS table has to be included into the temp CTE; your data model is correct, you don't have to do anything about it - my query was wrong (small screen + late hours issue; not a real excuse, just saying).
So:
with
temp as
(select i.product_id,
o.customer_id,
sum(i.quantity * i.unit_price) total
from order_items i join orders o on o.order_id = i.order_id
group by i.product_id, o.customer_id
),
The rest of my code is - otherwise - unmodified.

I want to delete duplication with condition from a table in PLSQL

I want to delete the dup lines using PLSQL. The sample of the table is below
Policy #
Price
Dealno for Loan #
Price of Loan
PersonID
123
10
Loan123
1,000
abc
123
10
Loan123
3,000
abc
456
10
Loan456
500
xyz
456
10
Loan456
500
null
As you can see, in the case of Policy #123, I try to get the line with the highest amount of Price of Loan. Which mean the Price of Loan for 3,000.
For Policy #456, I want to get the one without null value.
Is there a way for me to achieve that in PLSQL.
Thank you
This query identifies if a row is OK (rn = 1) or if is is a duplicated copy (rn > 1) based on your definition
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
;
POLICY# PRICE LOAN# PRICE_LOAN PER RN
---------- ---------- -------- ---------- --- ----------
123 10 loan123 3000 abc 1
123 10 loan123 1000 abc 2
456 10 loan4563 500 xyz 1
456 10 loan4563 500 2
Note that you use row_number where you partition by on the unique key and order by so that you get first the row that should be taken.
So to get the duplicates only you use this query
with rn as (
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
)
select * from rn where rn > 1;
POLICY# PRICE LOAN# PRICE_LOAN PER RN
---------- ---------- -------- ---------- --- ----------
123 10 loan123 1000 abc 2
456 10 loan4563 500 2
Based on this you write the DELETE statement (enclose in BEGIN ... END if you insist in PL/SQL)
delete from tab where rowid in
(
with rn as (
select POLICY#, PRICE, LOAN#, PRICE_LOAN, PERSON_ID,
row_number() over (partition by POLICY# order by PRICE_LOAN desc, PERSON_ID nulls last) as rn
from tab
)
select rowid from rn where rn > 1
);
You may check if the delete worked fine ....
select * from tab;
POLICY# PRICE LOAN# PRICE_LOAN PER
---------- ---------- -------- ---------- ---
123 10 loan123 3000 abc
456 10 loan4563 500 xyz
... and commit

Inactivate duplicate record and re-point child records to active one

There are two table as below
Table1
ID Name Age Active PID
-----------------------------
1 A 2 Y 100
2 A 2 Y 100
3 A 2 Y 100
4 B 3 Y 200
5 B 3 Y 200
Table2
T2ID CID
---------
10 1
20 1
30 1
40 2
50 2
60 3
70 3
80 3
90 4
100 5
110 5
I am trying to inactivate the duplicate record of table 1 and reassign the table2 record to activated rows of table 1,The result for table1 and table2 should be as below
ID Name Age Active PID
-----------------------------
1 A 2 Y 100
2 A 2 N 100
3 A 2 N 100
4 B 3 N 200
5 B 3 Y 200
T2ID CID
---------
10 1
20 1
30 1
40 1
50 1
60 1
70 1
80 1
90 5
100 5
110 5
please help for oracle query to update
You can do this by using two merge statements, like so:
Update table2:
MERGE INTO table2 tgt
USING (WITH t1 AS (SELECT ID,
NAME,
age,
active,
pid,
MIN(ID) OVER (PARTITION BY pid) min_id,
CASE WHEN COUNT(CASE WHEN active = 'Y' THEN 1 END) OVER (PARTITION BY pid) > 1 THEN 'Y' ELSE 'N' END multi_active_rows
FROM table1)
SELECT t2.t2id,
t2.cid old_cid,
t1.min_id new_cid
FROM t1
INNER JOIN table2 t2 ON t1.id = t2.cid
WHERE t1.multi_active_rows = 'Y') src
ON (tgt.t2id = src.t2id)
WHEN MATCHED THEN
UPDATE SET tgt.cid = src.new_cid;
Update table1:
MERGE INTO table1 tgt
USING (WITH t1 AS (SELECT ID,
NAME,
age,
active,
pid,
MIN(ID) OVER (PARTITION BY pid) min_id,
CASE WHEN COUNT(CASE WHEN active = 'Y' THEN 1 END) OVER (PARTITION BY pid) > 1 THEN 'Y' ELSE 'N' END multi_active_rows
FROM table1)
SELECT ID
FROM t1
WHERE multi_active_rows = 'Y'
AND ID != min_id) src
ON (tgt.id = src.id)
WHEN MATCHED THEN
UPDATE SET active = 'N';
Since we want to derive the results to update both table1 and table2 from the original dataset in table1, it's easier to update table2 first before updating table1.
This works by finding the lowest id across each set of pids in table1, plus checking to see if there is more than one active row for each pid (there's no need to do any updates if we have at most one active row available).
Once we have that information, we can use that to decide which rows to update in each table, and we can use the min_id to update table2 with, and we can update any rows in table1 where the id doesn't match the min_id to be not active.
N.B. If you could have a mix of Ys and Ns in your data, you may need to skip the and id != min_id check in the second merge statement and amend the update part to update the row to Y if the id is the min_id, otherwise set it to N.

OBIEE Sequence Data by Dimensions

First question here, so please be gentle. I've been a lurker for a long time and figured now's a great time to get involved.
Using Oracle OBIEE 12c, I'm looking to create a running counter in my result dataset for instances of a group of dimensions, which reset if the group starts again. Like this - in the below example the counter would consider Cust ID and Status:
Cust ID, Order ID, Status, Counter
111, 123456, APPROVED, 1
111, 123457, APPROVED, 2
111, 123458, APPROVED, 3
111, 123459, DECLINED, 1
111, 123460, APPROVED, 1
111, 123461, APPROVED, 2
222, 123462, APPROVED, 1
222, 123463, APPROVED, 2
Any ideas? I've tried a combination of case statements and RSUM(), but I can't quite get to what I'm after and I'm looking for a fresh approach.
Thanks
I'm not sure how you want your data ordered, so that will matter. It looks like you are ordering by ORDER ID.
You can use a count with Partitioning. Though this may not work depending on how your data is ordered.
SELECT CUSTID, ORDERID, STATUS, count(*)
over (PARTITION BY CUSTID||STATUS ORDER BY ORDERID ROWS UNBOUNDED PRECEDING) COUNTER
FROM MYTEST;
You can also use LAG to check for row changes, for example:
SELECT CUSTID, ORDERID, STATUS, curr_row, prev_row
,CASE WHEN curr_row != prev_row THEN 'Change' ELSE 'Same Group' END as TRACKER
FROM (
SELECT CUSTID, ORDERID, STATUS, CUSTID||STATUS AS curr_row
,LAG(CUSTID||STATUS) OVER (ORDER BY CUSTID, ORDERID, STATUS) AS prev_row
FROM MYTEST
ORDER BY ORDERID
)
;
The above examples are using this set of test data:
Test Data
You should probably calculate the counter outside of OBIEE as part of the extract/load/transform process.
Here is an approximation using the ROW_NUMBER analytical function in Oracle.
with dataset as (
SELECT 111 AS cust_id,
123456 AS order_id,
'APPROVED' AS status,
1 AS expected_counter
FROM dual
UNION ALL
SELECT 111,123457,'APPROVED',2 FROM dual
UNION ALL
SELECT 111,123458,'APPROVED',3 FROM dual
UNION ALL
SELECT 111,123459,'DECLINED',1 FROM dual
UNION ALL
SELECT 111,123460,'APPROVED',1 FROM dual
UNION ALL
SELECT 111,123461,'APPROVED',2 FROM dual
UNION ALL
SELECT 222,1234562,'APPROVED',1 FROM dual
UNION ALL
SELECT 222,1234563,'APPROVED',2 FROM dual
)
select cust_id,order_id,status,expected_counter,row_number() over (partition by cust_id,status order by order_id asc) as counter from dataset ;
The result is:
cust_id /order_id /status / your_counter / my_counter
111 123456 APPROVED 1 1
111 123457 APPROVED 2 2
111 123458 APPROVED 3 3
111 123460 APPROVED 1 4
111 123461 APPROVED 2 5
111 123459 DECLINED 1 1
222 1234562 APPROVED 1 1
222 1234563 APPROVED 2 2
This example helps you find out that customer 111 has 5 approved orders and 1 that was declined; customer 222 has 2 approved orders. I could not match your exact counter. Hope this helps
In OBIEE analysis column formula syntax that'd be sth like
RCOUNT("Orders"."Status" by "Customers"."Cust ID")

oracle procedure that update values based on amount

I have the following table (SEMINARS):
ID AMOUNT
6936120606 320.51
6978332460 261.05
6940456367 0
6973328053 438.68
6951690568 533.56
sum(1690.47)
I have the value 980 that I need to credit one by one the seminars amount
start from biggest amount to lowest.
for example the 980 value must will credit as following:
6951690568 533,56 (-533.56)=0 446.44 (980.00-533.56=446.44)
6973328053 438,68 (-438.68)=0 7.76 (446.44-438.68= 7.76)
6936120606 320,51 (- 7.76)=312.75 0.00 (0)
any idea?
In Oracle 11g you can use recursive query, this gave me desired output:
with
s as (select row_number() over (order by amount desc) rn, id, amount from seminars),
t(rn, id, amount, left1, left2) as (
select rn, id, amount, greatest(amount-980,0), 980-amount from s where rn = 1
union all
select s.rn, s.id, s.amount,
s.amount-least(left2, s.amount), greatest(left2-s.amount, 0)
from s join t on s.rn = t.rn+1 and left2>0)
select * from t
Output and SQLFiddle:
RN ID AMOUNT LEFT1 LEFT2
---------- --------------- ---------- ---------- ----------
1 6951690568 533.56 0 446.44
2 6973328053 438.68 0 7.76
3 6936120606 320.51 312.75 0

Resources