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

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

Related

How can I find the time difference in two dates?

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).

Deleting rows within floating data ranges using SQL

I have some date data as follows:-
Person | Date
1 | 1/1/2000
1 | 6/1/2000
1 | 11/1/2000
1 | 21/1/2000
1 | 28/1/2000
I need to delete rows within 14 days of a previous one. However, if a row is deleted, it should not later become a 'base' date against which later rows are checked. It's perhaps easier to show the results needed:-
Person | Date
1 | 1/1/2000
1 | 21/1/2000
My feeling is that recursive SQL will be needed but I'm not sure how to set it up. I'll be running this on Teradata.
Thanks.
--- Edit ---
Well, this is embarrassing. It turns out this question has been asked before - and it was asked by me! See this old question for an excellent answer from #dnoeth:-
Drop rows identified within moving time window
Use recursive tables. Use ROWNUMBER() to Order and Number the dates.
DATEDIFF() to receive the number of days passed from previous date
Maybe SQL2012 and above can simplify using SUM() OVER PARTITION with a RANGE
I didn't find it useful in this case
DECLARE #Tab TABLE ([MyDate] SMALLDATETIME)
INSERT INTO #Tab ([MyDate])
VALUES
('2000-01-06'),
('2000-01-01'),
('2000-01-11'),
('2000-01-21'),
('2000-01-28')
;
WITH DOrder (MyDate, SortID) AS (
SELECT MyDate,
ROW_NUMBER() OVER (ORDER BY MyDate)SortID
FROM #Tab t)
,Summarize(MyDate, SortID, sSum, rSum ) AS (
SELECT MyDate, SortID, 0, 0 rSum
FROM DOrder WHERE SortID = 1
UNION ALL
SELECT t.MyDate, t.SortID, DATEDIFF(D, ISNULL(s.MyDate,t.MyDate), t.MyDate) rSum,
CASE WHEN DATEDIFF(D, ISNULL(s.MyDate,t.MyDate), t.MyDate) + s.rSum>14 THEN 0
ELSE DATEDIFF(D, ISNULL(s.MyDate,t.MyDate), t.MyDate)
END rSum
FROM DOrder t INNER JOIN Summarize s
ON (t.SortID = s.SortID+1))
SELECT MyDate
FROM Summarize
WHERE rSum=0

Comparing dates using SQL

I have a date that looks like this: 2014-10-01 12:35:29.440
the table looks like this:
ORDER 1 | 2014-07-31 00:00:00.000
ORDER 2 | 2015-07-31 00:00:00.000
sorry i wanted ORDER 2 to show up.. As my get date returns todays date and that is GREATER than 2014-07-31 00:00:00.000
Here is what i have tried:
SELECT TOP 1 NAME
FROM ORDER_DATES
WHERE GETDATE() > ORDER_DATE
ORDER BY NAME DESC
Your question still isn't quite worded in a way that is conducive to what you need... but I think I understand what you want now based on the comments.
Based on the comment:
IF it doesnt match the date then it needs to return the next row.
Which is ORDER 2
Something like this should work:
SELECT TOP 1 name
FROM ORDER_DATES o
INNER JOIN (
-- This subquery finds the first date that occurs *after* the current date
SELECT MIN(ORDER_DATE) AS ORDER_DATE
FROM ORDER_DATES
WHERE ORDER_DATE > GETDATE()
) minDateAfterToday ON o.ORDER_DATE = minDateAfterToday.ORDER_DATE
ORDER BY name
This would work a lot better if you had an ID field in the table, but this should work with the given data, you'll potentially run into issues if you have two orders on the exact same date.
EDIT:
here's a fiddle showing the query in action:
http://sqlfiddle.com/#!6/f3057/1
DATEDIFF will come handy, also you have to order by ORDER_DATE:
SELECT TOP 1 NAME
FROM ORDER_DATES
WHERE DATEDIFF(DAY,ORDER_DATE,GETDATE())>0
ORDER BY ORDER_DATE DESC
You can write as:
SELECT NAME
FROM ORDER_DATES
WHERE cast(GETDATE()as date) > cast (ORDER_DATE as date)
ORDER BY NAME DESC
Demo
Check if you are querying against right table
declare #dt datetime = cast('2014-10-01 12:35:29.440' as datetime), #dt2 datetime= cast('2014-07-31 00:00:00.000' as datetime);
print(case when #dt > #dt2 then 1 else 0 end);
This piece of script shows output 1 i.e. condition should match for ORDER 1.
Verify if you are missing some thing.
Edit as per change to original question:
here the condition needed be reverted as date value is in future which is greater than current date
new query will be as
SELECT TOP 1 NAME
FROM ORDER_DATES
WHERE ORDER_DATE > GETDATE()
ORDER BY NAME DESC

Total number of Events on every date

I am stuck developing a query.
I have a table, structured like this:
[EventId] [Description] [EventName] [ValidFrom] [ValidTo] [Approved]
1 Sample1 1stEvent 2013-01-27 2013-05-10 1
2 Sample2 2stEvent 2013-04-07 2013-06-15 1
3 Sample3 3stEvent 2013-04-07 2013-06-15 1
4 Sample4 4stEvent 2013-03-02 2013-05-29 1
5 Sample5 5stEvent 2013-05-17 2013-07-10 1
6 Sample6 6stEvent 2013-03-20 2013-05-11 1
What i want is the total number of events for every date within a date range, inclusive.
Select distinct
Convert(varchar,ValidFrom,101)as [Date],
case
when count(EventID)>1 then Convert(nvarchar, count(EventID)) +' Events'
else Convert(nvarchar, count(EventID)) + ' Event'
end as CountOf,
Row_Number()
over (Order By Convert(varchar,ValidFrom,101)) as RowNumber
from [Table]
where Approved=1
group by Convert(varchar,ValidFrom,101)
This is the query I have come up with until now, but this shows the total number of events on a particular date without including the events which were continued as per the dates between valid from and valid to dates.
This code sample is not complete - you need to enter the fields you need to display and aggregate upon. It sounds like you're looking for a result that's between two dates, and you don't have that in your query. I'm not sure I comletely understand your question.
DECLARE #pStartDate DATE
DECLARE #pEndDate DATE
SET #pStartDate = [enter your start date for the date range]
SET #pEndDate = [enter your end date for the date range]
SELECT
COUNT(EventId),
ValidFrom,
ValidTo
FROM [Table]
WHERE
ValidFrom >= #pStartDate
AND ValidTo <= #pEndDate
AND Approved = 1
GROUP BY
ValidFrom,
ValidTo
This will do:
declare #dateFrom date
declare #dateTo date
SET #dateFrom = '20130101'
SET #dateTo = '20130501'
;with cte as(Select #dateFrom AS EveryDay
UNION ALL
Select dateadd(dd, 1, EveryDay) FROM cte WHERE EveryDay < #dateTo)
SELECT
EveryDay,
COUNT(DISTINCT [EventName]) AS NoEvents
from cte LEFT JOIN Table1 ON ValidFrom <= EveryDay AND ValidTo >= EveryDay
GROUP BY EveryDay
OPTION (MAXRECURSION 0)
SQL Fiddle
With the above Query i have mentioned gives the result like this :-
Date CountOf RowNumber
01/27/2013 2 Events 1
03/02/2013 1 Event 2
04/07/2013 2 Events 3
05/17/2013 1 Event 4
As you can see i am getting total events only on the base of particular date which is not desired.
i want to show the result as like in valid from =01/27/2013 in valid to 2013-05-10 which means in the case of Date =2013-03-02 the events should be incremented by one or the number of events which are active on that date,so the result for 2013-03-02 should be 6 events, i wish i can present a better picture but because of the complexity it made me blank :(
SELECT t1.EventId,t1.Description,t1.EventName,t1.ValidFrom,t1.ValidTo,t1.Approved,SUM(TotalEvent) FROM
(SELECT EventId,Description,EventName,ValidFrom,ValidTo,Approved,COUNT(EventId) as Total
FROM TABLE
WHERE Approved='1' AND ValidFrom >=(DATE) AND ValidTO<=(DATE)
GROUP BY EventId,Description,EventName,ValidFrom,ValidTo,Approved) AS t1
LEFT JOIN
(SELECT EventId, COUNT(EventId) as TotalEvent
FROM TABLE
WHERE ValidFrom >=(DATE) AND ValidTO<=(DATE)) AS t2
ON t1.EventId=t2.EventId
Group by t1.EventId,t1.Description,t1.EventName,t1.ValidFrom,t1.ValidTo,t1.Approved
Hope this help you.

Filter based on multiple date ranges?

The user clicks on a month and then this stored procedure is executed. It checks for the total booked time and what groups have been filtered.
| Job Group | Month Booked | Time (hrs) |
Cleaning Jan 7
I have the following SQL:
SELECT
tsks.grouping_ref, ttg.description AS grouping_desc,
SUM(ts.booked_time) AS booked_time_total,
DATENAME(MONTH, ts.start_dtm) + ' ' + DATENAME(YEAR, ts.start_dtm) AS month_name,
#month_ref AS month_ref
FROM
timesheets ts
JOIN
timesheet_categories cat ON ts.timesheet_cat_ref = cat.timesheet_cat_ref
JOIN
timesheet_tasks tsks ON ts.task_ref = tsks.task_ref
JOIN
timesheet_task_groupings ttg ON tsks.grouping_ref = ttg.grouping_ref
WHERE
ts.status IN(1, 2) --Booked and approved
AND cat.is_leave_category = 0 --Ignore leave
AND DATEPART(YEAR, ts.start_dtm) = #Year
AND DATEPART(MONTH, ts.start_dtm) = #Month
GROUP BY
tsks.grouping_ref, ttg.description,
DATENAME(MONTH, ts.start_dtm),
DATENAME(YEAR, ts.start_dtm)
ORDER BY
grouping_desc
I want to filter based on multiple date ranges.
I thought about adding this:
AND ((ts.start_dtm BETWEEN '2011-12-28' AND '2012-01-01')
OR (ts.start_dtm BETWEEN '2012-01-02' AND '2012-01-29'))
But then realized it wouldn't matter what month the user clicked it would still show all the records as it will carry out the OR statement.
What I need is something that's based on the month_ref, eg:
CASE WHEN #month_ref = 81201 THEN
AND (ts.start_dtm BETWEEN '2011-12-28' AND '2012-01-01')
END
But the case statement needs to go just after the WHERE clause.
I have about 12 accounting months for 2012 which I need to add as case statements so that when the user clicks on March, it will fire the correct filter.
In the database ts.start_dtm looks like this:
2011-04-01 00:00:00.000
Hope that was enough information for my first post?
I'm stuck writing the case statement and where to put it, been trying for hours now.
Hope you can help :)
Give the irregular nature of your dates would preclude using dateparts; I would build a temporary table of the permissible dates based on the user query and join on it. The static integers table in my app has 1 through 64000 your tables may vary.
DECLARE
#startdate DateTime = '2012-05-01',
#EndDate DateTime = '2012-06-03'
DECLARE
#AllDates TABLE (MyDate DateTime)
INSERT INTO #AllDates
SELECT
DATEADD(dd, StaticInteger, #startdate)
FROM dbo.tblStaticIntegers
WHERE StaticInteger <= DATEDIFF(dd, #startdate, #EndDate)
One option would be to have a table mapping the month reference number to a start and end date thus retrieving those values and using them in your ts.start_dtm check. ie it would have:
Month-ref | Start | End
81201 | 2011-12-28 | 2012-01-01
81202 | 2012-01-02 | 2012-01-29
etc
You can just join to this reference table or alternatively retrieve the two dates before your main query