Select record by given date from and date till. - sql

I having the records as below
StartDate | EndDate | ID
---------------------------------
25-12-2016 30-12-2016 0
01-01-2017 05-01-2017 1
10-01-2017 12-01-2017 2
01-02-2017 05-02-2017 3
By given selecting the Date Range from 02-01-2017 till 11-01-2017 , How do we select the record Startdate n EndDate that is fall on between the Date Range given as expected?
Would like to expect table result as below
StartDate | EndDate | ID
------------------------------
01-01-2017 05-01-2017 1
10-01-2017 12-01-2017 2

So, basically you are asking how to check if two date ranges overlap.
The way to do this is to check that one starts before the other ends, while the other starts before one ends. You can see a visualization in the overlap tag wiki.
Your query should be something like this:
SELECT StartDate, EndDate, ID
FROM YourTable
WHERE StartDate <= '11-01-2017'
AND EndDate >= '02-01-2017'

Try the below query,
DECLARE #V_START_DATE DATETIME = '2017-01-02'
,#V_END_DATE DATETIME = '2017-01-11'
SELECT *
FROM #TABLE
WHERE StartDate BETWEEN #V_START_DATE AND #V_END_DATE
OR EndDate BETWEEN #V_START_DATE AND #V_END_DATE

Try the below Query
SELECT * FROM DateRanges
WHERE StartDate BETWEEN '02-01-2017' and '11-01-2017'
OR ENDdate BETWEEN '02-01-2017' and '11-01-2017'

Related

SQL: Merge Overlapping Date Ranges

I have two tables both containing date ranges, the first table contains the default record ID that applies during the date range:
STARTDATE | ENDDATE | RECORDID
__________________________________________________
2022/Nov/01 00:00 | 2022/Nov/30 00:00 | 10
2022/Dec/01 00:00 | 2022/Dec/31 00:00 | 16
The second table contains the override record id that overrides the default record ID for the date range specified if present, if there isnt an override for the date in question then the default record represents that date range:
STARTDATE | ENDDATE | RECORDID
__________________________________________________
2022/Nov/14 00:00 | 2022/Nov/16 00:00 | 12
2022/Dec/06 00:00 | 2022/Dec/20 00:00 | 18
The override table records always fall within the dates covered by dates in the default table.
The end result for the above scenario should look like this:
STARTDATE | ENDDATE | RECORDID
__________________________________________________
2022/Nov/01 00:00 | 2022/Nov/14 00:00 | 10
2022/Nov/14 00:00 | 2022/Nov/16 00:00 | 12
2022/Nov/16 00:00 | 2022/Nov/30 00:00 | 10
2022/Dec/01 00:00 | 2022/Dec/06 00:00 | 16
2022/Dec/06 00:00 | 2022/Dec/20 00:00 | 18
2022/Dec/20 00:00 | 2022/Dec/31 00:00 | 16
Is there a way to do this with SQL Server 2019? I made an attempt but it was filled with clumsy loops and the end result was incorrect and would not have been efficient at all.
Try the following query, maybe there is a simpler way to do it, but the following works.
/*
left join the two tables to get the overlapped date ranges.
the left join is used in case there is no default date range
in the default table that overlapped with the override table date range.
*/
with overlapped_ranges as
(
select D.StartDate def_stdt, D.EndDate def_endt, D.RecordId def_rid,
O.StartDate ovr_stdt, O.EndDate ovr_endt, O.RecordId ovr_rid
from override_records O left join default_records D
on O.StartDate <= D.EndDate and O.EndDate >= D.StartDate
),
/*
from the joined tables, get all of the date range boundaries as values
using cross-apply. you could run (select * from get_all_ranges_boundaries)
to see the output of this query.
*/
get_all_ranges_boundaries as
(
select OV.*, dt
from overlapped_ranges OV
cross apply
(
select *
from (values (def_stdt), (def_endt), (ovr_stdt), (ovr_endt)) as T(dt)
) all_dates_boundaries
),
/*
now we have all the date range boundaries, so the start date, end date values
will be the date boundary as the start date and the next date boundary as the end date.
i.e.
Values('2022-11-01','2022-11-30','2022-11-14','2022-11-16') will be
('2022-11-01'-'2022-11-14'),('2022-11-14'-'2022-11-16'),('2022-11-16'-'2022-11-30')
*/
stDate_enDate as
(
select dt StartDate,
lead(dt) over (partition by def_stdt, def_endt, ovr_stdt, ovr_endt order by dt) EndDate,
case
when dt >= ovr_stdt and lead(dt) over (partition by def_stdt, def_endt, ovr_stdt, ovr_endt order by dt) <= ovr_endt
then ovr_rid else def_rid
end as RecordId -- check if the populated date range occurs within the override date range, if yes select override id, else select default id.
from get_all_ranges_boundaries
)
select StartDate, EndDate, RecordId
from stDate_enDate
where EndDate is not null and -- the last date range boundary has no lead value, this is to exclude the last row for each overlapped pair.
StartDate is not null and -- exclude null values where an override range does not overlap with default ranges. (from the left join query)
StartDate <> EndDate -- exclude rows where StartDate = EndDate i.e. ('2022-11-01',**'2022-11-05','2022-11-05'**,'2022-11-16')
order by StartDate, EndDate
See demo.

Reporting on time information using start and end time

Is it possible to create a report that sums hours for a day grouped by an Id using a start and end time stamp?
I need to be able to split time that spans days and take part of that time and sum to the correct date group.
NOTE: The date ids are to a date dimension table.
------------------------------------------------------------------------------
TaskId | StartDateId | EndDateId | StartTime | EndTime
------------------------------------------------------------------------------
2 | 20190317 | 20190318 | 2019-03-17 16:30:00 | 2019-03-18 09:00:00
------------------------------------------------------------------------------
1 | 20190318 | 20190318 | 2019-03-18 09:00:00 | 2019-03-18 16:30:00
------------------------------------------------------------------------------
2 | 20190318 | 20190319 | 2019-03-18 16:30:00 | 2019-03-19 09:00:00
------------------------------------------------------------------------------
So based on this, the desired report output would be:
-------------------------
Date | Task | Hours
-------------------------
2019-03-17 | 2 | 7.5
-------------------------
2019-03-18 | 1 | 7.5
-------------------------
2019-03-18 | 2 | 16.5
-------------------------
...
The only working solution I have managed to implement is splitting records so that no record spans multiple days. I was hoping to find a report query solution, rather than an ETL base based solution.
I have tried to simulate your problem here: https://rextester.com/DEV45608 and I hope it helps you :) (The CTE GetDates can be replaced by your date dimension)
DECLARE #minDate DATE
DECLARE #maxDate DATE
CREATE TABLE Tasktime
(
Task_id INT,
Start_time DATETIME,
End_time DATETIME
);
INSERT INTO Tasktime VALUES
(2,'2019-03-17 16:30:00','2019-03-18 09:00:00'),
(1,'2019-03-18 09:00:00','2019-03-18 16:30:00'),
(2,'2019-03-18 16:30:00','2019-03-19 09:00:00');
SELECT #mindate = MIN(Start_time) FROM Tasktime;
SELECT #maxdate = MAX(End_time) FROM Tasktime;
;WITH GetDates AS
(
SELECT 1 AS counter, #minDate as Date
UNION ALL
SELECT counter + 1, DATEADD(day,counter,#minDate)
from GetDates
WHERE DATEADD(day, counter, #minDate) <= #maxDate
)
SELECT counter, Date INTO #tmp FROM GetDates;
SELECT
g.Date,
t.Task_id,
SUM(
CASE WHEN CAST(t.Start_time AS DATE) = CAST(t.End_time AS DATE) THEN
DATEDIFF(second, t.Start_time, t.End_time) / 3600.0
WHEN CAST(t.Start_time AS DATE) = g.Date THEN
DATEDIFF(second, t.Start_time, CAST(DATEADD(day,1,g.Date) AS DATETIME)) / 3600.0
WHEN CAST(t.End_time AS DATE) = g.Date THEN
DATEDIFF(second, CAST(g.Date AS DATETIME), t.End_time) / 3600.0
ELSE
24.0
END) AS hours_on_the_day_for_the_task
from
#tmp g
INNER JOIN
Tasktime t
ON
g.Date BETWEEN CAST(t.Start_time AS DATE) AND CAST(t.End_time AS DATE)
GROUP BY g.Date, t.Task_id
The Desired Date can be joined to the date dimension and return the "calendar date" and you can show that date in the report.
As for the HOURS.. when you are retrieving your dataset in SQL, just do this.. it is as simple as:
cast(datediff(MINUTE,'2019-03-18 16:30:00','2019-03-19 09:00:00') /60.0 as decimal(13,1)) as 'Hours'
So in your case it would be
cast(datediff(MINUTE,sometable.startdate,sometable.enddate) /60.0 as decimal(13,1)) as 'Hours'
Just doing a HOUR will return the whole hour.. and dividing by 60 will return a whole number. Hence the /60.0 and the cast

Discard existing dates that are included in the result, SQL Server

In my database I have a Reservation table and it has three columns Initial Day, Last Day and the House Id.
I want to count the total days and omit those who are repeated, for example:
+-------------+------------+------------+
| | Results | |
+-------------+------------+------------+
| House Id | InitialDay | LastDay |
+-------------+------------+------------+
| 1 | 2017-09-18 | 2017-09-20 |
| 1 | 2017-09-18 | 2017-09-22 |
| 19 | 2017-09-18 | 2017-09-22 |
| 20 | 2017-09-18 | 2017-09-22 |
+-------------+------------+------------+
If you noticed the House Id with the number 1 has two rows, and each row has dates but the first row is in the interval of dates of the second row. In total the number of days should be 5 because the first shouldn't be counted as those days already exist in the second.
The reason why this is happening is that each house has two rooms, and different persons can stay in that house on the same dates.
My question is: how can I omit those cases, and only count the real days the house was occupied?
In your are using SQL Server 2012 or higher you can use LAG() to get the previous final date and adjust the initial date:
with ReservationAdjusted as (
select *,
lag(LastDay) over(partition by HouseID order by InitialDay, LastDay) as PreviousLast
from Reservation
)
select HouseId,
sum(case when PreviousLast>LastDay then 0 -- fully contained in the previous reservation
when PreviousLast>=InitialDay then datediff(day,PreviousLast,LastDay) -- overlap
else datediff(day,InitialDay,LastDay)+1 -- no overlap
end) as Days
from ReservationAdjusted
group by HouseId
The cases are:
The reservation is fully included in the previous reservation: we only need to compare end dates because the previous row is obtained ordering by InitialDay, LastDay, so the previous start date is always minor or equal than the current start date.
The current reservation overlaps with the previous: in this case we adjust the start and don't add 1 (the initial day is already counted), this case include when the previous end is equal to the current start (is a one day overlap).
There is no overlap: we just calculate the difference and add 1 to count also the initial day.
Note that we don't need extra condition for the reservation of a HouseID because by default the LAG() function returns NULL when there isn't a previous row, and comparisons with null always are false.
Sample input and output:
| HouseId | InitialDay | LastDay |
|---------|------------|------------|
| 1 | 2017-09-18 | 2017-09-20 |
| 1 | 2017-09-18 | 2017-09-22 |
| 1 | 2017-09-21 | 2017-09-22 |
| 19 | 2017-09-18 | 2017-09-27 |
| 19 | 2017-09-24 | 2017-09-26 |
| 19 | 2017-09-29 | 2017-09-30 |
| 20 | 2017-09-19 | 2017-09-22 |
| 20 | 2017-09-22 | 2017-09-26 |
| 20 | 2017-09-24 | 2017-09-27 |
| HouseId | Days |
|---------|------|
| 1 | 5 |
| 19 | 12 |
| 20 | 9 |
select house_id,min(initialDay),max(LastDay)
group by houseId
If I understood correctly!
Try out and let me know how it works out for you.
Ted.
While thinking through your question I came across the wonder that is the idea of a Calendar table. You'd use this code to create one, with whatever range of dates your want for your calendar. Code is from http://blog.jontav.com/post/9380766884/calendar-tables-are-incredibly-useful-in-sql
declare #start_dt as date = '1/1/2010';
declare #end_dt as date = '1/1/2020';
declare #dates as table (
date_id date primary key,
date_year smallint,
date_month tinyint,
date_day tinyint,
weekday_id tinyint,
weekday_nm varchar(10),
month_nm varchar(10),
day_of_year smallint,
quarter_id tinyint,
first_day_of_month date,
last_day_of_month date,
start_dts datetime,
end_dts datetime
)
while #start_dt < #end_dt
begin
insert into #dates(
date_id, date_year, date_month, date_day,
weekday_id, weekday_nm, month_nm, day_of_year, quarter_id,
first_day_of_month, last_day_of_month,
start_dts, end_dts
)
values(
#start_dt, year(#start_dt), month(#start_dt), day(#start_dt),
datepart(weekday, #start_dt), datename(weekday, #start_dt), datename(month, #start_dt), datepart(dayofyear, #start_dt), datepart(quarter, #start_dt),
dateadd(day,-(day(#start_dt)-1),#start_dt), dateadd(day,-(day(dateadd(month,1,#start_dt))),dateadd(month,1,#start_dt)),
cast(#start_dt as datetime), dateadd(second,-1,cast(dateadd(day, 1, #start_dt) as datetime))
)
set #start_dt = dateadd(day, 1, #start_dt)
end
select *
into Calendar
from #dates
Once you have a calendar table your query is as simple as:
select distinct t.House_id, c.date_id
from Reservation as r
inner join Calendar as c
on
c.date_id >= r.InitialDay
and c.date_id <= r.LastDay
Which gives you a row for each unique day each room was occupied. If you need a sum of how many days each room was occupied it becomes:
select a.House_id, count(a.House_id) as Days_occupied
from
(select distinct t.House_id, c.date_id
from so_test as t
inner join Calendar as c
on
c.date_id >= t.InitialDay
and c.date_id <= t.LastDay) as a
group by a.House_id
Create a table of all the possible dates and then join it to the Reservations table so that you have a list of all days between InitialDay and LastDay. Like this:
DECLARE #i date
DECLARE #last date
CREATE TABLE #temp (Date date)
SELECT #i = MIN(Date) FROM Reservations
SELECT #last = MAX(Date) FROM Reservations
WHILE #i <= #last
BEGIN
INSERT INTO #temp VALUES(#i)
SET #i = DATEADD(day, 1, #i)
END
SELECT HouseID, COUNT(*) FROM
(
SELECT DISTINCT HouseID, Date FROM Reservation
LEFT JOIN #temp
ON Reservation.InitialDay <= #temp.Date
AND Reservation.LastDay >= #temp.Date
) AS a
GROUP BY HouseID
DROP TABLE #temp

SQL query min startdate max enddate across multiple rows

SQL Server 2008 R2
I have data like this:
+---------+--------+-------------+-------------+
| SchedId | AdId | StartDate | EndDate |
+---------+--------+-------------+-------------+
| 335779 | 179911 | 2017-01-04 | 2017-01-04 |
| 335780 | 179911 | 2017-01-05 | 2017-01-05 |
| ... | | | |
| 335802 | 179911 | 2017-01-31 | 2017-01-31 |
+---------+--------+-------------+-------------+
Across all the records that have an AdId of 179911, I need the minimum StartDate (2017-01-04)and the maximum EndDate (2017-01-31) for a specific date of the month.
I would like to place the data I need for a specific date into a temp table. I tried this:
declare #dt datetime
set #dt = '2017-01-19'
SELECT
MIN(dbo.aoadrundates.StartDate) AS MinStartDate,
MAX(dbo.aoadrundates.EndDate) AS MaxEndDate,
dbo.aoadrundates.AdId AS T_AdId
FROM
dbo.aoadrundates
WHERE
StartDate >= #dt AND EndDate <= #dt
GROUP BY
dbo.AoAdRunDates.AdId,
dbo.aoadrundates.StartDate,
dbo.aoadrundates.EndDate
but I only get the single record with the date I'm selecting, e.g.,
MinStartDate MaxEndDate T_AdId
2017-01-19 2017-01-19 179911
I tried a few examples I found that use cast, min, max as well as a subquery with an inner join, but these have not worked (I think) because I need both min from 1 row and max from another.
Please, if you don't really want to help me, that's fine. If you can help, I would be most grateful. Thank you
So the problem is that you're grouping by the fields you want to aggregate by - dbo.aoadrundates.StartDate and dbo.aoadrundates.EndDate. Assuming you're wanting to group by AdId you can use:
declare #dt datetime
set #dt = '2017-01-19'
SELECT min(dbo.aoadrundates.StartDate) as MinStartDate,
max(dbo.aoadrundates.EndDate) as MaxEndDate,
dbo.aoadrundates.AdId
from
dbo.aoadrundates
where StartDate >= #dt and EndDate <= #dt
group by
dbo.AoAdRunDates.AdId
From the comment "Across all the records" though you might want it repeated for every record with that AdId, in which case you're going to have to use a CTE or similar (in 2012+ there are better ways...):
declare #dt datetime
set #dt = '2017-01-19'
;with cte_MinAndMax as (
SELECT min(dbo.aoadrundates.StartDate) as MinStartDate,
max(dbo.aoadrundates.EndDate) as MaxEndDate,
dbo.aoadrundates.AdId
from
dbo.aoadrundates
where StartDate >= #dt and EndDate <= #dt
group by
dbo.AoAdRunDates.AdId
)
select
dbo.aoadrundates.AdId as T_AdId,
cte_MinAndMax.MinStartDate,
cte_MinAndMax.MaxEndDate,
dbo.aoadrundates.whatever_else_you_want_to_select
from dbo.aoadrundates
left join cte_MinAndMax on dbo.aoadrundates.AdId = cte_MinAndMax.AdId

SQL WHERE query on date range

In my table I have 118 records detailing projects. The 2 fields I am concerned with here are startdate and enddate.
I need to produce a report from this view which shows which projects were 'active' between the following date ranges:
01/01/2011 - 01/12/2011
I have tried the following WHERE clase:
WHERE startdate BETWEEN '01/04/2011' AND '01/12/2011'
OR enddate BETWEEN '01/04/2011' AND '01/12/2011'
OR startdate <= '01/04/2011' AND enddate >= '01/12/2011'
What comes through does not seem correct, there are only a few records displayed and many which I know for a fact should be displayed are not, such as one project with a start date of 20/07/2011 and enddate of 21/11/2011 dissapears when the WHERE query is run.
Can anyone see a fault with this WHERE query
WHERE
startdate <= '2011-12-01'
AND enddate >= '2011-01-01'
(Assuming the value in enddate is the last date the project is active)
Examples using numbers, searching for anything that overlaps 100 to 200...
Start | End | Start <= 200 | End >= 100
000 | 099 | Yes | No
101 | 199 | Yes | Yes (HIT!)
201 | 299 | No | Yes
000 | 150 | Yes | Yes (HIT!)
150 | 300 | Yes | Yes (HIT!)
000 | 300 | Yes | Yes (HIT!)
This absolutely needs an AND in the logic :)
In terms of your query...
Your query with parenthesis, looks like this...
WHERE
(
startdate BETWEEN '01/04/2011' AND '01/12/2011'
OR enddate BETWEEN '01/04/2011' AND '01/12/2011'
OR startdate <= '01/04/2011'
)
AND enddate >= '01/12/2011'
But your example never meets the last AND condition. Try adding parenthesis to be more explicit...
WHERE
(startdate BETWEEN '01/04/2011' AND '01/12/2011')
OR (enddate BETWEEN '01/04/2011' AND '01/12/2011')
OR (startdate <= '01/04/2011' AND enddate >= '01/12/2011')
Before the query add
set dateformat dmy
Also maybe add some brackets
WHERE
(startdate BETWEEN '01/01/2011' AND '01/12/2011')
OR
(enddate BETWEEN '01/01/2011' AND '01/12/2011')
OR
(startdate <= '01/01/2011' AND enddate >= '01/12/2011')
Assuming startdate and enddate are date fields,
Try this:
WITH Dates AS (
SELECT [Date] = #StartDate
UNION ALL SELECT [Date] = DATEADD(DAY, 1, [Date])
FROM Dates WHERE [Date] <= DATEADD(DAY, -1, #EndDate)
)
-- YOUR SELECT STATEMENT
-- YOUR FROM STATEMENT
CROSS APPLY Dates
WHERE [Date] BETWEEN startdate AND enddate
-- The rest of your where statement here
OPTION(MAXRECURSION 0);
Declaring your start date as 01/01/2011 and your end date as 01/12/2011
Everyone's looking at this the wrong way round comparing startdate and enddate to a string when you can compare the string to the columns; the following is the simplest way of ascertaining what you want:
where ( '01/04/2011' between startdate and enddate
or '01/12/2011' between startdate and enddate
)
My original query was working, the database I was connecting to however had different date formats to my query.