MSSQL Check If From, To Date will fit between time range - sql

I need a function in MSSQL Server which as parameters will get #startTime #endTime and will check table reservation (which also has startTime and endTime) if asked date range will fit between time range within table and Return 1 If Yes and 0 If no. A little bit confusing I will explain better on table (At least I hope so)
I have table:
|:-----------------------|----------------------:|
| startDate | endDate |
|:-----------------------|----------------------:|
| 2017-01-25 00:00:00.000|2017-01-25 12:00:00.000|
| 2017-01-25 13:00:00.000|2017-01-25 14:00:00.000|
|:-----------------------|----------------------:|
Need to check If reservation will be avaible for i.e:
#startTime = 2017-01-25 13:30:00.000
#endTime = 2017-01-25 15:00:00.000
And should return 0 because there is reservation in this period of time.
I've tried do this by #startTime > startDate and #endTime < endDate but condition is check for every row and I need check whole table.
Kind Regards

You can do something like this:
select (case when exists (select 1
from reservations r
where r.startDate <= #endTime and
r.endDate >= #startTime
)
then 0 else 1
end) as available;
The logic is simple. Two time periods overlap if the first starts before the second ends and the first ends after the second starts.

create table info(startdate datetime, enddate datetime);
insert into info values
('2017-01-01', '2017-01-05'),
('2017-01-03', '2017-01-06'),
('2017-01-01', '2017-01-15'),
('2017-01-02', '2017-01-13'),
('2017-01-12', '2017-01-18');
declare #StartDate datetime = '2017-01-03';
declare #EndDate datetime = '2017-01-04'
select *
from info
where startdate <= #StartDate and enddate >= #EndDate;
+----+---------------------+---------------------+
| | startdate | enddate |
+----+---------------------+---------------------+
| 1 | 01.01.2017 00:00:00 | 05.01.2017 00:00:00 |
+----+---------------------+---------------------+
| 2 | 03.01.2017 00:00:00 | 06.01.2017 00:00:00 |
+----+---------------------+---------------------+
| 3 | 01.01.2017 00:00:00 | 15.01.2017 00:00:00 |
+----+---------------------+---------------------+
| 4 | 02.01.2017 00:00:00 | 13.01.2017 00:00:00 |
+----+---------------------+---------------------+

Related

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

how to get data when using where clause of time stamp in select query

I’m trying to get date with select query that have time stamp in WHERE clause.
my data table look like this:
| startTime | endTime | TimeID
----------------------------------
| 07:00:00 | 15:00:00 | 1
| 15:00:00 | 23:00:00 | 2
| 23:00:00 | 07:00:00 | 3
---------------------------------
this is my query statement:
SELECT TimeID
FROM myTable
WHERE StartTime >= 'Current_TIME' AND EndTime < 'Current_TIME'
if the time is somewhere between 07:00 and 23:00 then I get the answer correct, else I don't.
for example:
if the current time is 02:00:00 then the first condition is false because 02 is not larger then 23 and second condition is valid > 02 is smaller then 07
I try to use BETWEEN clause and use CASE WHEN and ISNULL but the query always returns empty in the scenario above.
This will work, but it's not the best in performance wise:
SELECT TimeID
FROM myTable
WHERE
(
(StartTime <= 'Current_TIME' AND EndTime > 'Current_TIME') or
(StartTime <= 'Current_TIME' AND StartTime > EndTime) or
(EndTime > 'Current_TIME' AND StartTime > EndTime)
)
It would probably be better to split the row that spans midnight into 2 separate rows, if possible.

SQL Server 2008 R2 - Select Case When date between dates

I have a table Like this :
---------------------------------------------------------------
| UserID | Amount | PayDate |TransactionType| ...
----------------------------------------------------------------
| 1 | 140 | 2014-09-30 22:00:00.000| 7 |
| 2 | 230 | 2014-09-30 22:00:00.000| 7 |
| 1 | 120 | 2014-08-01 22:00:00.000| 7 |
| 2 | 135 | 2014-07-30 22:00:00.000| 7 |
| 1 | 120 | 2014-09-30 22:00:00.000| 4 |
----------------------------------------------------------------
I wrote the below query but it returns NULL, Please advise on this query as is:
The declared below dates are between 29/09/2014 and 1/10/2014
Declare
#dateStart datetime= CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(GETUTCDATE())+2),GETUTCDATE()),101),
#dateEnd datetime=(CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(GETUTCDATE())-1),GETUTCDATE()),101))
Select
MemberID,
case
when transactionType = 7
and (PayDate between #dateStart and #dateEnd) then Amount
End AS 'Outstanding Amount'
from
MemberPayment
My output should be :
| MemberID | OutStanding Amount|
---------------------------------
| 1 | 140 |
| 2 | 230 |
but the query returns null, what am I doing wrong ? Is the CASE When DATE between DATES used correct in SQL Server 2008 R2 ?
PS: Please note I do not want to change the query to have WHERE Condition.
Thank you in advance stack overflow family.
This should do the work
Declare
#dateStart datetime= DATEADD(dd,-(DAY(GETUTCDATE())+2),GETUTCDATE()),
#dateEnd datetime=DATEADD(dd,-(DAY(GETUTCDATE())-1),GETUTCDATE())
select MemberID, [Outstanding Amount]
from
(
Select
UserID as MemberID,
case
when transactionType = 7
and (PayDate between #dateStart and #dateEnd) then Amount
End AS 'Outstanding Amount'
from
MemberPayment
) As TmpQuery
where [Outstanding Amount] is not null
I removed the convert to varchar from both of your variables.
Then i put a select around your query, to filter just the results with Oustanding Amount not NULL.
Please take note, that I selected UserID as MemberID, because u used UserID in your example.
I tested it with a table, where PayDate is a Datetime Column.
As already mentioned in one of your comments i would prefer the easy method (and it`s much faster!):
Declare
#dateStart datetime= DATEADD(dd,-(DAY(GETUTCDATE())+2),GETUTCDATE()),
#dateEnd datetime=DATEADD(dd,-(DAY(GETUTCDATE())-1),GETUTCDATE())
select UserID, Amount as [Outstanding Amount]
from MemberPayment
where TransactionType = '7'
and PayDate between #dateStart and #dateEnd

Calculate Date Range count for each day SQL Server 2012

I'm having trouble with getting the records for the following
| DATEFROM | DATETO
| 2012-01-02 | 2012-01-03
| 2012-01-11 | 2012-01-16
| 2012-01-08 | 2012-01-22
| 2012-01-29 | 2012-01-30
| 2012-01-08 | 2012-01-11
I'm trying to get count of ranges containing the day for each day from beginning of first range ending last date of last range.
Sample output:
2012-01-02 | 1
2012-01-03 | 1
2012-01-08 | 2
2012-01-09 | 2
2012-01-10 | 2
2012-01-11 | 3
2012-01-12 | 2
2012-01-13 | 2
2012-01-14 | 2
2012-01-15 | 2
2012-01-16 | 2
......
My database contains data from 2008 to nowadays.
In other words I am trying to get how many times a record is found for a specific date.
Not every day is in the TABLE for each month
I found this post Tricky mysql count occurrences of each day within date range but can't convert the code provided to my SQL Server 2012.
You could try here
http://sqlfiddle.com/#!6/0855b/1
Ok, this is one way to do what you want:
DECLARE #MinDate DATE, #MaxDate DATE;
SELECT #MinDate = MIN(DATEFROM),
#MaxDate = MAX(DATETO)
FROM ENTRIES;
WITH Dates AS
(
SELECT DATEADD(DAY,number,#MinDate) [Date]
FROM master.dbo.spt_values
WHERE type = 'P'
AND number > 0
AND DATEADD(DAY,number,#MinDate) <= #MaxDate
)
SELECT A.[Date],
COUNT(*) N
FROM Dates A
LEFT JOIN Entries B
ON A.[Date] >= B.DATEFROM
AND A.[Date] <= B.DATETO
GROUP BY A.[Date]
ORDER BY A.[Date]
If the range dates is over 2047 days, then you'll need to create more values than the ones that are available in master.dbo.spt_values (this is trivial, for instance you can use a CROSS JOIN).
Here is the sqlfiddle for you to try.