Oracle PL/SQL SUM OVER( ) starting from certain row - sql

I have a table who looks like this:
Pam_A Week Value_1
A 1 10
A 2 13
B 3 15
B 4 10
B 5 11
B 6 10
I want to achieve the following:
Pam_A Week Value_1 Value_2
A 1 10
A 2 13
B 3 15 28
B 4 10 38
B 5 11 49
B 6 10 59
When Pam_A=B, sum the current Value_1 and its preceding row value and keep that value increasing accordding the next value in Value_1
Any ideas for achieve this cumulative sum?

First of all you need to mark all rows that you want to count. You can do it like this:
with t(Pam_A, Week, Value_1) as (
select 'A', 1, 10 from dual union all
select 'A', 2, 13 from dual union all
select 'B', 3, 15 from dual union all
select 'B', 4, 10 from dual union all
select 'B', 5, 11 from dual union all
select 'B', 6, 10 from dual
)
select
Pam_A, Week, Value_1
,case
when Pam_A='B' or lead(Pam_A)over(order by week) = 'B'
then 'Y'
else 'N'
end as flag
from t;
Results:
PAM_A WEEK VALUE_1 FLAG
----- ---------- ---------- ----
A 1 10 N
A 2 13 Y
B 3 15 Y
B 4 10 Y
B 5 11 Y
B 6 10 Y
6 rows selected.
Then you can aggregate only rows that have flag='Y':
with t(Pam_A, Week, Value_1) as (
select 'A', 1, 10 from dual union all
select 'A', 2, 13 from dual union all
select 'B', 3, 15 from dual union all
select 'B', 4, 10 from dual union all
select 'B', 5, 11 from dual union all
select 'B', 6, 10 from dual
)
select
v.*
,case
when flag='Y' and Pam_a='B'
then sum(Value_1)over(partition by flag order by Week)
end as sums
from (
select
Pam_A, Week, Value_1
,case
when Pam_A='B' or lead(Pam_A)over(order by week) = 'B'
then 'Y'
else 'N'
end as flag
from t
) v;
Results:
PAM_A WEEK VALUE_1 FLAG SUMS
----- ---------- ---------- ---- ----------
A 1 10 N
A 2 13 Y
B 3 15 Y 28
B 4 10 Y 38
B 5 11 Y 49
B 6 10 Y 59
6 rows selected.

Using a combination of LEAD and SUM analytic functions, you can determine which rows have the next PAM_A as a B, then only SUM if the next row is a B or the current row is a B.
Query
WITH
d (pam_a, week, value_1)
AS
(SELECT 'A', 1, 10 FROM DUAL
UNION ALL
SELECT 'A', 2, 13 FROM DUAL
UNION ALL
SELECT 'B', 3, 15 FROM DUAL
UNION ALL
SELECT 'B', 4, 10 FROM DUAL
UNION ALL
SELECT 'B', 5, 11 FROM DUAL
UNION ALL
SELECT 'B', 6, 10 FROM DUAL)
SELECT pam_a,
week,
value_1,
CASE
WHEN pam_a = 'B'
THEN
SUM (CASE WHEN next_pam_a = 'B' OR pam_a = 'B' THEN value_1 ELSE 0 END)
OVER (ORDER BY week)
ELSE
NULL
END value_2
FROM (SELECT pam_a, week, value_1, LEAD (pam_a) OVER (ORDER BY week) AS next_pam_a FROM d);
Result
PAM_A WEEK VALUE_1 VALUE_2
________ _______ __________ __________
A 1 10
A 2 13
B 3 15 28
B 4 10 38
B 5 11 49
B 6 10 59

If I understand, you want a cumulative sum but with conditionality:
select t.*,
(case when pam_A = 'B' then sum(value_1) over (order by week) end) as value_2
from t;

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

Check Distinct value Present in the group

I have a table with multiple pos and I need to find the Purchase Id where it has only wallet per group and nothing else in the group.
For eg,here PID - 4 and 5 has only wallet , rest has other's as well. So wallet_flag should be 1 in the output.
I tried to use window's function but could not achieve the result. Can you please suggest.
select PID
,POS
, SUM(CASE WHEN POS='bwallet' THEN 1 ELSE 0 END ) OVER(PARTITION BY PID) as FLAG
from PAYMENTS
where "status" = 'SUCCESS'
OUTPUT:
Here's one option:
Sample data:
SQL> with test (pid, pos, amount) as
2 (select 1, 'wallet', 10 from dual union all
3 select 1, 'BT' , 10 from dual union all
4 select 1, 'Cash' , 10 from dual union all
5 select 2, 'BT' , 50 from dual union all
6 select 3, 'Cash' , 24 from dual union all
7 select 3, 'BT' , 12 from dual union all
8 select 4, 'wallet', 100 from dual union all
9 select 5, 'wallet', 20 from dual union all
10 select 5, 'wallet', 100 from dual
11 ),
Query begins here; cnt will be 0 if there's only "wallet" per PID:
12 temp as
13 (select pid,
14 sum(case when pos = 'wallet' then 0 else 1 end) cnt
15 from test
16 group by pid
17 )
18 select a.pid, a.pos, a.amount,
19 case when b.cnt = 0 then 1 else 0 end wallet_flag
20 from test a join temp b on a.pid = b.pid
21 order by a.pid;
PID POS AMOUNT WALLET_FLAG
---------- ------ ---------- -----------
1 wallet 10 0
1 BT 10 0
1 Cash 10 0
2 BT 50 0
3 Cash 24 0
3 BT 12 0
4 wallet 100 1
5 wallet 20 1
5 wallet 100 1
9 rows selected.
SQL>
SELECT
your_table.*,
MIN(
CASE pos
WHEN 'wallet' THEN 1
ELSE 0
END
)
OVER (
PARTITION BY pid
)
AS wallet_flag
from
your_table
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=e05a7863b9f4d912dcdf5ced5ec1c1b2

SQL logic to fail a check if any of the related customers has failed

I have the requirement to flag the customers Y only when all the related customers have also passed the check.
below are the two tables:
relationship table :
customer_id related_customer
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
11 11
11 22
22 11
22 22
Check table
customer_id check_flag
1 y
2 y
3 n
11 y
22 y
I want output like below:
customer_id paas_fail_flag
1 n
2 n
3 n
11 y
22 y
output justification: since 1,2,3 are related customers and since one of them (3) has n in table 2 , so all the related customers should also have n.
11,22 are related customers and both have y in table 2.so in output both should have y.
You need to join relationship to check and use conditional aggregation:
SELECT r.customer_id,
COALESCE(MAX(CASE WHEN c.check_flag = 'n' THEN c.check_flag END), 'y') paas_fail_flag
FROM relationship r INNER JOIN "check" c
ON c.customer_id = r.related_customer
GROUP BY r.customer_id
ORDER BY r.customer_id
See the demo.
Something like this? Sample data in lines #1 - 40; query begins at line #41:
SQL> WITH
2 -- sample data
3 rel (customer_id, related_customer)
4 AS
5 (SELECT 1, 1 FROM DUAL
6 UNION ALL
7 SELECT 1, 2 FROM DUAL
8 UNION ALL
9 SELECT 1, 3 FROM DUAL
10 UNION ALL
11 SELECT 2, 1 FROM DUAL
12 UNION ALL
13 SELECT 2, 2 FROM DUAL
14 UNION ALL
15 SELECT 2, 3 FROM DUAL
16 UNION ALL
17 SELECT 3, 1 FROM DUAL
18 UNION ALL
19 SELECT 3, 2 FROM DUAL
20 UNION ALL
21 SELECT 3, 3 FROM DUAL
22 UNION ALL
23 SELECT 11, 11 FROM DUAL
24 UNION ALL
25 SELECT 11, 22 FROM DUAL
26 UNION ALL
27 SELECT 22, 11 FROM DUAL
28 UNION ALL
29 SELECT 22, 22 FROM DUAL),
30 chk (customer_id, check_flag)
31 AS
32 (SELECT 1, 'y' FROM DUAL
33 UNION ALL
34 SELECT 2, 'y' FROM DUAL
35 UNION ALL
36 SELECT 3, 'n' FROM DUAL
37 UNION ALL
38 SELECT 11, 'y' FROM DUAL
39 UNION ALL
40 SELECT 22, 'y' FROM DUAL),
41 temp
42 AS
43 -- minimum CHECK_FLAG per customer and related customer
44 ( SELECT r.customer_id, r.related_customer, MIN (c.check_flag) mcf
45 FROM rel r JOIN chk c ON c.customer_id = r.related_customer
46 GROUP BY r.customer_id, r.related_customer)
47 SELECT customer_id, MIN (mcf) flag
48 FROM temp
49 GROUP BY customer_id
50 ORDER BY customer_id;
CUSTOMER_ID FLAG
----------- ----
1 n
2 n
3 n
11 y
22 y
SQL>
Assuming that your relationship data could be sparse, for example:
CREATE TABLE relationship ( customer_id, related_customer ) AS
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 11, 22 FROM DUAL;
CREATE TABLE "CHECK" ( customer_id, check_flag ) AS
SELECT 1, 'y' FROM DUAL UNION ALL
SELECT 2, 'y' FROM DUAL UNION ALL
SELECT 3, 'n' FROM DUAL UNION ALL
SELECT 11, 'y' FROM DUAL UNION ALL
SELECT 22, 'y' FROM DUAL;
(Note: The below query will also work on your dense data, where every relationship combination is enumerated.)
Then you can use a hierarchical query:
SELECT customer_id,
MIN(check_flag) AS check_flag
FROM (
SELECT CONNECT_BY_ROOT(c.customer_id) AS customer_id,
c.check_flag AS check_flag
FROM "CHECK" c
LEFT OUTER JOIN relationship r
ON (r.customer_id = c.customer_id)
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY NOCYCLE
( PRIOR r.related_customer = c.customer_id
OR PRIOR c.customer_id = r.related_customer )
AND PRIOR c.check_flag = 'y'
)
GROUP BY
customer_id
ORDER BY
customer_id
Which outputs:
CUSTOMER_ID
CHECK_FLAG
1
n
2
n
3
n
11
y
22
y
db<>fiddle here

create sequence of numbers on grouped column in Oracle

Consider below table with column a,b,c.
a b c
3 4 5
3 4 5
6 4 1
1 1 8
1 1 8
1 1 0
1 1 0
I need a select statement to get below output. i.e. increment column 'rn' based on group of column a,b,c.
a b c rn
3 4 5 1
3 4 5 1
6 4 1 2
1 1 8 3
1 1 8 3
1 1 0 4
1 1 0 4
You can use the DENSE_RANK analytic function to get a unique ID for each combination of A, B, and C. Just note that if a new value is inserted into the table, the IDs of each combination of A, B, and C will shift and may not be the same.
Query
WITH
my_table (a, b, c)
AS
(SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 6, 4, 1 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL)
SELECT t.*, DENSE_RANK () OVER (ORDER BY b desc, c desc, a) as rn
FROM my_table t;
Result
A B C RN
____ ____ ____ _____
3 4 5 1
3 4 5 1
6 4 1 2
1 1 8 3
1 1 8 3
1 1 0 4
1 1 0 4
As a starter: for your answer to make sense at all, you need a column that defines the ordering of the rows. Let me assume that you have such column, called id.
Then, you can use window functions:
select a, b, c,
sum(case when a = lag_a and b = lag_b and c = lag_c then 0 else 1 end) over(order by id) rn
from (
select t.*,
lag(a) over(order by id) lag_a,
lag(b) over(order by id) lag_b,
lag(c) over(order by id) lag_c
from mytable t
) t
Assuming you have some way of ordering your rows, then you can use MATCH_RECOGNIZE:
SELECT a, b, c, rn
FROM table_name
MATCH_RECOGNIZE (
ORDER BY id
MEASURES MATCH_NUMBER() AS rn
ALL ROWS PER MATCH
PATTERN ( FIRST_ROW EQUAL_ROWS* )
DEFINE EQUAL_ROWS AS (
EQUAL_ROWS.a = PREV( EQUAL_ROWS.a )
AND EQUAL_ROWS.b = PREV( EQUAL_ROWS.b )
AND EQUAL_ROWS.c = PREV( EQUAL_ROWS.c )
)
)
So, for your test data:
CREATE TABLE table_name ( id, a, b, c ) AS
SELECT 1, 3, 4, 5 FROM DUAL UNION ALL
SELECT 2, 3, 4, 5 FROM DUAL UNION ALL
SELECT 3, 6, 4, 1 FROM DUAL UNION ALL
SELECT 4, 1, 1, 8 FROM DUAL UNION ALL
SELECT 5, 1, 1, 8 FROM DUAL UNION ALL
SELECT 6, 1, 1, 0 FROM DUAL UNION ALL
SELECT 7, 1, 1, 0 FROM DUAL;
Outputs:
A | B | C | RN
-: | -: | -: | -:
3 | 4 | 5 | 1
3 | 4 | 5 | 1
6 | 4 | 1 | 2
1 | 1 | 8 | 3
1 | 1 | 8 | 3
1 | 1 | 0 | 4
1 | 1 | 0 | 4
db<>fiddle here
It can also be done without any ordering, by getting the distinct groups and numbering each group. Borrowing the first part from EJ Egjed:
WITH my_table (a, b, c) AS
(SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 4, 5 FROM DUAL
UNION ALL
SELECT 6, 4, 1 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 8 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL
UNION ALL
SELECT 1, 1, 0 FROM DUAL)
, groups as (select distinct a, b, c
from my_table)
, groupnums as (select rownum as num, a, b, c
from groups)
select a, b, c, num
from my_table join groupnums using(a,b,c);

Joining on a match with at least one argument

I have a two tables:
T1(RES1, RES2)
T2(RES, WORD, COUNT)
I need to generate T3:
T3(RES1, RES2, WORD, COUNT)
As in the following example
T1
RES1 RES2
------------
A B
C D
T2
RES WORD COUNT
----------------------
A W1 10
B W1 5
B W2 7
C W2 8
T3
RES1 RES2 WORD COUNT
-----------------------------
A B W1 15 = (10+5)
A B W2 7 = (NOTHING FOR A+7)
C D W2 8 = (8+NOTHING FOR D)
That is, for every pair in T1, generate the sum distinct word counts that occur with it. What would be the most efficient way to do this in SQL?
-- sample of data from your question
SQL> with t1(RES1, RES2) as(
2 select 'A', 'B' from dual union all
3 select 'C', 'D' from dual
4 ),
5 t2(RES , WORD, COUNT1) as(
6 select 'A', 'W1', 10 from dual union all
7 select 'B', 'W1', 5 from dual union all
8 select 'B', 'W2', 7 from dual union all
9 select 'C', 'W2', 8 from dual
10 ) -- the query
11 select t1.res1
12 , t1.res2
13 , t2.word
14 , sum(t2.count1) as count
15 from t1
16 join t2
17 on (t1.res1 = t2.res or
18 t1.res2 = t2.res)
19 group by t1.res1
20 , t1.res2
21 , t2.word
22 order by t1.res1
23 ;
RES1 RES2 WORD COUNT
---- ---- ---- ----------
A B W1 15
A B W2 7
C D W2 8