How to find all fields with same value in oracle - sql

Say we have the table below
Name Date reg_no
aa 12/12/2013 10:13:15 rty003
aa 13/12/2013 11:14:16 jyu887
bb 13/14/2013 09:45:10 rty003
bb 12/12/2013 10:13:27 rty003
i want to display only the names that have matching reg_no and date values (without seconds).
The result should be as shown below.
Name1 Name2 Date reg_no
aa bb 12/12/2013 10:13 rty003
How can i achieve this in oracle?

Try with listagg function:
SELECT listagg( NAME,',') WITHIN GROUP ( ORDER BY NAME),
to_char(date_time,'mm/dd/yyyy hh24:mi'),
reg_no
FROM t
GROUP BY to_char(date_time,'mm/dd/yyyy hh24:mi'), reg_no
HAVING count(*) > 1;

Could be better but worth,
WITH TAB(NAME,DDATE, REG_NO) AS (
SELECT 'aa', TO_DATE('12/12/2013 10:13:15', 'MM/DD/YYYY HH24:MI:SS'), 'rty003' FROM DUAL UNION ALL
SELECT 'aa', TO_DATE('03/12/2013 11:14:16', 'MM/DD/YYYY HH24:MI:SS'), 'jyu887' FROM DUAL UNION ALL
SELECT 'bb', TO_DATE('03/14/2013 09:45:10', 'MM/DD/YYYY HH24:MI:SS'), 'rty003' FROM DUAL UNION ALL
SELECT 'bb', TO_DATE('12/12/2013 10:13:27', 'MM/DD/YYYY HH24:MI:SS'), 'rty003' FROM DUAL),
----------------
-- End of Data Preparation
----------------
TAB2 AS
(SELECT NAME,
TRUNC(DDATE, 'MI') DDATE,
REG_NO,
ROW_NUMBER() OVER(PARTITION BY TRUNC(DDATE, 'MI'), REG_NO ORDER BY NAME) RN,
COUNT(1) OVER(PARTITION BY TRUNC(DDATE, 'MI'), REG_NO) CNT
FROM TAB)
SELECT MIN(DECODE(RN, 1, NAME, NULL)) NAME1,
MIN(DECODE(RN, 2, NAME, NULL)) name2,
DDATE,
REG_NO
FROM TAB2
WHERE CNT = 2
group by ddate, reg_no;
Output:
| NAME1 | NAME2 | DDATE | REG_NO |
|-------|-------|---------------------------------|--------|
| aa | bb | December, 12 2013 10:13:00+0000 | rty003 |
Your final query will be
WITH TAB2 AS
(SELECT NAME,
TRUNC(DDATE, 'MI') DDATE,
REG_NO,
ROW_NUMBER() OVER(PARTITION BY TRUNC(DDATE, 'MI'), REG_NO ORDER BY NAME) RN,
COUNT(1) OVER(PARTITION BY TRUNC(DDATE, 'MI'), REG_NO) CNT
FROM <your_table_name>)
SELECT MIN(DECODE(RN, 1, NAME, NULL)) NAME1,
MIN(DECODE(RN, 2, NAME, NULL)) name2,
DDATE,
REG_NO
FROM TAB2
WHERE CNT = 2
group by ddate, reg_no;

SELECT MIN(NAME),MAX(NAME),
TRUNC(DDATE, 'MI'),
REG_NO
FROM tab
GROUP BY
TRUNC(DDATE, 'MI'),
REG_NO
HAVING COUNT(*) >= 2

Related

create time range with 2 columns date_time

The problem I am facing is how to find distinct time periods from multiple time periods with overlap in Teradata ANSI SQL.
For example, the attached tables contain multiple overlapping time periods, how can I combine those time periods into 3 unique time periods in Teradata SQL???
I think I can do it in python with the loop function, but not sure how to do it in SQL
ID
Start Date
End Date
001
2005-01-01
2006-01-01
001
2005-01-01
2007-01-01
001
2008-01-01
2008-06-01
001
2008-04-01
2008-12-01
001
2010-01-01
2010-05-01
001
2010-04-01
2010-12-01
001
2010-11-01
2012-01-01
My expected result is:
ID
start_Date
end_date
001
2005-01-01
2007-01-01
001
2008-01-01
2008-12-01
001
2010-01-01
2012-01-01
From Oracle 12, you can use MATCH_RECOGNIZE to perform a row-by-row comparison:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY id
ORDER BY start_date
MEASURES
FIRST(start_date) AS start_date,
MAX(end_date) AS end_date
ONE ROW PER MATCH
PATTERN (overlapping_ranges* last_range)
DEFINE overlapping_ranges AS NEXT(start_date) <= MAX(end_date)
)
Which, for the sample data:
CREATE TABLE table_name (ID, Start_Date, End_Date) AS
SELECT '001', DATE '2005-01-01', DATE '2006-01-01' FROM DUAL UNION ALL
SELECT '001', DATE '2005-01-01', DATE '2007-01-01' FROM DUAL UNION ALL
SELECT '001', DATE '2008-01-01', DATE '2008-06-01' FROM DUAL UNION ALL
SELECT '001', DATE '2008-04-01', DATE '2008-12-01' FROM DUAL UNION ALL
SELECT '001', DATE '2010-01-01', DATE '2010-05-01' FROM DUAL UNION ALL
SELECT '001', DATE '2010-04-01', DATE '2010-12-01' FROM DUAL UNION ALL
SELECT '001', DATE '2010-11-01', DATE '2012-01-01' FROM DUAL;
Outputs:
ID
START_DATE
END_DATE
001
2005-01-01 00:00:00
2007-01-01 00:00:00
001
2008-01-01 00:00:00
2008-12-01 00:00:00
001
2010-01-01 00:00:00
2012-01-01 00:00:00
db<>fiddle here
Update: Alternative query
SELECT id,
start_date,
end_date
FROM (
SELECT id,
dt,
SUM(cnt) OVER (PARTITION BY id ORDER BY dt) AS grp,
cnt
FROM (
SELECT ID,
dt,
SUM(type) OVER (PARTITION BY id ORDER BY dt, ROWNUM) * type AS cnt
FROM table_name
UNPIVOT (dt FOR type IN (start_date AS 1, end_date AS -1))
)
WHERE cnt IN (1,0)
)
PIVOT (MAX(dt) FOR cnt IN (1 AS start_date, 0 AS end_date))
Or, an equivalent that does not use UNPIVOT, PIVOT or ROWNUM and works in both Oracle and PostgreSQL:
SELECT id,
MAX(CASE cnt WHEN 1 THEN dt END) AS start_date,
MAX(CASE cnt WHEN 0 THEN dt END) AS end_date
FROM (
SELECT id,
dt,
SUM(cnt) OVER (PARTITION BY id ORDER BY dt) AS grp,
cnt
FROM (
SELECT ID,
dt,
SUM(type) OVER (PARTITION BY id ORDER BY dt, rn) * type AS cnt
FROM (
SELECT r.*,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY dt ASC, type DESC) AS rn
FROM (
SELECT id, 1 AS type, start_date AS dt FROM table_name
UNION ALL
SELECT id, -1 AS type, end_date AS dt FROM table_name
) r
) p
) s
WHERE cnt IN (1,0)
) t
GROUP BY id, grp
Update 2: Another Alternative
SELECT id,
MIN(start_date) AS start_date,
MAX(end_Date) AS end_date
FROM (
SELECT t.*,
SUM(CASE WHEN start_date <= prev_max THEN 0 ELSE 1 END)
OVER (PARTITION BY id ORDER BY start_date) AS grp
FROM (
SELECT t.*,
MAX(end_date) OVER (
PARTITION BY id ORDER BY start_date
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS prev_max
FROM table_name t
) t
) t
GROUP BY id, grp
db<>fiddle Oracle PostgreSQL
This is a gaps and islands problem. Try this:
with u as
(select ID, start_date, end_date,
case
when start_date <= lag(end_date) over(partition by ID order by start_date, end_date) then 0
else 1 end as grp
from table_name),
v as
(select ID, start_date, end_date,
sum(grp) over(partition by ID order by start_date, end_date) as island
from u)
select ID, min(start_date) as start_Date, max(end_date) as end_date
from v
group by ID, island;
Fiddle
Basically you can identify "islands" by comparing start_date of current row to end_date of previous row (ordered by start_date, end_date), if it precedes it then it's the same island. Then you can do a rolling sum() to get the island numbers. Finally select min(start_date) and max(end_date) from each island to get the desired output.
This may work ,with little bit of change in function , I tried it in Dbeaver :
select ID,Start_Date,End_Date
from
(
select t.*,
dense_rank () over(partition by extract (year from Start_Date) order BY End_Date desc) drnk
from testing_123 t
) temp
where temp.drnk = 1
ORDER BY Start_Date;
Try this
WITH a as (
SELECT
ID,
LEFT(Start_Date, 4) as Year,
MIN(Start_Date) as New_Start_Date
FROM
TAB1
GROUP BY
ID,
LEFT(Start_Date, 4)
), b as (
SELECT
a.ID,
Year,
New_Start_Date,
End_Date
FROM
a
LEFT JOIN
TAB1
ON LEFT(a.New_Start_Date, 4) = LEFT(TAB1.Start_Date, 4)
)
select
ID,
New_Start_Date as Start_Date,
MAX(End_Date)
from
b
GROUP BY
ID,
New_Start_Date;
Example: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=97f91b68c635aebfb752538cdd752ace

find at least 2 consecutive items based on date ranges

there are lot of solutions of similar question but based only one date column.
I would like to know maybe better solution for this to solve, I am attaching my solution but I find it a little bit complicated if you know better approach to this please post it.
here is table with orders with start and end dates for 2 items.
I would like to print at least 2 consecutive rows based on date and item.
ITEM , START , END
1. A, 01.01.2020, 31.01.2020
2. A, 01.02.2020, 31.03.2020
3. B, 01.02.2020, 30.04.2020
4. A, 01.05.2020, 30.06.2020
5. B, 01.06.2020, 31.07.2020
6. B, 01.09.2020, 30.09.2020
7. A, 01.08.2020, 31.10.2020
8. B, 01.10.2020, 31.10.2020
9. B, 01.11.2020, 31.12.2020
the output should be rows 1 and 2 for item A and rows 6,8 and 9 for item B
here is my approuch to this
with pool as (
select ITEM, START_DATE, END_DATE,
nvl(lag(end_date,1) over (partition by item order by end_date),START_DATE-1) prev_End_Date
from orders )
, pool2 as (
select item ,
START_DATE, END_DATE,
sum(case when PREV_END_DATE+1 = START_DATE then 0 else 1 end ) over (partition by item order by START_DATE) grp
from pool )
select item,start_date,end_date from (
select
ITEM,
START_DATE,
END_DATE,
grp,
count(grp) over (partition by item,grp ) cnt
from pool2)
where cnt>=2
;
Hmmm . . . use lag() and lead() to see the next/previous values and check if they match:
select o.*
from (select o.*,
lag(end) over (partition by product order by start) as prev_end,
lead(start) over (partition by product order by start) as next_start
from orders o
) o
where start = prev_end + interval '1' day or
end = next_start - interval '1' day;
-- create table and insert rows for test
Create table order_overlap (id number, item varchar2(1), start_date date , end_date date );
insert into order_overlap(id,start_date, end_date, item) values( 1,to_date('01.01.2020', 'dd.mm.yyyy'), to_date( '31.01.2020', 'dd.mm.yyyy'), 'A');
insert into order_overlap(id,start_date, end_date, item) values( 2, to_date('01.02.2020', 'dd.mm.yyyy'), to_date( '31.03.2020', 'dd.mm.yyyy'), 'A');
insert into order_overlap(id,start_date, end_date, item) values( 3, to_date('01.02.2020', 'dd.mm.yyyy'), to_date( '30.04.2020', 'dd.mm.yyyy'), 'B');
insert into order_overlap(id,start_date, end_date, item) values( 4, to_date('01.05.2020', 'dd.mm.yyyy'), to_date( '30.06.2020', 'dd.mm.yyyy'), 'A');
insert into order_overlap(id,start_date, end_date, item) values( 5, to_date('01.06.2020', 'dd.mm.yyyy'), to_date( '31.07.2020', 'dd.mm.yyyy'), 'B');
insert into order_overlap(id,start_date, end_date, item) values( 6, to_date('01.09.2020', 'dd.mm.yyyy'), to_date( '30.09.2020', 'dd.mm.yyyy'), 'B');
insert into order_overlap(id,start_date, end_date, item) values( 7, to_date('01.08.2020', 'dd.mm.yyyy'), to_date( '31.10.2020', 'dd.mm.yyyy'), 'A');
insert into order_overlap(id,start_date, end_date, item) values( 8, to_date('01.10.2020', 'dd.mm.yyyy'), to_date( '31.10.2020', 'dd.mm.yyyy'), 'B');
insert into order_overlap(id,start_date, end_date, item) values( 5, to_date('01.11.2020', 'dd.mm.yyyy'), to_date( '31.12.2020', 'dd.mm.yyyy'), 'B');
-- I did sth a little bit different but maybe you like it.
-- I joined conseutive rows into one - so if you have item
A 01.01.2020 - 31.01.2020
A 01.02.2020 - 28.02.2020
you get one recod
A 01.01.2020 - 28.02.2020
select item, min(start_date) start_date , max(end_date) end_date, count(*)
from (
select item, start_date, end_date,
case when lead(start_date) over(partition by item order by start_date) = end_date + 1
OR lag(end_date) over(partition by item order by end_date) + 1 = start_date
then 0
else rownum
end continuity
from order_overlap )
group by item, continuity
order by item, start_date;
You can simply use MATCH_RECOGNIZE to perform a row-by-row comparison and to only return the groups of rows which match the pattern:
SELECT *
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY item
ORDER BY start_date, end_date
ALL ROWS PER MATCH
PATTERN ( FIRST_ROW NEXT_ROWS+ )
DEFINE
NEXT_ROWS AS (
NEXT_ROWS.START_DATE = PREV( END_DATE ) + INTERVAL '1' DAY
)
)
So, for your sample data:
CREATE TABLE table_name ( ITEM, START_DATE, END_DATE ) AS
SELECT 'A', DATE '2020-01-01', DATE '2020-01-31' FROM DUAL UNION ALL
SELECT 'A', DATE '2020-02-01', DATE '2020-03-31' FROM DUAL UNION ALL
SELECT 'B', DATE '2020-02-01', DATE '2020-04-30' FROM DUAL UNION ALL
SELECT 'A', DATE '2020-05-01', DATE '2020-06-30' FROM DUAL UNION ALL
SELECT 'B', DATE '2020-06-01', DATE '2020-07-31' FROM DUAL UNION ALL
SELECT 'B', DATE '2020-09-01', DATE '2020-09-30' FROM DUAL UNION ALL
SELECT 'A', DATE '2020-08-01', DATE '2020-10-31' FROM DUAL UNION ALL
SELECT 'B', DATE '2020-10-01', DATE '2020-10-31' FROM DUAL UNION ALL
SELECT 'B', DATE '2020-11-01', DATE '2020-12-31' FROM DUAL;
This outputs:
ITEM | START_DATE | END_DATE
:--- | :--------- | :---------
A | 2020-01-01 | 2020-01-31
A | 2020-02-01 | 2020-03-31
B | 2020-09-01 | 2020-09-30
B | 2020-10-01 | 2020-10-31
B | 2020-11-01 | 2020-12-31
db<>fiddle here

SQL min() max() function with exception

Here is my simplied code:
SELECT
a.user_id as User_ID,
min(b.a_day) as Date_from,
max(b.a_day) as Date_to,
c.code as ID
FROM a, b, c
WHERE
a_day > (day, -15, getdate())
GROUP BY
a.user_id,
c.code
Query gives the following output:
User ID date_from date_to id
1234567 2016-06-13 2016-06-13 B
1234567 2016-06-17 2016-06-17 A
12345672016-06-18 2016-06-18 A
1234567 2016-06-19 2016-06-19 A
1234567 2016-06-20 2016-06-20 A
1234567 2016-06-21 2016-06-21 B
I need something like this:
User ID date_from date_to id
1234567 2016-06-13 2016-06-13 B
1234567 2016-06-17 2016-06-20 A
1234567 2016-06-21 2016-06-21 B
When I use min() and max() function with group by, it aggregates fine for all records with ID=A but there should be exception for ID=B. I have to aggregate only dates with the same ID day after day.
Any ideas?
Thanks in advance.
You can combine these rows using the following strategy:
Determine where a new grouping begins.
Do a cumulative sum of the flag from (1) to identify each grouping.
Then do the aggregation.
This looks like:
select min(date_from) as date_from, max(date_to) as date_to, id
from (select t.*,
sum(isNewGroup) over (partition by id order by date_from) as grp
from (select t.*,
(case when lag(date_to) over (partition by id order by date_from) >= date_from
then 0 else 1
end) as isNewGroup
from t
) t
) t
group by id, grp;
it's my solution to get min/max continuous date.
try to run the SQL in your oracle.
is it helpful for you?
WITH TEST_DATA AS (
SELECT TO_DATE('20160613', 'YYYYMMDD') AS DATE_FROM, TO_DATE('20160613', 'YYYYMMDD') AS DATE_TO, 'B' AS ID FROM DUAL
UNION ALL
SELECT TO_DATE('20160617', 'YYYYMMDD') AS DATE_FROM, TO_DATE('20160617', 'YYYYMMDD') AS DATE_TO, 'A' AS ID FROM DUAL
UNION ALL
SELECT TO_DATE('20160618', 'YYYYMMDD') AS DATE_FROM, TO_DATE('20160618', 'YYYYMMDD') AS DATE_TO, 'A' AS ID FROM DUAL
UNION ALL
SELECT TO_DATE('20160619', 'YYYYMMDD') AS DATE_FROM, TO_DATE('20160619', 'YYYYMMDD') AS DATE_TO, 'A' AS ID FROM DUAL
UNION ALL
SELECT TO_DATE('20160620', 'YYYYMMDD') AS DATE_FROM, TO_DATE('20160620', 'YYYYMMDD') AS DATE_TO, 'A' AS ID FROM DUAL
UNION ALL
SELECT TO_DATE('20160621', 'YYYYMMDD') AS DATE_FROM, TO_DATE('20160621', 'YYYYMMDD') AS DATE_TO, 'B' AS ID FROM DUAL
)
SELECT
MIN(ID) AS ID,
MIN(DATE_FROM) AS DATE_FROM,
MAX(DATE_TO) AS DATE_TO
FROM (
SELECT
CONNECT_BY_ROOT(DATE_FROM) || CONNECT_BY_ROOT(ID) AS GROUP_KEY,
ROW_NUMBER() OVER(PARTITION BY ID, DATE_FROM, DATE_TO ORDER BY ID, LEVEL DESC) AS DISTINCT_FLG,
DATE_FROM,
DATE_TO,
ID
FROM
TEST_DATA
WHERE ID = CONNECT_BY_ROOT(ID)
CONNECT BY DATE_FROM = PRIOR DATE_TO + 1
ORDER BY DATE_FROM
)
WHERE
DISTINCT_FLG = 1
GROUP BY
GROUP_KEY
Here is mysql solution:
select grp, min(f) f, max(t) t, i
from
(
select x.*
,case when #lastu = i and datediff(f, #lastf)=1 then #gr:=#gr else #gr:=#gr+1 end grp
,#lastu:= i
,#lastf:= f
from
(
select '2016-06-13' f,'2016-06-13' t ,'B' i union all
select '2016-06-17','2016-06-17','A' union all
select '2016-06-18','2016-06-18','A' union all
select '2016-06-19','2016-06-19','A' union all
select '2016-06-20','2016-06-20','A' union all
select '2016-06-21','2016-06-21','B'
order by i, f, t
) x
, (select #gr:=0, #lastu:='', #lastf:='' ) b
) xx
group by grp, i

PL/SQL: How to convert multiple rows with continuous time frames into one single row convering the whole time frame?

Let's say I have the following database table:
id | from | to
1 | 01-JAN-2015 | 03-MAR-2015
1 | 04-MAR-2015 | 31-AUG-2015
1 | 01-SEP-2015 | 31-DEC-2015
2 | 01-JAN-2015 | 30-JUN-2015
2 | 01-NOV-2015 | 31-DEC-2015
And I want to summarise the records with the same id that are continuous in time into one single row covering the full time frame, as follows:
id | from | to
1 | 01-JAN-2015 | 31-DEC-2015
2 | 01-JAN-2015 | 30-JUN-2015
2 | 01-NOV-2015 | 31-DEC-2015
So, because the time frames are sequential and have no gaps between them, the 3 rows for id 1 could be converted into 1 single row with the minimum from date and the maximum to date. The 2 rows for id 2 would remain the same as the time frames are not continuous.
I'm thinking on doing this using a loop through a cursor, but I might be complicating things.
Any better ideas? perhaps with SQL queries only?
You can do it using hierarchical queries, something like this:
select id, min(root_dt_from) dt_from, dt_to
from (select id, dt_from, dt_to, level, connect_by_isleaf, connect_by_root(dt_from) root_dt_from
from t
where connect_by_isleaf = 1
connect by prior id = id and prior (dt_to + 1) = dt_from
)
group by id, dt_to;
Sample execution:
SQL> with t as (
2 select 1 id, to_date('01-JAN-2015', 'DD-MON-YYYY') dt_from, to_date('03-MAR-2015', 'DD-MON-YYYY') dt_to from dual union all
3 select 1 id, to_date('04-MAR-2015', 'DD-MON-YYYY') dt_from, to_date('31-AUG-2015', 'DD-MON-YYYY') dt_to from dual union all
4 select 1 id, to_date('01-SEP-2015', 'DD-MON-YYYY') dt_from, to_date('31-DEC-2015', 'DD-MON-YYYY') dt_to from dual union all
5 select 2 id, to_date('01-JAN-2015', 'DD-MON-YYYY') dt_from, to_date('30-JUN-2015', 'DD-MON-YYYY') dt_to from dual union all
6 select 2 id, to_date('01-NOV-2015', 'DD-MON-YYYY') dt_from, to_date('31-DEC-2015', 'DD-MON-YYYY') dt_to from dual
7 ) -- end of sample data
8 select id, min(root_dt_from) dt_from, dt_to
9 from (select id, dt_from, dt_to, level, connect_by_isleaf, connect_by_root(dt_from) root_dt_from
10 from t
11 where connect_by_isleaf = 1
12 connect by prior id = id and prior (dt_to + 1) = dt_from
13 )
14 group by id, dt_to;
ID DT_FROM DT_TO
---------- ----------- -----------
1 01-JAN-2015 31-DEC-2015
2 01-NOV-2015 31-DEC-2015
2 01-JAN-2015 30-JUN-2015
You can do this is stages with a few analytic and aggregate functions:
with t1(id, from_dt, to_dt) as (
select 1, to_date('01-JAN-2015', 'dd-mon-rrrr'), to_date('03-MAR-2015', 'dd-mon-rrrr') from dual union all
select 1, to_date('04-MAR-2015', 'dd-mon-rrrr'), to_date('31-AUG-2015', 'dd-mon-rrrr') from dual union all
select 1, to_date('01-SEP-2015', 'dd-mon-rrrr'), to_date('31-DEC-2015', 'dd-mon-rrrr') from dual union all
select 2, to_date('01-JAN-2015', 'dd-mon-rrrr'), to_date('30-JUN-2015', 'dd-mon-rrrr') from dual union all
select 2, to_date('01-NOV-2015', 'dd-mon-rrrr'), to_date('31-DEC-2015', 'dd-mon-rrrr') from dual
), t2 as (
select id
, from_dt
, to_dt
, from_dt-lag(to_dt,1,from_dt-1) over (partition by id order by to_dt) dst
, row_number() over (partition by id order by to_dt) rn
from t1
), t3 as (
select id
, from_dt
, to_dt
, sum(dst) over (partition by id order by rn) - rn grp
from t2
)
select id
, min(from_dt) from_dt
, max(to_dt) to_dt
from t3
group by id, grp;
The first stage T1 is just recreating your data. In T2 I subtract the lag of to_dt from from_dt to find the distance (dst) between consecutive records and generate row_number for each record (rn). In T3 I subtract rn from the running sum of dst to generate a group id (grp). Finally in the output stage I take the min and max of from_dt and to_dt respectively grouping by ID and grp columns.
You can try here some analytical functions which can really simplify
the scenario. Hope this below snippet helps. Let me know for any
issues.
SELECT B.ID,
MIN(B.FRM_DT) FRM_DT,
MAX(B.TO_DT) TO_DT
FROM
(SELECT A.ID,
A.FRM_DT,
A.TO_DT,
NVL(LAG(A.TO_DT+1) OVER(PARTITION BY A.ID ORDER BY A.TO_DT),A.FRM_DT) nxt_dt,
CASE
WHEN NULLIF(A.FRM_DT,NVL(LAG(A.TO_DT+1) OVER(PARTITION BY A.ID ORDER BY A.TO_DT),A.FRM_DT)) IS NULL
THEN 'True'
ELSE 'False'
END COND
FROM
(SELECT 1 AS ID,
TO_DATE('01/01/2015') FRM_DT,
TO_DATE('03/03/2015') TO_DT
FROM DUAL
UNION
SELECT 1 AS ID,
TO_DATE('03/04/2015') FRM_DT,
TO_DATE('07/31/2015') TO_DT
FROM DUAL
UNION
SELECT 1 AS ID,
TO_DATE('08/01/2015') FRM_DT,
TO_DATE('12/31/2015') TO_DT
FROM DUAL
UNION
SELECT 2 AS ID,
TO_DATE('01/01/2015') FRM_DT,
TO_DATE('06/30/2015') TO_DT
FROM DUAL
UNION
SELECT 2 AS ID,
TO_DATE('11/01/2015') FRM_DT,
TO_DATE('12/31/2015') TO_DT
FROM DUAL
UNION
SELECT 3 AS ID,
TO_DATE('01/01/2015') FRM_DT,
TO_DATE('03/14/2015') TO_DT
FROM DUAL
UNION
SELECT 3 AS ID,
TO_DATE('03/15/2015') FRM_DT,
TO_DATE('11/30/2015') TO_DT
FROM DUAL
UNION
SELECT 3 AS ID,
TO_DATE('12/01/2015') FRM_DT,
TO_DATE('12/31/2015') TO_DT
FROM DUAL
UNION
SELECT 4 AS ID,
TO_DATE('02/01/2015') FRM_DT,
TO_DATE('05/30/2015') TO_DT
FROM DUAL
UNION
SELECT 4 AS ID,
TO_DATE('06/01/2015') FRM_DT,
TO_DATE('12/31/2015') TO_DT
FROM DUAL
)A
)B
GROUP BY B.ID,
B.COND;
-----------------------------------OUTPUT------------------------------------------
ID FRM_DT TO_DT
4 02/01/2015 05/30/2015
4 06/01/2015 12/31/2015
1 01/01/2015 12/31/2015
2 01/01/2015 06/30/2015
2 11/01/2015 12/31/2015
3 01/01/2015 12/31/2015
-----------------------------------OUTPUT------------------------------------------

Finding a 'run' of rows from an ordered result set

I'm trying to figure out a way of identifying a "run" of results (successive rows, in order) that meet some condition. Currently, I'm ordering a result set, and scanning by eye for particular patterns. Here's an example:
SELECT the_date, name
FROM orders
WHERE
the_date BETWEEN
to_date('2013-09-18',..) AND
to_date('2013-09-22', ..)
ORDER BY the_date
--------------------------------------
the_date | name
--------------------------------------
2013-09-18 00:00:01 | John
--------------------------------------
2013-09-19 00:00:01 | James
--------------------------------------
2013-09-20 00:00:01 | John
--------------------------------------
2013-09-20 00:00:02 | John
--------------------------------------
2013-09-20 00:00:03 | John
--------------------------------------
2013-09-20 00:00:04 | John
--------------------------------------
2013-09-21 16:00:01 | Jennifer
--------------------------------------
What I want to extract from this result set is all the rows attributed to John on 2013-09-20. Generally what I'm looking for is a run of results from the same name, in a row, >= 3. I'm using Oracle 11, but I'm interested to know if this can be achieved with strict SQL, or if some kind of analytical function must be used.
You need multiple nested window functions:
SELECT *
FROM
(
SELECT the_date, name, grp,
COUNT(*) OVER (PARTITION BY grp) AS cnt
FROM
(
SELECT the_date, name,
SUM(flag) OVER (ORDER BY the_date) AS grp
FROM
(
SELECT the_date, name,
CASE WHEN LAG(name) OVER (ORDER BY the_date) = name THEN 0 ELSE 1 END AS flag
FROM orders
WHERE
the_date BETWEEN
TO_DATE('2013-09-18',..) AND
TO_DATE('2013-09-22', ..)
) dt
) dt
) dt
WHERE cnt >= 3
ORDER BY the_date
Try this
WITH ORDERS
AS (SELECT
TO_DATE ( '2013-09-18 00:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-19 00:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'James' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:02',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:03',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:04',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-21 16:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'Jennifer' AS NAME
FROM
DUAL)
SELECT
B.*
FROM
(SELECT
TRUNC ( THE_DATE ) THE_DATE,
NAME,
COUNT ( * )
FROM
ORDERS
WHERE
THE_DATE BETWEEN TRUNC ( TO_DATE ( '2013-09-18',
'YYYY-MM-DD' ) )
AND TRUNC ( TO_DATE ( '2013-09-22',
'YYYY-MM-DD' ) )
GROUP BY
TRUNC ( THE_DATE ),
NAME
HAVING
COUNT ( * ) >= 3) A,
ORDERS B
WHERE
A.NAME = B.NAME
AND TRUNC ( A.THE_DATE ) = TRUNC ( B.THE_DATE );
OUTPUT
9/20/2013 12:00:01 AM John
9/20/2013 12:00:02 AM John
9/20/2013 12:00:03 AM John
9/20/2013 12:00:04 AM John