How can I find the time difference in two dates? - sql

I am trying to use the DATEDIFF() function to find the difference between two dates within a table. The problem I am having is the understanding how to subtract the time from the most recent date in the table VS the starting date.
Dates are in the format: YYYY-MM-DD HH:MM:SS
I have tried this:
select FileName, '20:00' as StartTime, ModifiedDate, DATEDIFF(MINUTE,
'20:00', ModifiedDate) as 'BackupTime'
from BackLogData
But it returns the minutes from the start time.
Here is a sample of the table:
+-----------+-----------------------------+------------+
| StartTime | ModifiedDate | BackupTime |
+-----------+-----------------------------+------------+
| 20:00 | 2019-06-10 01:04:17.3692999 | 62817424 |
| 20:00 | 2019-06-10 00:53:23.4900986 | 62817413 |
| 20:00 | 2019-06-10 00:51:09.2363761 | 62817411 |
+-----------+-----------------------------+------------+
The correct table:
+-----------+-----------------------------+------------+--+
| StartTime | ModifiedDate | BackupTime | |
+-----------+-----------------------------+------------+--+
| 20:00 | 2019-06-10 01:04:17.3692999 | 11 | |
| 20:00 | 2019-06-10 00:53:23.4900986 | 2 | |
| 20:00 | 2019-06-10 00:51:09.2363761 | 291 | |
+-----------+-----------------------------+------------+--+

You can take your difference in minutes and transform it to time datatype using dateadd and cast. Please note that if your difference is bigger then 24 hours then this won't work (time data type stores up to 24 hours).
SELECT FileName, '20:00' AS StartTime, ModifiedDate,
cast(dateadd(minute,DATEDIFF(MINUTE, RecordDate, ModifiedDate),'19000101') as time(0)) AS 'BackupTime'
FROM BackLogData
Example:
SELECT
cast(dateadd(minute,DATEDIFF(MINUTE, '2019-05-05 16:00:00', '2019-05-05 18:00:00'),'19000101') as time(0)) AS 'BackupTime'
Output:
02:00:00

If all you're wanting is the difference of minutes from hour '20:00' compared to the time of ModifiedDate, you have to just compare the time values:
Try:
SELECT [FileName]
, '20:00' AS [StartTime]
, [ModifiedDate]
, DATEDIFF(MINUTE, '20:00', CONVERT(TIME, [ModifiedDate])) AS 'BackupTime' --convert your modified date to time
FROM [BackLogData];
Reason your getting a weird large value is you were trying to basically find the different between 1900-01-01 20:00 and your ModifiedDate.

Marc Guillot was on the right track but i found some issues with his query. Here's a revision:
--this is setup, you don't need this
CREATE TABLE t
([StartTime] time, [ModifiedDate] datetime)
;
INSERT INTO t
([StartTime], [ModifiedDate])
VALUES
('20:00', '2019-06-10 01:04:17'),
('20:00', '2019-06-10 00:53:23'),
('20:00', '2019-06-10 00:51:09')
;
--we now have a table with a TIME column (cast it in the cte if yours is not), a DATETIME
with LOGS as (
select StartTime,
ModifiedDate,
DATEADD(DAY, -1, CAST(CAST(ModifiedDate as DATE) as DATETIME)) as ModifiedMidnightDayBefore,
CAST(StartTime as DateTime) as StartDateTime,
row_number() over (order by ModifiedDate) as num
from t
)
select curr.StartTime,
curr.ModifiedDate,
datediff(minute,
COALESCE(
prev.ModifiedDate,
curr.ModifiedMidnightDayBefore + curr.StartDateTime
),
curr.ModifiedDate) as BackupTime
from
LOGS curr
left join LOGS as prev on prev.num = curr.num - 1
order by curr.num
The LOGS CTE is joined to itself on num = num-1 thereby putting the current row and previous row data together on a row. One row will have no previous data (blank) so when we are doing our datediff, we use coalesce, which is like ISNULL but is supported by all major db vendors. COALESCE returns the first non null argument. It is used to fill in a value if there is no PREVious value for the modified date
DATEDIFF of prev vs current is fairly obvious. The trick is in the logic if ther eis no previous value:
The CTE also casts the modifieddate datetime, to a date, to drop the time component (set it to midnight) and back to a datetime (so it emerges from the dateadd as a datetime). Dateadd subtracts one day from it, so it is midnight on the pervious day, and then we add our start time (8pm) to this. So effectively the min date in the table is converted to midnight, bumped back a day and then has 8pm added, so it becomes "8pm on the day prior to the modified date", and then we can datediff this nicely to 291 minutes

To get the previous time you can join your table with itself. But first I would number the rows on a CTE, so you can now set an easy condition to join each row with the previous row.
This query returns the difference between each ModifiedTime and its previous one (or StartDate on the first row), resulting in the desired result set that you have posted :
declare #StartTime time = convert(time, '20:00');
declare #StartDate datetime = (select convert(datetime, dateadd(day, -1, convert(date, max(ModifiedDate)))) +
convert(datetime, #StartTime)
from BackLogData);
with LOGS as (
select ModifiedDate,
row_number() over (order by ModifiedDate) as num
from BackLogData
)
select #StartTime as StartTime,
LOGS.ModifiedDate,
datediff(minute,
case when LOGS.num = 1 then #StartDate else PREVIOUS.ModifiedDate end,
LOGS.ModifiedDate) as BackupTime
from LOGS
left join LOGS as PREVIOUS on PREVIOUS.num = LOGS.num - 1
order by LOGS.num
PS: As Caius Jard noted, to be able to directly calculate the time difference between ModifiedDate and StartTime, we have to convert StartTime to a datetime using the date part of the last ModifiedDate minus one (meaning it started the day before).

Related

Sql split entries into two entries if EndDateTime is the next day(after midnight)

I have this query:
select
sth.Id,
sth.CreatedDateTime,
EndDateTime = Lead(sth.CreatedDateTime, 1) over (partition by sth.Id order by sth.Id, sth.CreatedDateTime)
from
Sth as sth
order by
sth.Id, sth.CreatedDateTime
Which returns these results:
Id StartDateTime EndDate
--------------------------------------------------------------------
2746743 2019-11-20 14:35:05.5841266 NULL
2746744 2019-11-20 14:35:05.5841266 NULL
3 2018-06-25 23:35:12.2799952 2018-06-26 09:57:27.8943163
13 2018-06-26 09:57:27.8943163 2018-06-26 10:41:19.2973307
I have been asked to update the above query, split the row with Id=3 into two rows.
Meaning: as you can see the record with Id 3 starts at 23:35 and ends the **next day** at 09:57
What I need it to split this record into two.
The first one should be from 23:35 -> 23:59
And the one below should be from 00:00 -> 09:57
If records span for more than one day. Nothing needs to be done. Also the end solution should be able to work for a history table. More than 3 million rows.
So the record should result to sth like this
Id StartDateTime EndDateTime
3 2018-06-25 23:35:12.2799952 2018-06-25 23:59:59.000000
3 2018-06-26 00:00:00.0000000 2018-06-26 09:57:27.8943163
I hope this makes sense!
All other records will yield similar results. There are records that do not need to be splitted.
The result set in your question cannot be a result from the query you have specified (every id would have a null value for the end date). So, I am interpreting the question as handling the situation where the end date is present and one day after the start date.
I would just use a lateral join:
with t as (
select sth.*, CreatedDateTime as StartDateTime,
Lead(sth.CreatedDateTime, 1) over (partition by sth.Id order by sth.Id, sth.CreatedDateTime) as EndDateTime
from Sth as sth
)
select t.id, v.*
from t cross apply
(values (startdatetime,
(case when datediff(day, startdatetime, enddatetime) = 1
then dateadd(second, -1, dateadd(day, 1, convert(datetime, convert(date, startdatetime))))
else enddatetime
end)
),
(dateadd(day, 1, convert(date, startdatetime)),
(case when datediff(day, startdatetime, enddatetime) = 1
then enddatetime
end)
)
) v(startdatetime, enddatetime)
where v.enddatetime is not null;
Here is a db<>fiddle.

Date closest to current date in SQL (prior dates or future dates)

I am running into a bit of an issue with finding out how to look for dates closest to the current date. I looked at this among others:
Get closest date to current date sql
I also looked here: https://bytes.com/topic/sql-server/answers/79502-get-closest-date
I am using MS SQL 2012 and there have been many questions asked about this, so I apologize for bringing it back up. I can't seem to get my query to work.
Basically I have a table as follows:
ITEMNMBR | MINDATE | MAXDATE | CLOSESTDATE
------------------------------------------------
123456 | 2017-10-15 | 2017-11-04 | NULL
654321 | 2017-09-29 | 2017-12-08 | NULL
The current date would be today, '2017-10-03'. We would find for item number '123456' the closest date of purchase is 2017-10-15. For item number '654321' the closest date of purchase is 2017-09-29 as that happened much more recently than how long it will take for the next purchase to take effect (hence I am looking at an absolute value of the difference between the dates). You'll have to forgive me, but the query I am including doesn't include the "CLOSESTDATE" column. I've included it there to let you know that any of my calculations have rendered that column NULL. So here's what I have:
--Lines commented below are not used in the current iteration of the query
--DECLARE #dt DATETIME
--SET #dt = GETDATE()
SELECT
I.ITEMNMBR,
MIN(PDATE1) AS MINDATE,
MAX(PDATE1) AS MAXDATE
FROM dbo.IV00101 I
LEFT OUTER JOIN
(SELECT P.[Item Number],
P.[Req Date] AS PDATE1
FROM dbo.Purchases P
WHERE ((P.[Document Status] = 'Open') AND
(P.[POStat] <> 'Closed') AND
(P.[POStat] <> 'Received')) AND P.[Req Date] >= DATEADD(d, -15,
DATEDIFF(d, 0, GETDATE()))
) AS P ON P.[Item Number]= I.ITEMNMBR
WHERE P.[Item Number] = '123456'
GROUP BY
I.ITEMNMBR
ORDER BY MINDATE DESC
When I run this query, I get the table I outlined previously, minus the "CLOSESTDATE" column. The "CLOSESTDATE" column is what I want to use to display what date of purchase is closest to TODAY. Basically, if a date of purchase happened three days ago and the next date of purchase is a month out, then I want to show the date of purchase from three days ago. Also, the query can be written without using a subquery, but I was using other calculations within the subquery prior to reverting back to the rather simplistic original query. Thus, the query can be written like this:
SELECT
I.ITEMNMBR,
MIN(P.[Req Date]) AS MINDATE,
MAX(P.[Req Date]) AS MAXDATE
FROM dbo.IV00101 I
LEFT OUTER JOIN
Purchases P ON P.[Item Number] = I.ITEMNMBR
WHERE P.[Item Number] = '123456' ((P.[Document Status] = 'Open') AND
(P.[POStat] <> 'Closed') AND
(P.[POStat] <> 'Received')) AND P.[Req Date] >= DATEADD(d, -15,
DATEDIFF(d, 0, GETDATE()))
GROUP BY
I.ITEMNMBR
ORDER BY MINDATE DESC
Lastly, as you can see, I have a date constraint for the past 15 days so that anything older than that won't show up at all.
Many thanks in advance!
I'm still not quite understanding your question, but I hope this gives you a start, you can try the rextester sample here
But what it looks like to me is you need a simple case statement:
select ITEMNMBR
,case when abs(datediff(day, MINDATE, convert(date,getdate()))) > abs(datediff(day, MAXDATE, convert(date,getdate()))) then 'MINDATE is greater' else 'MAXDATE is greater' end as ClosestDate
from myTable
Kindly let me know if you have any questions. I'd be happy to help.
I have taken your data and schema and made an approximation of what I believe your underlying data actually looks like. From that, your problem is actually very simple:
declare #t table(ItemNumber int, ReqDate datetime, DocumentStatus nvarchar(100), POStat nvarchar(100))
insert into #t values
(123456,'2017-10-15','Open','Not Closed')
,(123456,'2017-11-04','Open','Not Closed')
,(654321,'2017-09-29','Open','Not Closed')
,(654321,'2017-12-08','Open','Not Closed')
,(123456,'2017-10-11','Open','Closed')
,(123456,'2017-11-01','Closed','Not Closed')
,(654321,'2017-09-21','Closed','Not Closed')
,(654321,'2017-12-01','Open','Received');
select t.ItemNumber
,min(t.ReqDate) as MinDate
,max(t.ReqDate) as MaxDate
-- Find the difference in days for both Min and Max dates, converting to positive numbers where negative,
,case when abs(datediff(d,min(t.ReqDate),getdate())) < abs(datediff(d,max(t.ReqDate),getdate()))
then min(t.ReqDate) -- And then return the appropriate one.
else max(t.ReqDate)
end as ClosestDate
from #t t
where t.DocumentStatus = 'Open'
and t.POStat not in('Closed','Received')
and t.ReqDate >= dateadd(d,-15,cast(getdate() as date))
group by t.ItemNumber
order by MinDate desc;
Output:
+------------+-------------------------+-------------------------+-------------------------+
| ItemNumber | MinDate | MaxDate | ClosestDate |
+------------+-------------------------+-------------------------+-------------------------+
| 123456 | 2017-10-15 00:00:00.000 | 2017-11-04 00:00:00.000 | 2017-10-15 00:00:00.000 |
| 654321 | 2017-09-29 00:00:00.000 | 2017-12-08 00:00:00.000 | 2017-09-29 00:00:00.000 |
+------------+-------------------------+-------------------------+-------------------------+
Change the initial part of the query with this:
SELECT DISTINCT
I.ITEMNMBR,
MIN(PDATE1) AS MINDATE,
MAX(PDATE1) AS MAXDATE
IF(ABS(DATEDIFF(MIN(PDATE1)-SYSDATETIME())) >
ABS(DATEDIFF(MAX(PDATE1)-SYSDATETIME())),
MAX(PDATE1),MIN(PDATE1)) as CLOSESTDATE

Selecting description of 12 hour time period SQL Server 2008

I am writing a query for a report in which I need to select if the time period falls into the "Day" or "Night". The table I wish to select from looks like this:
DAYNIGHT_TABLE:
Description | StartTime | EndTime
Night | 18:00:00.0000000 | 06:00:00.0000000
Day | 06:00:00.0000000 | 18:00:00.0000000
For different customers this may be broken up further into smaller time slots but as a starting point I just need to select Day or Night based on a datetime that I retrieve from another table.
If I do something like this then I only get day:
SELECT Description
FROM Table s
WHERE convert(TIME,myTable.MyDateTime) >= s.StartTime
AND convert(TIME,myTable.MyDateTime) < s.StartTime
I feel like I am missing something obvious here but how can I retrieve the Day/Night Description from DAYNIGHT_TABLE based on the known time from myTable.MyDateTime?
If you can't touch the DAYNIGHT_TABLE table at all, read below.
Since all you need is a label "Day" or "Night" I would recommend to alter your DAYNIGHT_TABLE table and include one more row into it, so that no range of times in any row goes across the midnight. It makes query much simpler and efficient:
DAYNIGHT_TABLE:
Description | StartTime | EndTime
Night | 18:00:00.0000000 | 00:00:00.0000000
Night | 00:00:00.0000000 | 06:00:00.0000000
Day | 06:00:00.0000000 | 18:00:00.0000000
Also, make sure that you consistently assume which end of the interval is inclusive and which is exclusive. I'll assume that it is [StartTime; EndTime), i.e. StartTime inclusive and EndTime exclusive.
Have an index on StartTime DESC with Description as included column. It is not really needed and with so few rows in DAYNIGHT_TABLE you would likely not notice any difference, but still.
Now, if you have a table myTable with column MyDateTime and you want to get correct matching description from DAYNIGHT_TABLE, use something like this:
SELECT
myTable.MyDateTime
,CA.Description
FROM
myTable
CROSS APPLY
(
SELECT TOP(1) DAYNIGHT_TABLE.Description
FROM DAYNIGHT_TABLE
WHERE DAYNIGHT_TABLE.StartTime <= CAST(myTable.MyDateTime AS time)
ORDER BY DAYNIGHT_TABLE.StartTime DESC
) AS CA
For every row in myTable CROSS APPLY would find one matching Description.
Make sure that intervals in DAYNIGHT_TABLE cover all 24 hours without gaps.
Notice, that query doesn't use EndTime at all, because it assumes that intervals in the table cover full 24 hours. So, you can remove this column from the table and it will look like this:
Description | StartTime
Night | 00:00:00.0000000
Day | 06:00:00.0000000
Night | 18:00:00.0000000
With such table that has these two columns the only index that is needed is just a unique clustered primary key on StartTime.
If you can't touch the DAYNIGHT_TABLE table at all, but if you still can guarantee that intervals in the table (a) don't overlap, (b) don't have gaps, (c) cover full 24 hours, then there will be one extra step in the query.
If all conditions outlined above hold true, then there can be at most only one row that spans across midnight, which means that its StartTime is greater than its EndTime. This is how we can find it and take care of it. Again, I assume that StartTime is inclusive and EndTime is exclusive.
DECLARE #DAYNIGHT_TABLE TABLE(Description varchar(50), StartTime time, EndTime time);
INSERT INTO #DAYNIGHT_TABLE(Description, StartTime, EndTime) VALUES
('Night', '18:00:00', '06:00:00'),
('Day', '06:00:00', '18:00:00');
DECLARE #myTable TABLE(MyDateTime datetime);
INSERT INTO #myTable (MyDateTime) VALUES
('2015-01-01 00:00:00'),
('2015-01-01 02:02:02'),
('2015-01-01 06:00:00'),
('2015-01-01 12:02:02'),
('2015-01-01 18:00:00'),
('2015-01-01 22:02:02');
WITH
CTE_TimeIntervals
AS
(
SELECT
Description
,StartTime
FROM #DAYNIGHT_TABLE AS DAYNIGHT_TABLE
UNION ALL
SELECT
Description
,CAST('00:00:00' AS time) AS StartTime
FROM #DAYNIGHT_TABLE AS DAYNIGHT_TABLE
WHERE StartTime >= EndTime
)
SELECT
myTable.MyDateTime
,CA.Description
FROM
#myTable AS myTable
CROSS APPLY
(
SELECT TOP(1) CTE_TimeIntervals.Description
FROM CTE_TimeIntervals
WHERE CTE_TimeIntervals.StartTime <= CAST(myTable.MyDateTime AS time)
ORDER BY CTE_TimeIntervals.StartTime DESC
) AS CA
Result set
MyDateTime Description
2015-01-01 00:00:00.000 Night
2015-01-01 02:02:02.000 Night
2015-01-01 06:00:00.000 Day
2015-01-01 12:02:02.000 Day
2015-01-01 18:00:00.000 Night
2015-01-01 22:02:02.000 Night
You need somthing like this, test it with different values:
http://www.sqlfiddle.com/#!3/d6328/1/0
select s1.myDate, t.Descr
from s s1
inner join DAYNIGHT_TABLE t on datepart(hour, s1.myDate) >= datepart(hour, t.StartTime)
AND datepart(hour, s1.myDate) < datepart(hour, t.EndTime)
OR ((t.EndTime < t.StartTime) AND ( datepart(hour, s1.myDate) >= datepart(hour, t.StartTime)) AND datepart(hour, s1.mydate) < (datepart(hour, t.StartTime) + datepart(hour, t.EndTime)))

SQL -- return 0s if no group exists

I have a rollup table that sums up raw data for a given hour. It looks something like this:
stats_hours:
- obj_id : integer
- start_at : datetime
- count : integer
The obj_id points to a separate table, the start_at field contains a timestamp for the beginning of the hour of the data, and the count contains the sum of the data for that hour.
I would like to build a query that returns a set of data per day, so something like this:
Date | sum_count
2014-06-01 | 2000
2014-06-02 | 3000
2014-06-03 | 0
2014-06-04 | 5000
The query that I built does a grouping on the date column and sums up the count:
SELECT date(start_at) as date, sum(count) as sum_count
FROM stats_hours GROUP BY date;
This works fine unless I have no data for a given date, in which case it obviously leaves out the row:
Date | sum_count
2014-06-01 | 2000
2014-06-02 | 3000
2014-06-04 | 5000
Does anyone know of a good way in SQL to return a zeroed-out row in the case that there is no data for a given date group? Maybe some kind of case statement?
You need a full list of dates first, then connect that list to your available dates and group by that. Try the following:
--define start and end limits
Declare #todate datetime, #fromdate datetime
Select #fromdate='2009-03-01', #todate='2014-06-04'
;With DateSequence( Date ) as
(
Select #fromdate as Date
union all
Select dateadd(day, 1, Date)
from DateSequence
where Date < #todate
)
--select result
SELECT DateSequence.Date, SUM(Stats_Hours.Count) AS Sum_Count
FROM
DateSequence
LEFT JOIN
Stats_Hours ON DateSequence.Date = Stats_Hours.Start_At
GROUP BY DateSequence.Date
option (MaxRecursion 0)
EDIT: CTE code from this post

Modulo Time in SQL Server 2005 - Return data every n hours

I have something like this:
SELECt *
FROM (
SELECT prodid, date, time, tmp, rowid
FROM live_pilot_plant
WHERE date BETWEEN CONVERT(DATETIME, '3/19/2012', 101)
AND CONVERT(DATETIME, '3/31/2012', 101)
) b
WHERE b.rowid % 400 = 0
FYI: The reason for the convert in the where clause, is because my date is stored as a varchar(10), I had to convert it to datetime in order to get the correct range of data. (I tried a bunch of different things and this worked)
I'm wondering how I can return the data I want every 4 hours during those selected dates. I have data collected approximately every 5 seconds (with some breaks in data) - ie data wasn't collected during a 2 hour period, but then continues at 5 second increments.
In my example I just used a modulo with my rowid - and the syntax works, but as I mentioned above there are some periods where data isnt collected so using logic like: if you take data every 5 seconds and multiple that by 4 hours you can approximately say how many rows are in between wont work.
My time column is a varchar column and is in the form hh:mm:ss
My ideal output is:
| prodid | date | time | tmp |
| 4 | 3/19/2012 | 10:00:00 | 2.3 |
| 7 | 3/19/2012 | 14:00:24 | 3.2 |
As you can see I can be a bit off (in terms of seconds) - I more so need the approximate value in terms of time.
Thank you in advance.
This should work
select prodid, date, time, tmp, rowid
from live_pilot_plant as lpp
inner join (
select min(prodid) as prodid -- is prodid your PK?? if not change it to rowid or whatelse is your PK
from live_pilot_plant
WHERE date BETWEEN CONVERT(DATETIME, '3/19/2012', 101) -- or whatever you want
AND CONVERT(DATETIME, '3/31/2012', 101) -- for better performance it is on the inner select
group by date,
floor( -- floor makes the trick
convert(float,convert(datetime, time)) -- assumes "time" column is a varchar containing data like '19:23:05'
* 6 -- 6 comes form 24 hours / 4 hours
)
) as filter on lpp.prodid = filter.prodid -- if prodid is not the PK also correct here.
A side note for everyone else who have date + time data in only one datetime field, suppose named "when_it_was", the group by can be as simple as:
group by floor(when_it_was * 6) -- again, 6 comes from 24/4
something along the lines of the following should work. Basically create date + time partitions, each partition representing a block of 4 hours and pick the record with the highest rank from each partition
select * from (
select *,
row_number() over (partition by date,cast(left( time, charindex( ':', time) - 1) as int) / 4 order by
date, time) as ranker from live_pilot_plant
) Z where ranker = 1
Assuming rowid is a PK and increased with date/time. Just convert time field to 4 hours interval number substring(time,1,2))/4 and select MIN(rowid) from each of 4 hours groups in a day:
select prodid, date, time, tmp, rowid from live_pilot_plant where rowid in
(
select min(rowid)
from live_pilot_plant
WHERE CONVERT(DATETIME, date, 101) BETWEEN CONVERT(DATETIME, '3/19/2012', 101)
AND CONVERT(DATETIME, '3/31/2012', 101)
group by date,convert(int,substring(time,1,2))/4
)
order by CONVERT(DATETIME, date, 101),time