Comparing effective dates in SQL - sql

Wondering if there is a better why in the WHERE clause of choosing records when you need to look at effective start and end dates?
Currently this how I've done it in the past on MS SQL Server. Just worried about the date and not the time. I'm using SQL Server 2005.
AND Convert(datetime, Convert(char(10), ep.EffectiveStartDate, 101))
<= Convert(datetime, Convert(char(10), GetDate(), 101))
AND Convert(datetime, Convert(char(10), ep.EffectiveEndDate, 101))
>= Convert(datetime, Convert(char(10), GetDate(), 101))

That is terrible, take a look at Only In A Database Can You Get 1000% + Improvement By Changing A Few Lines Of Code to see how you can optimize this since that is not sargable
Also check out Get Datetime Without Time and Query Optimizations With Dates

#Darren Kopp - you can use
set #date2 = '20201001'
this will let you lose the cast.
footndale - you can use date arithmetic to remove the time as well. Something like
select dateadd(d, datediff(d, 0, CURRENT_TIMESTAMP), 0)
to get today's date (without the time). I believe this is more efficient than casting back and forth.

#Darren Kopp
Be carefull with BETWEEN, check out How Does Between Work With Dates In SQL Server?

AND DateDiff(Day, 0, GetDate()) + 1 > ep.EffectiveStartDate
AND DateDiff(Day, 0, GetDate()) < ep.EffectiveEndDate
I think you will find that these conditions offer the best performance possible. This will happily utilize indexes.
I am also very sure that this is right and will give the right data. No further calculation of dates without time portions is needed.

try
ep.EffectiveStartDate BETWEEN #date1 AND #date2
where you would do something like
declare #date1 datetime, #date2 datetime;
set #date1 = cast('10/1/2000' as datetime)
set #date2 = cast('10/1/2020' as datetime)

Related

Date Time in SQL Server 2005

I have a datetime column in table that has time component.
Is there anyway I can set '2011-03-14 11:46:31' to '2011-03-14 00:00:00'?
declare #date as datetime
set #date = getdate()
Select Cast(Floor(Cast(#date as float)) as DateTime)
Prior to 2k8 I always;
DATEADD(DAY, DATEDIFF(DAY, 0, datecol), 0)
As all the previous answers are the same, I'll add the other "good" option I know of:
SELECT DateAdd(Day, 0, DateDiff(Day, 0, GetDate()))
This is preferred by some/many people because it doesn't rely on the undocumented/unsupported fact that days are represented by integers in DateTime/SmallDateTime.
There have been interesting discussions on this topic in the SqlServerCentral.com forums, but I couldn't quickly find a link, sorry.
UPDATE: Gail Shaw posted a nice quick performance comparison of the common methods (this one apparently is fastest, but only by a v small amount): http://sqlinthewild.co.za/index.php/2008/09/04/comparing-date-truncations/
An Alternative that does the same thing, depending where you use it. This would also be possible to use in a view etc where a full script isn't possible.
SELECT CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, GETDATE())))

Is there an easier way to set a DateTime in SQL Server to be 23:59:59

Good Morning All,
I'm trying to refactor an SQL stored procedure. I'm no SQL expert, but something tells me there must be a better way to do this.
IF #ipv_dtEndDate IS NOT NULL
BEGIN
SET #ipv_dtEndDate = DATEADD(hh,23,#ipv_dtEndDate)
SET #ipv_dtEndDate = DATEADD(mi,59,#ipv_dtEndDate)
SET #ipv_dtEndDate = DATEADD(ss,59,#ipv_dtEndDate)
END
This value is used later inside a WHERE clause. These filters seem difficult to understand to me. I was hoping to come up with a cleaner implementation.
AND qtrh.StatusTime <= IsNull(#ipv_dtEndDate, qtrh.StatusTime)
And this date calculation...
AND DATEDIFF(ss,qtrh.StatusTime,ISNULL(#dtNow,DATEADD(ss,-1,qtrh.StatusTime))) < DATEDIFF(ss,ISNULL(#dtDateOptionCompare,GETDATE()),GETDATE())
... seems quite convoluted and unreadable. If any SQL gurus out there have some suggestions on how I can improve this, I would love to hear some ideas. Thanks for your time. Have a terrific holiday weekend.
Cheers,
~ck in San Diego
If the only use of #ipv_dtEndDate is inside the Where clause, you could remove the entire IF #ipv_dtEndDate IS NOT NULL block, and replace the condition in the SQL query with:
AND qtrh.StatusTime < DATEADD(dd,1,IsNull(#ipv_dtEndDate, qtrh.StatusTime))
(Strictly speaking, you will now also be including StatusTime values between 23:59:59 and 00:00:00, which were previously excluded.)
How about this?
SET #ipv_dtEndDate = CONVERT(varchar, #ipv_dtEndDate, 101) + ' 23:59:59'
Usually I use < and the date for the next day rather than trying to run a <+ with the last second of midnight.
You could convert the date to varchar, add your "23:59:59" and then convert it back to datetime
If you feel uncomfortable with the varchar approach, you can do the following.
SET #ipv_dtEndDate = DATEADD(ss, DATEDIFF(ss, 0, '11:59:59'), #ipv_dtEndDate)
To convert a date without a time (rather, with the time set to "midnight the morning of") to the "end of the day",
you can just add the number of seconds:
DECLARE #ipv_dtEndDate datetime
SET #ipv_dtEndDate = 'Sep 3, 2010'
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- Before
SET #ipv_dtEndDate = dateadd(ss, 1439, #ipv_dtEndDate)
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- After
Of course, SQL datetime is accurate to the [333rd of a] millisecond, so the end of the day is actually:
DECLARE #ipv_dtEndDate datetime
SET #ipv_dtEndDate = 'Sep 3, 2010'
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- Before
SET #ipv_dtEndDate = dateadd(ms, 1439997, #ipv_dtEndDate)
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- After
Using the built in (and mathematically based) date/time functions will be more efficient than converting to character strings and back.
This will return the latest time for today:
SELECT DATEADD(ms, -2, DATEADD(dd, 1, DATEDIFF(dd, 0, GETDATE())))
Just substitute GETDATE() with whatever you want to be inclusive. So, in your example:
AND qtrh.StatusTime <= DATEADD(ms, -2, DATEADD(dd, 1, DATEDIFF(dd, 0, #ipv_dtEndDate)))

Fastest way to check date range

I store events in SQLServer 2005 where the time the event occured is important and must be stored in the datebase. What is the fastest way to write the date range check in the where clause to ensure everything on that day is selected?
Currently when #DateStart and #DateEnd are passed in I set #DateStart to midnight and set #DateEnd to the last instant before midnight as the very first thing to catch every possible event on the day.
IF (#DateStart IS NOT NULL)
BEGIN
SET #DateStart = CAST (
( CAST (DATEPART (yyyy,#DateStart) AS NVARCHAR(4)) +'/'+
CAST (DATEPART (mm,#DateStart) AS NVARCHAR(2)) +'/'+
CAST (DATEPART (dd,#DateStart) AS NVARCHAR(2)) +' '+
'00:00:00.000'
)
AS DATETIME)
END
IF (#DateEnd IS NOT NULL)
BEGIN
SET #DateEnd = CAST (
( CAST (DATEPART (yyyy,#DateEnd) AS NVARCHAR(4)) +'/'+
CAST (DATEPART (mm,#DateEnd) AS NVARCHAR(2)) +'/'+
CAST (DATEPART (dd,#DateEnd) AS NVARCHAR(2)) +' '+
'23:59:59.997'
)
AS DATETIME
)
END
So the where clause is very easy to read:
WHERE ( EventDate >= #DateStart AND EventDate <= #DateEnd )
Thanks,
You could always use the alternate syntax of WHERE EventDate BETWEEN #DateStart AND #DateEnd
Your where clause would look like;
WHERE DateCol >= DATEADD(dd, DATEDIFF(dd, 0, #DateStart), 0) --Midnight on the Start date
AND DateCol < DATEADD(dd, DATEDIFF(dd, 0, #DateEnd + 1), 0) --Midnight of the day after End date
and all your IF statement would do is handle null parameters (i.e. IF #DateEnd IS NULL THEN SET #DateEnd = #DateStart)
You probably want to Index on DATEADD(dd, DATEDIFF(dd, 0, DateCol), 0) if your table is large.
the fastest way to truncate a date, previous midnight:
DATEADD(day, DATEDIFF(day, '19010101', LastModifiedDate), '19010101')
next midnight:
DATEADD(day, DATEDIFF(day, '19010101', LastModifiedDate)+1, '19010101')
You can also wrap this up as an inline UDF:
http://sqlblog.com/blogs/alexander_kuznetsov/archive/2008/05/23/reuse-your-code-with-cross-apply.aspx
Try this:
WHERE DATEPART(yyyy, EventDate) = DATEPART(yyyy, getdate())
AND DATEPART(dy, EventDate) = DATEPART(dy, getdate()) --day of year
EDIT To address Tom H's comment:
I've never had any luck with indexes on date fields; what has always worked better for me was extra integer columns to handle the year and day of year values and indexed those instead.
AlexK's is probably the best idea all around.
The only thing I would worry about is performance of using functions in the predicate.
You should consider adding a column called searchdate, or something similar, to your table to contain the date without the time. I'd also suggest indexing this column, especially if you're going to be searching against the column's data a lot.
When you query on this column you will not have any scalar functions in your SQL to rob your indexes of their performance.
The con... well, additional storage space, time during insert to write the data (not much here though).
SQL Server 2008 has better support for date only data types. You could also check in on it.
I think this T-SQL is equivalent to the code you have:
-- set time portion of #DateStart back to midnight
SET #DateStart = CONVERT(DATETIME,CONVERT(VARCHAR(10),#DateStart,20),20)
-- advance time portion of #DateEnd to last instant before next midnight
SET #DateEnd = CONVERT(DATETIME,CONVERT(VARCHAR(11),#DateEnd,20)+'23:59:59.997',21)
The CONVERT function will handle NULLS, so there's no need for a separate test for a NULL value (unless, of course, you are doing some special handling other than what you are showing, and are not passing the NULL values through to query predicate (i.e. WHERE clause). Or, perhaps you are expecting a lot of the arguments to be NULL, and you want to avoid the overhead of the calls to CONVERT.
However, I concur with Tom H.'s recommendation, and avoid messing with subtracting milliseconds, and instead set the #DateEnd to midnight of the following day e.g.
-- advance #DateEnd to midnight of following day
SET #DateEnd = DATEADD(day,1,CONVERT(DATETIME,CONVERT(VARCHAR(10),#DateEnd,20),20))
and change the predicate to do a range test like this:
WHERE (EventDate >= #DateStart AND EventDate < #DateEnd)
You can avoid the separate SET statements, and move the expressions straight into the query, but I don't expect that will improve performance any, and make the SQL statement harder to read, you'd definitely want to keep the comments ...
WHERE (EventDate >= CONVERT(DATETIME,CONVERT(VARCHAR(10),#DateStart,20),20)
AND EventDate

Best way to check for current date in where clause of sql query

I'm trying to find out the most efficient (best performance) way to check date field for current date. Currently we are using:
SELECT COUNT(Job) AS Jobs
FROM dbo.Job
WHERE (Received BETWEEN DATEADD(d, DATEDIFF(d, 0, GETDATE()), 0)
AND DATEADD(d, DATEDIFF(d, 0, GETDATE()), 1))
WHERE
DateDiff(d, Received, GETDATE()) = 0
Edit: As lined out in the comments to this answer, that's not an ideal solution. Check the other answers in this thread, too.
If you just want to find all the records where the Received Date is today, and there are records with future Received dates, then what you're doing is (very very slightly) wrong... Because the Between operator allows values that are equal to the ending boundary, so you could get records with Received date = to midnight tomorrow...
If there is no need to use an index on Received, then all you need to do is check that the date diff with the current datetime is 0...
Where DateDiff(day, received, getdate()) = 0
This predicate is of course not SARGable so it cannot use an index...
If this is an issue for this query then, assuming you cannot have Received dates in the future, I would use this instead...
Where Received >= DateAdd(day, DateDiff(Day, 0, getDate()), 0)
If Received dates can be in the future, then you are probably as close to the most efficient as you can be... (Except change the Between to a >= AND < )
If you want performance, you want a direct hit on the index, without any CPU etc per row; as such, I would calculate the range first, and then use a simple WHERE query. I don't know what db you are using, but in SQL Server, the following works:
// ... where #When is the date-and-time we have (perhaps from GETDATE())
DECLARE #DayStart datetime, #DayEnd datetime
SET #DayStart = CAST(FLOOR(CAST(#When as float)) as datetime) -- get day only
SET #DayEnd = DATEADD(d, 1, #DayStart)
SELECT COUNT(Job) AS Jobs
FROM dbo.Job
WHERE (Received >= #DayStart AND Received < #DayEnd)
that's pretty much the best way to do it.
you could put the DATEADD(d, DATEDIFF(d, 0, GETDATE()), 0) and DATEADD(d, DATEDIFF(d, 0, GETDATE()), 1) into variables and use those instead but i don't think that this will improve performance.
I'm not sure how you're defining "best" but that will work fine.
However, if this query is something you're going to run repeatedly you should get rid of the get_date() function and just stick a literal date value in there via whatever programming language you're running this in. Despite their output changing only once every 24 hours, get_date(), current_date(), etc. are non-deterministic functions, which means that your RDMS will probably invalidate the query as a candidate for storing in its query cache if it has one.
How 'bout
WHERE
DATEDIFF(d, Received, GETDATE()) = 0
I would normally use the solution suggested by Tomalak, but if you are really desperate for performance the best option could be to add an extra indexed field ReceivedDataPartOnly - which would store data without the time part and then use the query
declare #today as datetime
set #today = datediff(d, 0, getdate())
select
count(job) as jobs
from
dbo.job
where
received_DatePartOnly = #today
Compare two dates after converting into same format like below.
where CONVERT(varchar, createddate, 1) = CONVERT(varchar, getdate(), 1);

MS SQL Date Only Without Time

Question
Hello All,
I've had some confusion for quite some time with essentially flooring a DateTime SQL type using T-SQL. Essentially, I want to take a DateTime value of say 2008-12-1 14:30:12 and make it 2008-12-1 00:00:00. Alot of the queries we run for reports use a date value in the WHERE clause, but I either have a start and end date value of a day and use a BETWEEN, or I find some other method.
Currently I'm using the following:
WHERE CAST(CONVERT(VARCHAR, [tstamp], 102) AS DATETIME) = #dateParam
However, this seems kinda clunky. I was hoping there would be something more simple like
CAST([tstamp] AS DATE)
Some places online recommend using DATEPART() function, but then I end up with something like this:
WHERE DATEPART(year, [tstamp]) = DATEPART(year, #dateParam)
AND DATEPART(month, [tstamp]) = DATEPART(month, #dateParam)
AND DATEPART(day, [tstamp]) = DATEPART(day, #dateParam)
Maybe I'm being overly concerned with something small and if so please let me know. I just want to make sure the stuff I'm writing is as efficient as possible. I want to eliminate any weak links.
Any suggestions?
Thanks,
C
Solution
Thanks everyone for the great feedback. A lot of useful information. I'm going to change around our functions to eliminate the function on the left hand side of the operator. Although most of our date columns don't use indexes, it is probably still a better practice.
If you're using SQL Server 2008 it has this built in now, see this in books online
CAST(GETDATE() AS date)
that is very bad for performance, take a look at Only In A Database Can You Get 1000% + Improvement By Changing A Few Lines Of Code
functions on the left side of the operator are bad
here is what you need to do
declare #d datetime
select #d = '2008-12-1 14:30:12'
where tstamp >= dateadd(dd, datediff(dd, 0, #d)+0, 0)
and tstamp < dateadd(dd, datediff(dd, 0, #d)+1, 0)
Run this to see what it does
select dateadd(dd, datediff(dd, 0, getdate())+1, 0)
select dateadd(dd, datediff(dd, 0, getdate())+0, 0)
The Date functions posted by others are the most correct way to handle this.
However, it's funny you mention the term "floor", because there's a little hack that will run somewhat faster:
CAST(FLOOR(CAST(#dateParam AS float)) AS DateTime)
CONVERT(date, GETDATE()) and CONVERT(time, GETDATE()) works in SQL Server 2008. I'm uncertain about 2005.
How about this?
SELECT DATEADD(dd, DATEDIFF(dd,0,GETDATE()), 0)
Yes, T-SQL can feel extremely primitive at times, and it is things like these that often times push me to doing a lot of my logic in my language of choice (such as C#).
However, when you absolutely need to do some of these things in SQL for performance reasons, then your best bet is to create functions to house these "algorithms."
Take a look at this article. He offers up quite a few handy SQL functions along these lines that I think will help you.
http://weblogs.sqlteam.com/jeffs/archive/2007/01/02/56079.aspx
Careful here, if you use anything a long the lines of WHERE CAST(CONVERT(VARCHAR, [tstamp], 102) AS DATETIME) = #dateParam it will force a scan on the table and no indexes will be used for that portion.
A much cleaner way of doing this is defining a calculated column
create table #t (
d datetime,
d2 as
cast (datepart(year,d) as varchar(4)) + '-' +
right('0' + cast (datepart(month,d) as varchar(2)),2) + '-' +
right('0' + cast (datepart(day,d) as varchar(2)),2)
)
-- notice a lot of care need to be taken to ensure the format is comparable. (zero padding)
insert #t
values (getdate())
create index idx on #t(d2)
select d2, count(d2) from #t
where d2 between '2008-01-01' and '2009-01-22'
group by d2
-- index seek is used
This way you can directly check the d2 column and an index will be used and you dont have to muck around with conversions.
DATEADD(d, 0, DATEDIFF(d, 0, [tstamp]))
Edit: While this will remove the time portion of your datetime, it will also make the condition non SARGable. If that's important for this query, an indexed view or a between clause is more appropriate.
Alternatively you could use
declare #d datetimeselect
#d = '2008-12-1 14:30:12'
where tstamp
BETWEEN dateadd(dd, datediff(dd, 0, #d)+0, 0)
AND dateadd(dd, datediff(dd, 0, #d)+1, 0)
Here's a query that will return all results within a range of days.
DECLARE #startDate DATETIME
DECLARE #endDate DATETIME
SET #startDate = DATEADD(day, -30, GETDATE())
SET #endDate = GETDATE()
SELECT *
FROM table
WHERE dateColumn >= DATEADD(day, DATEDIFF(day, 0, #startDate), 0)
AND dateColumn < DATEADD(day, 1, DATEDIFF(day, 0, #endDate))
FWIW, I've been doing the same thing as you for years
CAST(CONVERT(VARCHAR, [tstamp], 102) AS DATETIME) = #dateParam
Seems to me like this is one of the better ways to strip off time in terms of flexibility, speed and readabily. (sorry). Some UDF functions as suggested can be useful, but UDFs can be slow with larger result sets.
WHERE DATEDIFF(day, tstamp, #dateParam) = 0
This should get you there if you don't care about time.
This is to answer the meta question of comparing the dates of two values when you don't care about the time.