Oracle sql: Find min max consecutive dates within same group - sql

I know the question is probably badly explained, but I don't know how else to explain this. I have the following data: (ordered by date)
DATE GROUP
11-Oct-16 A
12-Oct-16 A
13-Oct-16 A
14-Oct-16 B
15-Oct-16 B
16-Oct-16 A
17-Oct-16 A
18-Oct-16 C
19-Oct-16 C
20-Oct-16 C
21-Oct-16 C
22-Oct-16 A
23-Oct-16 A
24-Oct-16 A
I want to find consecutive usage for groups. The results I want will explain this better than me:
GROUP MIN(DATE) MAX(DATE)
A 11-Oct-16 13-Oct-16
B 14-Oct-16 15-Oct-16
A 16-Oct-16 17-Oct-16
C 18-Oct-16 21-Oct-16
A 22-Oct-16 24-Oct-16
Any idea how to do this in oracle sql?
Thank you.

This can be a way:
with test("DATE","GROUP") as
(
select to_date('11-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('12-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('13-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('14-10-16', 'dd-mm-rr'),'B' from dual union all
select to_date('15-10-16', 'dd-mm-rr'),'B' from dual union all
select to_date('16-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('17-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('18-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('19-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('20-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('21-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('22-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('23-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('24-10-16', 'dd-mm-rr'),'A' from dual
)
select min("DATE"), max("DATE"), "GROUP"
from (
select "DATE",
"DATE" - row_number() over (partition by "GROUP" order by "DATE") as minDate,
"GROUP"
from test
)
group by "GROUP", minDate
order by "GROUP", minDate
The inner query builds the minimum date for a group of consecutive dates, while the external one simply aggregates by this minimum date, thus building a row for every group of consecutive dates.
As an aside, it's better to avoid using reserved words as column names.

So basically I think that the proper answer was in #mathguy comment.
Here I am just expanding and giving the proper full query.
select GROUP_NAME, grp_id, min(date_field) as starting_date, max(date_field) as ending_date
from (
select DATE_FIELD, GROUP_NAME,
row_number() over ( order by date_field) - row_number() over ( partition by group_name order by date_field ) as grp_id
from darber.my_table) innested
group by GROUP_NAME, grp_id
order by min(date_field);
Please note: I changed the field name because they were giving conflicts on my database. In this solution DATE is now DATE_FIELD and GROUP is now GROUP_NAME.
I found this to be a really smart and elegant solution.
I hope will help others.

I had the same issue with multiple members in the group. In case you are running into the same issue, replace the GROUP_NAME with "groupMember1, groupMember2, ..."

Related

SQL to identify missing dates in column

Is there a way to generate list of missing dates in table in Oracle?
Input
name,my_date
A,04-JAN-2000
A,05-JAN-2000
A,08-JAN-2000
A,08-JAN-2000 -- duplicates possible
A,10-JAN-2000
B,09-FEB-2001
B,10-FEB-2001
B,05-FEB-2001
Result
A,06-JAN-2000
A,07-JAN-2000
A,09-JAN-2000
B,06-FEB-2001
B,07-FEB-2001
B,08-FEB-2001
After suggestion from #diiN__________ to see Oracle: select missing dates, I managed to get it working for a specific name as follows:
WITH all_dates_wo_boundary_values as
(SELECT oldest + level my_date
FROM (SELECT MIN(my_date) oldest
,MAX(my_date) recent
FROM mytable my
WHERE my.name = 'A'
)
connect by level <= recent - oldest - 1
)
SELECT my_date
FROM all_dates_wo_boundary_values
MINUS
SELECT my_date
FROM mytable my
WHERE my.name = 'A'
How could it be done for multiple names at once?
For multiple names, you can use the LEAD analytic function to find the next date and then CROSS JOIN LATERAL (available from Oracle 12) a row-generator to generate the missing values:
SELECT t.name,
m.missing
FROM (
SELECT name,
dt,
LEAD(dt) OVER (PARTITION BY name ORDER BY dt) AS next_dt
FROM table_name
) t
CROSS JOIN LATERAL (
SELECT dt + LEVEL AS missing
FROM DUAL
WHERE dt + 1 < next_dt
CONNECT BY dt + LEVEL < next_dt
) m
Which, for the sample data:
CREATE TABLE table_name (Name,dt) AS
SELECT 'A', DATE '2000-01-04' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-05' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-08' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-08' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-10' FROM DUAL UNION ALL
SELECT 'B', DATE '2001-02-05' FROM DUAL UNION ALL
SELECT 'B', DATE '2001-02-09' FROM DUAL UNION ALL
SELECT 'B', DATE '2001-02-10' FROM DUAL;
Outputs:
NAME
MISSING
A
06-JAN-00
A
07-JAN-00
A
09-JAN-00
B
06-FEB-01
B
07-FEB-01
B
08-FEB-01
db<>fiddle here

replacement of Offset Limit in SQL Server

We have DataTemp table which has the records in desc order.
select * from (
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
sno - Auto Id.
id - code of the Doors*.
N_th_Record - for denoting the n the record.
At a time, only three* records per Door are need to store on this table. For example Door 'a' has new entry(means 4th record) then first of 'a' Door need to delete.
4th record:
select * from (
select 10,'a',4 union --- new entry
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union -- need to delete
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
I do following query. But I need easiest way for deleting the row. Because, we are try to reduce the time consumption of over all project.
delete from door where sno = (
select sno from (
select 10,'a',4 union
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
where id = 'a'
order by sno desc -- For 'DataTemp' *order by* is no needed.
offset 3 rows fetch next 1 rows only
)
Note:
Three rows and three Door are given for example. Actually we work with 144 rows per 12 Doors.
Before this delete, we check lot of Business rules.
Version: SQL Server 2012
You could use ROW_NUMBER:
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY id ORDER BY sno DESC) rn FROM t)
DELETE FROM cte WHERE rn > 3;
db<>fiddle demo

update statement is not working in my query

with t as (
select 'AA-00001152' itemid from dual union all
select 'AA-00001152' from dual union all
select 'AA-00001153' from dual union all
select 'AA-00001154' from dual union all
select 'AA-00001154' from dual union all
select 'CC-254565' from dual union all
select 'AA-00001156' from dual union all
select 'AA-00001156' from dual union all
select 'BB-00001200' from dual
)
select 14999 + dense_rank() over(order by itemid) as seq_no,
itemid
from t
order by seq_no
Here i have generated seq_no for multiple itemIds, but i am trying to update in a seq_no column which is throwing error saying subquery returns more than one row. Please help in update query. thanks.
my update query:-
update test
set seq_num =14999 + dense_rank() over(order by itemid)
where item_type='non_product')
In SQL Server, you can use an updatable CTE:
with toupdate as (
select dense_rank() over (order by itemid) as seqnum,
t.*
from t
)
update toupdate
set seq_num = 14999 + seqnum
where item_type = 'non_product';
However, I suspect that you are not really using SQL Server. This syntax would not work in most other databases.

Query in oracle to extract data with prevoius daytime from certain dayime

I have name and daytime columns, so the same name could have multiple dates,
what would be the query in oracle to extract data with prevoius daytime from certain dayime, for example:
name daytime
t12 12-Mar-2016
t12 14-Aug-2016
t34 13-Jan-2005
t34 18-Mar-2005
and I need:
name daytime
t12 12-Mar-2016
t34 13-Jan-2005
thanks,
S
You could use a min and group by
select name, min(daytime)
from my_table
group by name
One way is to use the analytic function row_number() (or perhaps one of its close relatives, either rank() or dense_rank(), depending on the requirement. They will all produce the same output if there are no "ties", meaning you can't have two rows with the same name and the same daytime).
This is more often used with rn = 1 to get the max daytime; if you want the "second" most recent daytime, use rn = 2.
The inner query returns one row for each row in the input table - with the same columns and values in each row and column, and with one column added, rn, showing the "rank" of each row within its group of rows with the same name (the column in partition by), ordered by daytime descending. Then it should be obvious what the outer query does.
with
test_data ( name, daytime ) as (
select 't12', to_date('12-Mar-2016', 'dd-Mon-yyyy') from dual union all
select 't12', to_date('14-Aug-2016', 'dd-Mon-yyyy') from dual union all
select 't34', to_date('13-Jan-2005', 'dd-Mon-yyyy') from dual union all
select 't34', to_date('18-Mar-2005', 'dd-Mon-yyyy') from dual
)
-- End of test data (not part of the solution). SQL query begins BELOW THIS LINE
select name, daytime
from (
select name, daytime,
row_number() over (partition by name order by daytime desc) as rn
from test_data
)
where rn = 2
;
NAME DAYTIME
---- -----------
t12 12-Mar-2016
t34 13-Jan-2005
2 rows selected.
I am not sure I understand the question, but possibly you want to use LAG. This will give you the date before any given date, assuming no duplicates. Borrowing mathguy's test data CTE, I tried:
with
test_data ( name, daytime ) as (
select 't12', to_date('12-Mar-2016', 'dd-Mon-yyyy') from dual union all
select 't12', to_date('14-Aug-2016', 'dd-Mon-yyyy') from dual union all
select 't34', to_date('13-Jan-2005', 'dd-Mon-yyyy') from dual union all
select 't34', to_date('18-Mar-2005', 'dd-Mon-yyyy') from dual
)
-- End of test data (not part of the solution). SQL query begins BELOW THIS LINE
select * from
(
select name, lag(daytime) over(partition by name order by daytime) daytime
from test_data
)
where daytime is not null

Accesing parent identifier in an ordered nested subquery

I need a query to get the value of an item together with the value of the previous item if exists.
I am using the following query (a simplification of the actual):
select v1.value item_value,
nvl(
(
select * from (
select v2.value
from ITEMS v2
where v2.insert_date<v1.insert_date
order by v2.insert_date desc
) where rownum=1
), 0
) as previous_value
from ITEMS v1
where v1.item_id=1234
This query won't work (ORA-00904) because I am using v1.insert_date in an inner select with two levels of nesting.
How can I achieve this with Oracle 11?
I think you can achieve this with analytic function LAG. More info about analytic functions LAG LEAD
I created a sample query:
with items as (
select 1 as value, sysdate as insert_date from dual
union all
select 2 as value, sysdate-1 as insert_date from dual
union all
select 3 as value, sysdate+1 as insert_date from dual
)
select v1.value item_value,
lag(v1.value,1,0) over (order by v1.insert_date desc) as previous_value,insert_date
from ITEMS v1
order by insert_date desc