SQL Query Startdate to Enddate - sql

I have a table (eg. TableA) like this
| Name | Startdate | Enddate |
|----------------------------------------------|
| a | 2014-02-26 | 2014-02-28 |
| b | 2014-03-05 | 2014-03-06 |
If I want to below results. Please help how to do?
| Name | Date |
|---------------------------|
| a | 2014-02-26 |
| a | 2014-02-27 |
| a | 2014-02-28 |
| b | 2014-03-05 |
| b | 2014-03-06 |
Thank you.

In Oracle it's a little tricky:
SELECT distinct b.name, trim(regexp_substr(b.dates, '[^,]+', 1, LEVEL)) day
FROM
(
select a.name name,(select WM_CONCAT(a.startdate + rownum -1) from all_objects where rownum <= a.enddate - a.startdate + 1) dates
from TableA a
) b
CONNECT BY LEVEL <= length(b.dates) - length(REPLACE(b.dates, ',', ''))+1
order by 1,2;
The inner query generates days for each name as a comma-separated String. The outer simply splits the String in several rows.

In SQL Server it can be achieved by using CTE as below
;WITH cte(name, Startdate,Enddate)
AS
(
SELECT name, Startdate,Enddate FROM your_table
UNION ALL
SELECT name, DATEADD(dd, 1,Startdate),Enddate FROM CTE
WHERE Startdate < Enddate
)
SELECT * FROM cte ORDER BY name
Here is the code at SQL Fiddle

Here is a easy way to do this
select name, startdate from test_table
UNION ALL
select name, enddate from test_table
order by 2 desc

Related

Split a date range in SQL Server

I'm struggling with a solution for a problem but I couldn't find anything similar here.
I have a table "A" like:
+---------+------------+------------+-----------+
| user_id | from | to | attribute |
+---------+------------+------------+-----------+
| 1 | 2020-01-01 | 2020-12-31 | abc |
+---------+------------+------------+-----------+
and I get a table "B" like:
+---------+------------+------------+-----------+
| user_id | from | to | attribute |
+---------+------------+------------+-----------+
| 1 | 2020-03-01 | 2020-04-15 | def |
+---------+------------+------------+-----------+
And what I need is:
+---------+------------+------------+-----------+
| user_id | from | to | attribute |
+---------+------------+------------+-----------+
| 1 | 2020-01-01 | 2020-02-29 | abc |
| 1 | 2020-03-01 | 2020-04-15 | def |
| 1 | 2020-04-16 | 2020-12-31 | abc |
+---------+------------+------------+-----------+
I tried just using insert and update but I couldn't figure out how to simultaneously do both. Is there a much simpler way? I read about CTE, could this be an approach?
I'd be very thankful for your help!
Edit: more examples
TABLE A
| user_id | from | to | attribute |
+=========+============+============+===========+
| 1 | 2020-01-01 | 2020-12-31 | atr1 |
| 1 | 2021-01-01 | 2021-12-31 | atr2 |
| 2 | 2020-01-01 | 2021-06-15 | atr1 |
| 3 | 2020-01-01 | 2021-06-15 | atr3 |
TABLE B
| user_id | from | to | attribute |
+=========+============+============+===========+
| 1 | 2020-09-01 | 2021-02-15 | atr3 |
| 2 | 2020-04-15 | 2020-05-31 | atr2 |
| 3 | 2021-04-01 | 2022-01-01 | atr1 |
OUTPUT:
| user_id | from | to | attribute |
+=========+============+============+===========+
| 1 | 2020-01-01 | 2020-08-31 | atr1 |
| 1 | 2020-09-01 | 2021-02-15 | atr3 |
| 1 | 2021-02-16 | 2021-12-31 | atr2 |
| 2 | 2020-01-01 | 2020-04-14 | atr1 |
| 2 | 2020-04-15 | 2020-05-31 | atr2 |
| 2 | 2020-06-01 | 2021-06-15 | atr1 |
| 3 | 2020-01-01 | 2021-03-31 | atr3 |
| 3 | 2021-04-01 | 2022-01-01 | atr1 |
Initially I just asked to split the date range and make a new row because the new attribute of table B is between the one in table A. But it's only a part of the problem. Maybe it's more clear with the new dataset(?)
Sample data,
create table #TableA( userid int, fromdt date
,todt date, attribute varchar(10))
insert into #TableA (userid , fromdt , todt , attribute)
values
( 1 ,'2020-01-01','2020-12-31' , 'atr1' ),
( 1 ,'2021-01-01','2021-12-31' , 'atr2' ),
( 2 ,'2020-01-01','2021-06-15' , 'atr1' ),
( 3 ,'2020-01-01','2021-06-15' , 'atr3' )
create table #TableB( userid int,fromdt date
,todt date, attribute varchar(10))
insert into #TableB (userid,fromdt, todt, attribute)
values
( 1 ,'2020-09-01','2021-02-15' , 'atr3' ),
( 2 ,'2020-04-15','2020-05-31' , 'atr2' ),
( 3 ,'2021-04-01','2022-01-01' , 'atr1' )
;
The Script,
;WITH CTE
AS (
SELECT *
FROM #TableA
UNION ALL
SELECT *
FROM #TableB
)
,CTE2
AS (
SELECT userid
,min(fromdt) minfromdt
,max(todt) maxtodt
FROM CTE
GROUP BY userid
)
,CTE3
AS (
SELECT c.userid
,c.fromdt
,c.todt
,c.attribute
,LEAD(c.fromdt, 1) OVER (
PARTITION BY c.userid ORDER BY c.fromdt
) LeadFromdt
FROM CTE c
)
,CTE4
AS (
SELECT c3.userid
,c3.fromdt
,CASE
WHEN c3.todt > c3.LeadFromdt
THEN dateadd(day, - 1, c3.leadfromdt)
--when c3.todt<c3.LeadFromdt then dateadd(day,-1,c3.leadfromdt)
ELSE c3.todt
END AS Todt
,
--c3.todt as todt1,
c3.attribute
FROM CTE3 c3
)
,CTE5
AS (
SELECT userid
,fromdt
,todt
,attribute
FROM CTE4
UNION ALL
SELECT c2.userid
,dateadd(day, 1, c4.Todt) AS Fromdt
,maxtodt AS Todt
,c4.attribute
FROM CTE2 c2
CROSS APPLY (
SELECT TOP 1 c4.todt
,c4.attribute
FROM cte4 c4
WHERE c2.userid = c4.userid
ORDER BY c4.Todt DESC
) c4
WHERE c2.maxtodt > c4.Todt
)
SELECT *
FROM CTE5
ORDER BY userid
,fromdt
drop table #TableA, #TableB
Your output is wrong.
Also append other sample data in same example
where my script is not working.
The easiest way is to work with a calendar table. You can create one and reuse it later.
When you have one (here I called it "AllDates"), you can do something like this:
WITH cte
as
(
select ad.theDate,u.userid,isnull(b.attrib,a.attrib) as attrib,
ROW_NUMBER() over (PARTITION BY u.userid, isnull(b.attrib,a.attrib)ORDER BY ad.theDate)
- ROW_NUMBER() over (PARTITION BY u.userid ORDER BY ad.theDate) as grp
from AllDates ad
cross join (select userid from tableA union select userid from tableB) u
left join tableB b on ad.theDate between b.frm and b.toD and u.userid = b.userid
left join tableA a on ad.theDate between a.frm and a.toD and u.userid = a.userid
where b.frm is not null
or a.frm is not null
)
SELECT userid,attrib,min(theDate) as frmD, max(theDate) as toD
FROM cte
GROUP BY userid,attrib,grp
ORDER BY 1,3;
If I understand the request correctly the data from table A should be merged into table B to fill the gaps based on four scenarios, here is how I achieved it:
/*
Scenario 1 - Use dates from B as base to be filled in from A
- Start and end dates from B
*/
SELECT
B.UserId,
B.StartDate,
B.EndDate,
B.Attr
FROM #tmpB AS B
UNION
/*
Scenario 2 - Start date between start and end date of another record
- End date from B plus one day as start date
- End date from A as end date
*/
SELECT
B.UserId,
DATEADD(DD, 1, B.EndDate) AS StartDate,
A.EndDate,
A.Attr
FROM #tmpB AS B
JOIN #tmpA AS A ON
B.UserId = A.UserId
AND B.StartDate < A.StartDate
AND B.EndDate > A.StartDate
UNION
/*
Scenario 3 - End date between start and end date of another record or both dates between start and end date of another record
- Start date from A as start date
- Start date from B minus one as end date
*/
SELECT
B.UserId,
A.StartDate,
DATEADD(DD, -1, B.StartDate) AS EndDate,
A.Attr
FROM #tmpB AS B
JOIN #tmpA AS A ON
B.UserId = A.UserId
AND (B.StartDate < A.EndDate AND B.EndDate > A.EndDate
OR B.StartDate BETWEEN A.StartDate AND A.EndDate AND B.EndDate BETWEEN A.StartDate AND A.EndDate)
UNION
/*
Scenario 4 - Both dates between start and end date of another record
- End date from B minus one as start date
- End date from A as end date
*/
SELECT
B.UserId,
DATEADD(DD, -1, B.EndDate) AS StartDate,
A.EndDate,
A.Attr
FROM #tmpB AS B
JOIN #tmpA AS A ON
B.UserId = A.UserId
AND B.StartDate BETWEEN A.StartDate AND A.EndDate
AND B.EndDate BETWEEN A.StartDate AND A.EndDate

Overlap SQL with group by

I have a question regarding overlap in SQL, I have the following structure and data from my database:
Table A (Id = uniqueidentifier)
| Name | StartDate | EndDate | DaysToReceive |
------------------------------------------------------
| A | 2019-08-26 | 2020-04-13 | 232 |
| A | 2019-12-15 | 2020-04-11 | 119 |
| A | 2020-03-06 | 2020-03-31 | 26 |
| B | 2020-01-07 | 2020-01-31 | 25 |
| B | 2020-02-11 | 2020-02-29 | 19 |
I need to get the days to receive, but if there is an overlap I need the difference between the min date and the max date otherwise I use the DaysToReceive Summed Column.
I'm trying to get the result to look like this:
| Name | DaysToReceive |
------------------------
A | 232
B | 44
I have managed to get this query but only works for overlap days.
select DATEDIFF(d, MIN(t1.dt),MAX(t1.enddt)) + 1 as DaysToReceive
from (
select distinct cp1.dt, min(cp2.dt) enddt
from ( select StartDate as dt, Id from TableA ) cp1,
( select EndDate as dt from TableA ) cp2
where cp2.dt > cp1.dt cp1.Id = cp2.Id
group by cp1.dt
) t1, TableA t2
where t2.StartDate between t1.dt and t1.enddt
group by t1.dt, t1.enddt
Thanks in advance.
Cheers
Check this
Select [name], Case when [InRange] = 1
then Max(DateDiff(dd, MinStartdate, MaxEnddate) + 1)
Else
Sum(DateDiff(dd, Startdate, Enddate) + 1)
End as [Days]
from
(
Select Distinct a.[name], StartDate, EndDate, MinStartdate, MaxEnddate,
Case when StartDate > MinStartdate and EndDate < MaxEnddate or
(StartDate = MinStartdate and EndDate = MaxEnddate) then
1 Else 0
End as [InRange]
from
(
SELECT [name],
Min(StartDate) AS MinStartdate, Max(EndDate) AS MaxEnddate
FROM A
Group By [Name]
) Q
inner join A a
on a.[name] = q.[name]
) QQ
and here is the fiddle

Joining a record from a single column date range

I have two tables:
Person
+---------+-----------+
| Name | Added |
+---------+-----------+
| Roger | 2/1/2001 |
| Natalie | 5/5/2001 |
| George | 6/6/2001 |
| Paul | 12/5/1999 |
+---------+-----------+
Stage
+-------------+----------+
| Description | Start |
+-------------+----------+
| 1 | 1/1/1980 |
| 2 | 4/1/2001 |
| 3 | 6/1/2001 |
+-------------+----------+
I want to join Person with stage such that I get the following result.
Result
+---------+-----------+--------+
| Name | Added | Stage |
+---------+-----------+--------+
| Roger | 2/1/2001 | 1 |
| Natalie | 5/5/2001 | 2 |
| George | 6/6/2001 | 3 |
| Paul | 12/5/1999 | 1 |
+---------+-----------+--------+
So, the stage 1 matches (added >= 1/1/1980 AND added < 4/1/2001), stage 2 matches (added >= 4/1/2001 AND added < 6/1/2001), stage 3 (added >= 6/1/2001) etc... This works, but I think it's kind of ugly (and only happens to work because the description is sequential as well).
SELECT person.name,
person.added,
(SELECT MAX(description) FROM stage d2 WHERE person.added >= d2.start) description
FROM person
Is there a way to do this in a regular join, and if description were a string rather than a sequential number? Thanks.
Instead of a subquery, you could use row_number():
select name, added, description
from (
select p.name, p.added, s.description
, row_number() over (
partition by p.name
order by s.start desc
) as rn
from person p
inner join stage s
on s.start <= p.added
) t
where rn = 1
test setup: http://rextester.com/SIAUAZ29747
with Person (Name,Added_date) as (
select 'Roger' , to_date('2001-02-01','yyyy-mm-dd') from dual union all
select 'Natalie' , to_date('2001-05-05','yyyy-mm-dd') from dual union all
select 'George' , to_date('2001-06-06','yyyy-mm-dd') from dual union all
select 'Paul' , to_date('1999-12-05','yyyy-mm-dd') from dual
),
Stage ( Description , Start_date ) as (
select 1, to_date('1980-01-01','yyyy-mm-dd') from dual union all
select 2, to_date('2001-04-01','yyyy-mm-dd') from dual union all
select 3, to_date('2001-06-01','yyyy-mm-dd') from dual
)
select name, to_char(added_date,'yyyy-mm-dd') added, description
from (
select p.name, p.added_date, s.description
, row_number() over (
partition by p.name
order by s.start_date desc
) as rn
from person p
inner join stage s
on s.start_date <= p.added_date
) t
where rn = 1
order by added_date
returns:
+---------+------------+-------------+
| NAME | ADDED | DESCRIPTION |
+---------+------------+-------------+
| Paul | 1999-12-05 | 1 |
| Roger | 2001-02-01 | 1 |
| Natalie | 2001-05-05 | 2 |
| George | 2001-06-06 | 3 |
+---------+------------+-------------+
Problems of this type can often be solved with no joins at all. Instead, combine the two tables (as illustrated below) with UNION ALL and use the LAST_VALUE() function:
select name, added, description
from (
select name, added,
last_value(description ignore nulls)
over (order by added, description) as description
from ( select name, null as description, added
from person
union all
select null, description, start_date
from stage
)
)
where name is not null
order by added, name -- if needed
;
NAME ADDED DESCRIPTION
------- ---------- -----------
Paul 12/05/1999 1
Roger 02/01/2001 1
Natalie 05/05/2001 2
George 06/06/2001 3
Big THANK YOU to #MT0 for providing the setup (CREATE TABLE statements).
Here is a version that joins the rows in Person to Stage with a 1:1 correspondence (unlike the accepted solution which will join Person to multiple rows in Stage and then have to filter out the unwanted rows):
Oracle Setup:
CREATE TABLE Person (Name,Added) AS
SELECT 'Roger' , DATE '2001-02-01' FROM DUAL UNION ALL
SELECT 'Natalie' , DATE '2001-05-05' FROM DUAL UNION ALL
SELECT 'George' , DATE '2001-06-06' FROM DUAL UNION ALL
SELECT 'Paul' , DATE '1999-12-05' FROM DUAL;
CREATE TABLE Stage ( Description , Start_date ) AS
SELECT 1, DATE '1980-01-01' FROM DUAL UNION ALL
SELECT 2, DATE '2001-04-01' FROM DUAL UNION ALL
SELECT 3, DATE '2001-06-01' FROM DUAL;
Query:
SELECT name, added, description
FROM person p
INNER JOIN
(
SELECT description,
start_date,
LEAD( start_date ) OVER ( ORDER BY start_date ) AS end_date
FROM stage
) s
ON ( s.start_date <= p.added AND ( s.end_date IS NULL OR p.added < s.end_date ) );
Output:
NAME ADDED DESCRIPTION
------- ------------------- -----------
Paul 1999-12-05 00:00:00 1
Roger 2001-02-01 00:00:00 1
Natalie 2001-05-05 00:00:00 2
George 2001-06-06 00:00:00 3

Expand table rows based on column value

I have a table with two columns; EVENT_DATE date and RANG number the first column holds a date for an event while the second column is for the period of that event. here is a sample of data
| EVENT_DATE | RANG |
|------------|------|
| 03/01/2015 | 1 |
| 09/04/2015 | 3 |
| 15/10/2015 | 2 |
is there any way to expand the EVENT_DATE by increment it based on the RANG value, so the output will be like,
| EVENT_DATE |
|------------|
| 03/01/2015 |
| 04/01/2015 |
| 09/04/2015 |
| 10/04/2015 |
| 11/04/2015 |
| 12/04/2015 |
| 15/10/2015 |
| 16/10/2015 |
| 17/10/2015 |
Here you go.
select to_char(event_date + (l - 1),'dd/mm/yyyy') from tab1 t
left outer join (
select level l from dual
connect by level <= (select max(rang) + 1 from tab1)
) on l <= rang + 1
order by event_date, 1;
SQL Fiddle
This should work:
select (t.event_date + t2.value) as event_date
from t, (select rownum -1 as value from all_objects) t2
where t2.value <= t.rang
order by 1 asc;
Another possible answer. Assume Event1 is your table
with tmp as
(select MAX(RANG) s FROM Event1)
,rec as (
select 0 num
union all
select num+1 from rec where num < (select s from tmp))
SELECT DATEADD(DAY, r.num, e.Event_Date) Result from rec r join Event1 e on r.num <= e.RANG
ORDER BY DATEADD(DAY, r.num, e.Event_Date)

In MySQL: fetching rows distinct by year

I have a MySQL table similar to this:
| id | name | create_date |
---------------------------
| 1 | foo | 2003-03-11 |
| 2 | goo | 2003-04-27 |
| 3 | woo | 2004-10-07 |
| 4 | too | 2004-12-01 |
| 5 | hoo | 2005-04-20 |
| 6 | koo | 2006-01-12 |
| 7 | boo | 2006-04-17 |
| 8 | moo | 2006-08-19 |
I want to fetch all the latest yearly rows - one per year. So in the example above I'll get 2, 4, 5 and 8.
What's the right syntax?
Some of the other answers may work for you but this simple query does not require any joins
SELECT YEAR(create_date),
(SELECT id ORDER BY create_date DESC LIMIT 1)
FROM mytable
group by YEAR(create_date)
you can do something like
select * from table_name
where create_date in (
select max(create_date)
from table_name
group by year(create_date))
SELECT id FROM foo JOIN
(SELECT YEAR(create_date),MAX(create_date) AS md
FROM foo
GROUP BY YEAR(create_date)) as maxes
ON (create_date=md);
If you put an index on create_date, this will be fairly fast.
SELECT mi.*
FROM (
SELECT DISTINCT YEAR(created_date) AS dyear
FROM mytable
) md
JOIN mytable mi
ON mi.id =
(
SELECT id
FROM mytable ml
WHERE ml.create_date < CAST(CONCAT_WS('.', dyear + 1, 1, 1)) AS DATETIME)
ORDER BY
ml.create_date DESC
LIMIT 1
)
select id
from mytable
where not exists (
select * from mytable as T2
where T2.id = mytable.id
and T2.id >= year(created_date) + 1
)