Reporting MIN(VAL) for Each Partitioned Value in Another Column - sql

Oracle 11g
How can I return single row of min(email_sent) for each email_type per ID?
That is, I'd like to get the first invitation date and the first confirmation date into a single row.
with mailings as (
select 1 as recipient_id, 'INVITE' as email_type, to_date('JAN-01-2020','MON-DD-YYYY') as email_sent from dual union all
select 1 as recipient_id, 'INVITE' as email_type, to_date('JAN-02-2020','MON-DD-YYYY') as email_sent from dual union all
select 1 as recipient_id, 'INVITE' as email_type, to_date('JAN-03-2020','MON-DD-YYYY') as email_sent from dual union all
select 1 as recipient_id, 'CONFIRM'as email_type, to_date('JAN-10-2020','MON-DD-YYYY') as email_sent from dual union all
select 1 as recipient_id, 'CONFIRM'as email_type, to_date('JAN-11-2020','MON-DD-YYYY') as email_sent from dual
)
select *
from (
select recipient_id,
email_type,
min(email_sent) over (partition by recipient_id, email_type) as first_invite,
min(email_sent) over (partition by recipient_id, email_type) as first_confirmation,
rank() over (partition by recipient_id, email_type order by email_sent) as email_type
from mailings
)
where email_type=1;
Desired Result: Report the dates for the first invitation and the first confirmation.
Recipient_ID FIRST_INVITE FIRST_CONFIRMATION
1 JAN-01-2020 JAN-10-2020

Here you go, as per your wording, not your result (as you first sent, but you show last sent):
with mailings as (
select 1 as recipient_id, 'INVITE' as email_type, to_date('JAN-01-2020','MON-DD-YYYY') as email_sent from dual union all
select 1 , 'INVITE' , to_date('JAN-02-2020','MON-DD-YYYY') from dual union all
select 1 , 'INVITE' , to_date('JAN-03-2020','MON-DD-YYYY') from dual union all
select 1 , 'CONFIRM', to_date('JAN-10-2020','MON-DD-YYYY') from dual union all
select 1 , 'CONFIRM', to_date('JAN-11-2020','MON-DD-YYYY') from dual
)
select recipient_id,
min(case when email_type='INVITE' then email_sent else null end) sent,
min(case when email_type='CONFIRM' then email_sent else null end) confirmed
from mailings
group by recipient_id;
RECIPIENT_ID SENT CONFIRMED
1 01-JAN-20 10-JAN-20

Related

SQL: Analytical Function SUM with Distinct

Here We have a duplicate ID, Need to get distinct of ID and SUM, So it should be 13 as Amount
I know it is possible to do this by first getting the distinct query and then on top of the query getting the SUM.
Is there a way to achieve this in one single query
with data as
(
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 2 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 3 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 4 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
)
select * , sum(amount) over (partition by name ,status) from data
There might be a better way to do, but here's my take:
with data as
(
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 2 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 3 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 4 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
)
select sum(Amount) from (select distinct * from data) a
Here is my take,
with data as
(
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 2 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 3 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 4 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
)
select sum(Amount) from data d where not exists(select 'x' from data d2 where d2.amount = d.amount and d2.id > d.id)
I'd leverage Snowflake's QUALIFY function to do something along these lines:
with data as
(
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 1 as ID ,'ABC' as Name, 'Paid' as Status, 10 as Amount
union all
select 2 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 3 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
union all
select 4 as ID ,'ABC' as Name, 'Paid' as Status, 1 as Amount
),
filter as (
select *
from data
qualify row_number() over (partition by id order by name) = 1
)
select * , sum(amount) over (partition by name ,status)
from filter;

How to get first row of each status?

I want to get the first row of each status for each id.
There can be multiple rows for each status. So I want to get the first occurrence of each status based on the previous status.
e.g. info_required first occurs at row 2, then it changes to another status pending at row 4, and then info_required again at row 6.
Likewise, status pending first at row 4, then at row 8 since the status changed after row4, it needs to be in the resultset.
Hence below I want to get the row number 1, 2, 4, 6, and 8.
WITH t1 AS (
SELECT 1 AS row, 'A' AS id, 'created' AS status, '2021-05-18 18:30:00'::timestamp AS created_at UNION ALL
SELECT 2 AS row, 'A' AS id, 'info_required' AS status, '2021-05-19 11:30:00'::timestamp AS created_at UNION ALL
SELECT 3 AS row, 'A' AS id, 'info_required' AS status, '2021-05-19 12:00:00'::timestamp AS created_at UNION ALL
SELECT 4 AS row, 'A' AS id, 'pending' AS status, '2021-05-19 12:30:00'::timestamp AS created_at UNION ALL
SELECT 5 AS row, 'A' AS id, 'pending' AS status, '2021-05-20 13:30:00'::timestamp AS created_at UNION ALL
SELECT 6 AS row, 'A' AS id, 'info_required' AS status, '2021-05-20 14:30:00'::timestamp AS created_at UNION ALL
SELECT 7 AS row, 'A' AS id, 'info_required' AS status, '2021-05-20 15:30:00'::timestamp AS created_at UNION ALL
SELECT 8 AS row, 'A' AS id, 'pending' AS status, '2021-05-20 16:30:00'::timestamp AS created_at
)
SELECT *
FROM t1
Using CONDITIONAL_CHANGE_EVENT
WITH cte AS (
SELECT *, CONDITIONAL_CHANGE_EVENT(status) over (partition by id
order by created_at) AS cce
FROM t1
)
SELECT *
FROM cte
QUALIFY ROW_NUMBER() OVER(PARTITION BY id, cce ORDER BY created_at) = 1;
Data preparation:
CREATE TABLE t1 AS
WITH t1 AS (
SELECT 1 AS row_, 'A' AS id, 'created' AS status, '2021-05-18 18:30:00'::timestamp AS created_at UNION ALL
SELECT 2 AS row_, 'A' AS id, 'info_required' AS status, '2021-05-19 11:30:00'::timestamp AS created_at UNION ALL
SELECT 3 AS row_, 'A' AS id, 'info_required' AS status, '2021-05-19 12:00:00'::timestamp AS created_at UNION ALL
SELECT 4 AS row_, 'A' AS id, 'pending' AS status, '2021-05-19 12:30:00'::timestamp AS created_at UNION ALL
SELECT 5 AS row_, 'A' AS id, 'pending' AS status, '2021-05-20 13:30:00'::timestamp AS created_at UNION ALL
SELECT 6 AS row_, 'A' AS id, 'info_required' AS status, '2021-05-20 14:30:00'::timestamp AS created_at UNION ALL
SELECT 7 AS row_, 'A' AS id, 'info_required' AS status, '2021-05-20 15:30:00'::timestamp AS created_at UNION ALL
SELECT 8 AS row_, 'A' AS id, 'pending' AS status, '2021-05-20 16:30:00'::timestamp AS created_at
)
SELECT *
FROM t1;
Cte part:
SELECT *, CONDITIONAL_CHANGE_EVENT(status) over (partition by id
order by created_at) AS cce
FROM t1;
You can use lag() and qualify():
select t.*
from t
qualify lag(status) over (partition by id order by created_at) is distinct from status;

How to get rows with last update on column

Goal:
For each "IDCONT", i need to get the "DAY_ID" where i have the last change/update on "STATE_ID".
Example:
with reftable as (
select 1 as PROCESSID, 'A' as IDCONT, 'X' as STATEID, '10' AS DAY_ID union all
select 2 as PROCESSID, 'A' as IDCONT, 'X' as STATEID, '11' AS DAY_ID union all
select 3 as PROCESSID, 'A' as IDCONT, 'Y' as STATEID, '12' AS DAY_ID union all
select 4 as PROCESSID, 'A' as IDCONT, 'Y' as STATEID, '13' AS DAY_ID union all
select 1 as PROCESSID, 'B' as IDCONT, 'N' as STATEID, '14' AS DAY_ID union all
select 2 as PROCESSID, 'B' as IDCONT, 'N' as STATEID, '15' AS DAY_ID union all
select 3 as PROCESSID, 'B' as IDCONT, 'M' as STATEID, '16' AS DAY_ID union all
select 1 as PROCESSID, 'C' as IDCONT, 'X' as STATEID, '11' AS DAY_ID union all
select 2 as PROCESSID, 'C' as IDCONT, 'X' as STATEID, '18' AS DAY_ID union all
) ...
Expected result:
PROCESSID IDCONT STATID DAYID
3 A Y 12
2 B N 15
1 C X 11
I solved the problem with this:
...
SELECT IDCONT, STATEID, MIN(DAY_ID)
FROM REFTABLE
WHERE (IDCONT, STATEID) IN (
SELECT IDCONT, FIRST_VALUE(STATEID) OVER PARTITION BY IDCONT ORDER BY PROCESSID DESC) AS STATEID
FROM REFTABLE
)
But i want to do the same without the need to call the table a 2nd time.
Thx!
Here is one method:
select r.*
from (select r.*,
lag(stateid) over (partition by idcont order by day_id) as prev_stateid,
first_value(stateid) over (partition by idcont order by day_id desc) as last_stateid
from reftable r
) r
where stateid = last_stateid and (prev_stateid is null or prev_stateid <> stateid);
However, this does not handle the case where the state changes back to a previous state. That logic can be added in, if necessary.
It would be simpler if you didn't need to return IDCONT whose STATEID didn't change (that would be a C). One REFTABLE trip might look like this; see if it does any good in reality.
SQL> with reftable as (
2 select 1 as PROCESSID, 'A' as IDCONT, 'X' as STATEID, '10' AS DAY_ID from dual union all
3 select 2 as PROCESSID, 'A' as IDCONT, 'X' as STATEID, '11' AS DAY_ID from dual union all
4 select 3 as PROCESSID, 'A' as IDCONT, 'Y' as STATEID, '12' AS DAY_ID from dual union all
5 select 4 as PROCESSID, 'A' as IDCONT, 'Y' as STATEID, '13' AS DAY_ID from dual union all
6 --
7 select 1 as PROCESSID, 'B' as IDCONT, 'N' as STATEID, '14' AS DAY_ID from dual union all
8 select 2 as PROCESSID, 'B' as IDCONT, 'N' as STATEID, '15' AS DAY_ID from dual union all
9 select 3 as PROCESSID, 'B' as IDCONT, 'M' as STATEID, '16' AS DAY_ID from dual union all
10 --
11 select 1 as PROCESSID, 'C' as IDCONT, 'X' as STATEID, '11' AS DAY_ID from dual union all
12 select 2 as PROCESSID, 'C' as IDCONT, 'X' as STATEID, '18' AS DAY_ID from dual
13 ),
14 inter as
15 (select processid, idcont, stateid, day_id,
16 case when nvl(lag(stateid) over
17 (partition by idcont order by processid ), '?') <> stateid then
18 row_number() over (partition by idcont order by processid )
19 end grp
20 from reftable
21 )
22 select processid, idcont, stateid, day_id
23 from inter i
24 where grp = (select max(i1.grp)
25 from inter i1
26 where i1.idcont = i.idcont)
27 order by idcont, processid;
PROCESSID IDCONT STATEID DAY_ID
---------- ---------- ---------- ----------
3 A Y 12
3 B M 16
1 C X 11
SQL>
Using "FIRST_VALUE" does give you the change, but can you rely on there only being a single change in the table? Won't other changes invalidate this and result in bad data?
I used the LAG function instead but it doesn't return IDCONT C because it has not had a change.
Using a CTE to grab the data and then a query to filter might be faster (since you can't put LAG or FIRST_VALUE in the where clause). It would prevent another trip to the database.
CREATE TABLE REFTABLE
([PROCESSID] int, [IDCONT] varchar(1), [STATEID] varchar(1), [DAY_ID] int)
;
INSERT INTO REFTABLE
([PROCESSID], [IDCONT], [STATEID], [DAY_ID])
VALUES
(1, 'A', 'X', 10),
(2, 'A', 'X', 11),
(3, 'A', 'Y', 12),
(4, 'A', 'Y', 13),
(1, 'B', 'N', 14),
(2, 'B', 'N', 15),
(3, 'B', 'M', 16),
(1, 'C', 'X', 11),
(2, 'C', 'X', 18)
;
with chgfound as (SELECT TOP 100 PERCENT PROCESSID, IDCONT, STATEID, DAY_ID, LAG(STATEID) OVER(PARTITION BY IDCONT ORDER BY IDCONT, PROCESSID) as LastState
from REFTABLE
order by IDCONT, PROCESSID
)
select * from chgfound where STATEID !=LastState
http://www.sqlfiddle.com/#!18/086134
Also just noticed you have the Oracle tag. I did this in SQL Server but it has to be really close to the same.

Last changed Data with T as status in Oracle sql

My Data is given below
In the below sample latest record has T and last occurrence of T was updated on 3-Apr-17 so that row needs to be displayed
EMP EFFDT STATUS
11367 15-Apr-15 A
11367 14-Jun-15 A
11367 10-Aug-15 T
11367 2-Apr-17 A
11367 3-Apr-17 T *
11367 10-Apr-17 T
In the below sample latest record has T and last occurrence of T was updated on 23-Feb-18 so that row needs to be displayed
EMP EFFDT STATUS
20612 4-Sep-16 A
20612 23-Feb-18 T *
20612 20-Jul-18 T
In the below sample latest record has T and that is the only occurrence so display it
EMP EFFDT STATUS
20644 12-Jul-15 A
20644 8-Aug-16 A
20644 6-Oct-16 T*
In the below sample latest record does not has T so no need to display
EMP EFFDT STATUS
21155 18-May-17 T
21155 21-Jun-17 A
21155 13-Mar-18 T
21155 15-Aug-18 A
My Desired Output should be (* marked records)
EMP EFFDT STATUS
11367 3-Apr-17 T
20612 23-Feb-18 T
20644 6-Oct-16 T
This is an island and gap problem.
In the cte you try to found out what island have T as last update (t=0)
SQL DEMO
WITH cte as (
SELECT "EMP",
"EFFDT",
SUM(CASE WHEN "STATUS" <> 'T'
THEN 1
ELSE 0
END) OVER (partition by "EMP" ORDER BY "EFFDT" DESC) as t
FROM Table1
)
SELECT "EMP", MIN("EFFDT") as "EFFDT", MAX('T') as "STATUS"
FROM cte
WHERE t = 0
GROUP BY "EMP"
OUTPUT
| EMP | EFFDT | STATUS |
|-------|-----------------------|--------|
| 11367 | 2017-04-03 00:00:00.0 | T |
| 20612 | 2018-02-23 00:00:00.0 | T |
| 20644 | 2016-10-06 00:00:00.0 | T |
For debug you can try
SELECT *
FROM cte
to see how t values are created
WITH cte1
AS (
SELECT A.*
,lag(STATUS, 1, 0) OVER (
PARTITION BY EMP ORDER BY EFFDT
) AS PRIOR_STATUS
FROM Table1 A
)
SELECT EMP
,STATUS
,MAX(EFFDT) AS EFFDT
FROM cte1 A
WHERE A.STATUS = 'T'
AND A.PRIOR_STATUS <> 'T'
GROUP BY EMP
,STATUS
SQL Fiddle here: http://sqlfiddle.com/#!4/458733/18
alter session set nls_date_format = 'dd-Mon-rr';
Solution (including simulated data in with clause):
with
simulated_data (EMP, EFFDT, STATUS) as (
select 11367, to_date('15-Apr-15'), 'A' from dual union all
select 11367, to_date('14-Jun-15'), 'A' from dual union all
select 11367, to_date('10-Aug-15'), 'T' from dual union all
select 11367, to_date( '2-Apr-17'), 'A' from dual union all
select 11367, to_date( '3-Apr-17'), 'T' from dual union all
select 11367, to_date('10-Apr-17'), 'T' from dual union all
select 20612, to_date( '4-Sep-16'), 'A' from dual union all
select 20612, to_date('23-Feb-18'), 'T' from dual union all
select 20612, to_date('20-Jul-18'), 'T' from dual union all
select 20644, to_date('12-Jul-15'), 'A' from dual union all
select 20644, to_date( '8-Aug-16'), 'A' from dual union all
select 20644, to_date( '6-Oct-16'), 'T' from dual union all
select 21155, to_date('18-May-17'), 'T' from dual union all
select 21155, to_date('21-Jun-17'), 'A' from dual union all
select 21155, to_date('13-Mar-18'), 'T' from dual union all
select 21155, to_date('15-Aug-18'), 'A' from dual
)
-- End of simulated data (for testing only).
-- SQL query (solution) begins BELOW THIS LINE.
select emp, min(effdt) as eff_dt, 'T' as status
from (
select emp, effdt, status,
row_number() over (partition by emp, status
order by effdt desc) as rn,
min(status) keep (dense_rank last order by effdt)
over (partition by emp) as last_status
from simulated_data
)
where last_status = 'T' and status = 'T' and rn <= 2
group by emp
;
Output:
EMP EFF_DT STATUS
---------- --------- ------
11367 03-Apr-17 T
20612 23-Feb-18 T
20644 06-Oct-16 T
Explanation:
In the subquery, we add two columns to the input data. Column RN gives a rank within each partition by EMPNO and STATUS, in descending order by EFFDT. LAST_STATUS used the analytic version of the LAST() function to assign either T or A as the last status for each EMP (and it attaches this value to EVERY row for the EMP, regardless of each row's own STATUS).
In the outer query, we are only interested to retain the EMP where the last status was T. For those rows, we only want to retain the rows where the actual status of the row is in fact T (we know this will always include the last row for that EMP, by the way, and it will have RN = 1). Moreover, we are only interested in those rows where RN is 1 or possibly 2 (if there are at least two rows with status T for that EMP). Of these either one or two rows with status T for a given EMP, we want to get the EARLIEST date. That will be the ONLY date if there is no row with RN = 2 for that partition; otherwise, it will be the date from the earlier row, with RN = 2.
In the outer SELECT we select the EMP, the earliest date, and the status we already know, it is T (so we don't need any work for this - actually it is not clear why the third column is even needed, since it is known beforehand it will be T in all rows).
Assuming that A and T are the only statuses, this should work.
WITH cte1
AS (
SELECT A.EMP, A.EFFDT, A.STATUS
,min(STATUS) OVER (
PARTITION BY EMP ORDER BY EFFDT RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS MIN_STATUS
FROM Table1 A
)
SELECT
cte1.EMP
,MIN(cte1.EFFDT) AS EFFDT
,MIN(cte1.STATUS) as STATUS
FROM cte1
WHERE cte1.MIN_STATUS = 'T'
GROUP BY EMP
EDIT: well, if you have another statues, let's make it more robust. Actually, it's almost the same as juan-carlos-oropeza proposed, but he missed "RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING" part.
Ooops, it IS the same solution: juan-carlos-oropeza used order by DESC istead of unbounded following.
with emp_status_log (EMP, EFFDT, STATUS) as
(
select 11367, to_date('15-Apr-15', 'dd-Mon-yy'), 'A' from dual union all
select 11367, to_date('14-Jun-15', 'dd-Mon-yy'), 'A' from dual union all
select 11367, to_date('10-Aug-15', 'dd-Mon-yy'), 'T' from dual union all
select 11367, to_date( '2-Apr-17', 'dd-Mon-yy'), 'A' from dual union all
select 11367, to_date( '3-Apr-17', 'dd-Mon-yy'), 'T' from dual union all
select 11367, to_date('10-Apr-17', 'dd-Mon-yy'), 'T' from dual union all
select 20612, to_date( '4-Sep-16', 'dd-Mon-yy'), 'A' from dual union all
select 20612, to_date('23-Feb-18', 'dd-Mon-yy'), 'T' from dual union all
select 20612, to_date('20-Jul-18', 'dd-Mon-yy'), 'T' from dual union all
select 20644, to_date('12-Jul-15', 'dd-Mon-yy'), 'A' from dual union all
select 20644, to_date( '8-Aug-16', 'dd-Mon-yy'), 'A' from dual union all
select 20644, to_date( '6-Oct-16', 'dd-Mon-yy'), 'T' from dual union all
select 21155, to_date('18-May-17', 'dd-Mon-yy'), 'T' from dual union all
select 21155, to_date('21-Jun-17', 'dd-Mon-yy'), 'A' from dual union all
select 21155, to_date('13-Mar-18', 'dd-Mon-yy'), 'T' from dual union all
select 21155, to_date('15-Aug-18', 'dd-Mon-yy'), 'A' from dual
)
,
-- End of simulated data (for testing only).
/* SQL query (solution) begins BELOW THIS LINE.
with--*/
cte1 as
(
select sl.*
,sum(decode(sl.STATUS, 'T', 0, 1)) OVER (
PARTITION BY sl.EMP ORDER BY sl.EFFDT RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS non_t_count
from emp_status_log sl
)
select
cte1.emp
, min(cte1.effdt) as effdt
, min(cte1.status) as status
from cte1
where cte1.non_t_count = 0
group by cte1.emp

How to count consecutive duplicates in a table?

I have below question:
Want to find the consecutive duplicates
SLNO NAME PG
1 A1 NO
2 A2 YES
3 A3 NO
4 A4 YES
6 A5 YES
7 A6 YES
8 A7 YES
9 A8 YES
10 A9 YES
11 A10 NO
12 A11 YES
13 A12 NO
14 A14 NO
We will consider the value of PG column and I need the output as 6 which is the count of maximum consecutive duplicates.
It can be done with Tabibitosan method. Run this, to understand it:
with a as(
select 1 slno, 'A' pg from dual union all
select 2 slno, 'A' pg from dual union all
select 3 slno, 'B' pg from dual union all
select 4 slno, 'A' pg from dual union all
select 5 slno, 'A' pg from dual union all
select 6 slno, 'A' pg from dual
)
select slno, pg, newgrp, sum(newgrp) over (order by slno) grp
from(
select slno,
pg,
case when pg <> nvl(lag(pg) over (order by slno),1) then 1 else 0 end newgrp
from a
);
Newgrp means a new group is found.
Result:
SLNO PG NEWGRP GRP
1 A 1 1
2 A 0 1
3 B 1 2
4 A 1 3
5 A 0 3
6 A 0 3
Now, just use a group by with count, to find the group with maximum number of occurrences:
with a as(
select 1 slno, 'A' pg from dual union all
select 2 slno, 'A' pg from dual union all
select 3 slno, 'B' pg from dual union all
select 4 slno, 'A' pg from dual union all
select 5 slno, 'A' pg from dual union all
select 6 slno, 'A' pg from dual
),
b as(
select slno, pg, newgrp, sum(newgrp) over (order by slno) grp
from(
select slno, pg, case when pg <> nvl(lag(pg) over (order by slno),1) then 1 else 0 end newgrp
from a
)
)
select max(cnt)
from (
select grp, count(*) cnt
from b
group by grp
);
with test as (
select 1 slno,'A1' name ,'NO' pg from dual union all
select 2,'A2','YES' from dual union all
select 3,'A3','NO' from dual union all
select 4,'A4','YES' from dual union all
select 6,'A5','YES' from dual union all
select 7,'A6','YES' from dual union all
select 8,'A7','YES' from dual union all
select 9,'A8','YES' from dual union all
select 10,'A9','YES' from dual union all
select 11,'A10','NO' from dual union all
select 12,'A11','YES' from dual union all
select 13,'A12','NO' from dual union all
select 14,'A14','NO' from dual),
consecutive as (select row_number() over(order by slno) rr, x.*
from test x)
select x.* from Consecutive x
left join Consecutive y on x.rr = y.rr+1 and x.pg = y.pg
where y.rr is not null
order by x.slno
And you can control output with condition in where.
where y.rr is not null query returns duplicates
where y.rr is null query returns "distinct" values.
Just for completeness, here's the actual Tabibitosan method:
with sample_data as (select 1 slno, 'A1' name, 'NO' pg from dual union all
select 2 slno, 'A2' name, 'YES' pg from dual union all
select 3 slno, 'A3' name, 'NO' pg from dual union all
select 4 slno, 'A4' name, 'YES' pg from dual union all
select 6 slno, 'A5' name, 'YES' pg from dual union all
select 7 slno, 'A6' name, 'YES' pg from dual union all
select 8 slno, 'A7' name, 'YES' pg from dual union all
select 9 slno, 'A8' name, 'YES' pg from dual union all
select 10 slno, 'A9' name, 'YES' pg from dual union all
select 11 slno, 'A10' name, 'NO' pg from dual union all
select 12 slno, 'A11' name, 'YES' pg from dual union all
select 13 slno, 'A12' name, 'NO' pg from dual union all
select 14 slno, 'A14' name, 'NO' pg from dual)
-- end of mimicking a table called "sample_data" containing your data; see SQL below:
select max(cnt) max_pg_in_queue
from (select count(*) cnt
from (select slno,
name,
pg,
row_number() over (order by slno)
- row_number() over (partition by pg
order by slno) grp
from sample_data)
where pg = 'YES'
group by grp);
MAX_PG_IN_QUEUE
---------------
6
SELECT MAX(consecutives) -- Block 1
FROM (
SELECT t1.pg, t1.slno, COUNT(*) AS consecutives -- Block 2
FROM test t1 INNER JOIN test t2 ON t1.pg = t2.pg
WHERE t1.slno <= t2.slno
AND NOT EXISTS (
SELECT * -- Block 3
FROM test t3
WHERE t3.slno > t1.slno
AND t3.slno < t2.slno
AND t3.pg != t1.pg
)
GROUP BY t1.pg, t1.slno
);
The query calculates the result in following way:
Extract all couples of records that don't have a record with different value of PG in between (blocks 2 and 3)
Group them by PG value and starting SLNO value -> this counts the consecutive values for any [PG, (starting) SLNO] couple (block 2);
Extract Maximum value from query 2 (block 1)
Note that the query may be simplified if the slno field in table contains consecutive values, but this seems not your case (in your example record with SLNO = 5 is missing)
Only requiring a single aggregation query and no joins (the rest of the calculation can be done with ROW_NUMBER, LAG and LAST_VALUE):
SELECT MAX( num_before_in_queue ) AS max_sequential_in_queue
FROM (
SELECT rn - LAST_VALUE( has_changed ) IGNORE NULL OVER ( ORDER BY ROWNUM ) + 1
AS num_before_in_queue
FROM (
SELECT pg,
ROW_NUMBER() OVER ( ORDER BY slno ) AS rn,
CASE pg WHEN LAG( pg ) OVER ( ORDER BY slno )
THEN NULL
ELSE ROW_NUMBER() OVER ( ORDER BY sl_no )
END AS change
FROM table_name
)
WHERE pg = 'Y'
);
Try to use row_number()
select
SLNO,
Name,
PG,
row_number() over (partition by PG order by PG) as 'Consecutive'
from
<table>
order by
SLNO,
NAME,
PG
This is should work with minor tweaking.
--EDIT--
Sorry, partiton by PG.
The partitioning tells the row_number when to start a new sequence.