Is there a better way to convert SQL datetime from hh:mm:ss to hhmmss? - sql

I have to write an SQL view that returns the time part of a datetime column as a string in the format hhmmss (apparently SAP BW doesn't understand hh:mm:ss).
This code is the SAP recommended way to do this, but I think there must be a better, more elegant way to accomplish this
TIME = case len(convert(varchar(2), datepart(hh, timecolumn)))
when 1 then /* Hour Part of TIMES */
case convert(varchar(2), datepart(hh, timecolumn))
when '0' then '24' /* Map 00 to 24 ( TIMES ) */
else '0' + convert(varchar(1), datepart(hh, timecolumn))
end
else convert(varchar(2), datepart(hh, timecolumn))
end
+ case len(convert(varchar(2), datepart(mi, timecolumn)))
when 1 then '0' + convert(varchar(1), datepart(mi, timecolumn))
else convert(varchar(2), datepart(mi, timecolumn))
end
+ case len(convert(varchar(2), datepart(ss, timecolumn)))
when 1 then '0' + convert(varchar(1), datepart(ss, timecolumn))
else convert(varchar(2), datepart(ss, timecolumn))
end
This accomplishes the desired result, 21:10:45 is displayed as 211045.
I'd love for something more compact and easily readable but so far I've come up with nothing that works.

NOTE:
The question says that the column is of datatype DATETIME, not the newer (SQL Server 2008) TIME datatype.
ANSWER:
REPLACE(CONVERT(VARCHAR(8),timecolumn,8),':','')
Let's unpack that.
First, CONVERT formats the time portion of the datetime into a varchar, in format 'hh:mi:ss' (24-hour clock), as specified by the format style value of 8.
Next, the REPLACE function removes the colons, to get varchar in format 'hhmiss'.
That should be sufficient to get a usable string in the format you'd need.
FOLLOW-UP QUESTION
(asked by the OP question)
Is an inline expression faster/less server intensive than a user defined function?
The quick answer is yes. The longer answer is: it depends on several factors, and you really need to measure the performance to determine if that's actually true or not.
I created and executed a rudimentary test case:
-- sample table
create table tmp.dummy_datetimes (c1 datetime)
-- populate with a row for every minute between two dates
insert into tmp.dummy_datetimes
select * from udfDateTimes('2007-01-01','2009-01-01',1,'minute')
(1052641 row(s) affected)
-- verify table contents
select min(c1) as _max
, max(c1) as _min
, count(1) as _cnt
from tmp.dummy_datetimes
_cnt _min _max
------- ----------------------- -----------------------
1052641 2007-01-01 00:00:00.000 2009-01-01 00:00:00.000
(Note, the udfDateTimes function returns the set of all datetime values between two datetime values at the specified interval. In this case, I populated the dummy table with rows for each minute for two entire years. That's on the order of a million ( 2x365x24x60 ) rows.
Now, user defined function that performs the same conversion as the inline expression, using identical syntax:
CREATE FUNCTION [tmp].[udfStrippedTime] (#ad DATETIME)
RETURNS VARCHAR(6)
BEGIN
-- Purpose: format time portion of datetime argument to 'hhmiss'
-- (for performance comparison to equivalent inline expression)
-- Modified:
-- 28-MAY-2009 spencer7593
RETURN replace(convert(varchar(8),#ad,8),':','')
END
NOTE: I know the function is not defined to be DETERMINISTIC. (I think that requires the function be declared with schema binding and some other declaration, like the PRAGMA required Oracle.) But since every datetime value is unique in the table, that shouldn't matter. The function is going to have to executed for each distinct value, even if it were properly declared to be DETERMINISTIC.
I'm not a SQL Server 'user defined function' guru here, so there may be something else I missed that will inadvertently and unnecessarily slow down the function.
Okay.
So for the test, I ran each of these queries alternately, first one, then the other, over and over in succession. The elapsed time of the first run was right in line with the subsequent runs. (Often that's not the case, and we want to throw out the time for first run.) SQL Server Management Studio reports query elapsed times to the nearest second, in format hh:mi:ss, so that's what I've reported here.
-- elapsed times for inline expression
select replace(convert(varchar(8),c1,8),':','') from tmp.dummy_datetimes
00:00:10
00:00:11
00:00:10
-- elapsed times for equivalent user defined function
select tmp.udfStrippedTime(c1) from tmp.dummy_datetimes
00:00:15
00:00:15
00:00:15
For this test case, we observe that the user defined function is on the order of 45% slower than an equivalent inline expression.
HTH

you could use a user-defined function like:
create FUNCTION [dbo].[udfStrippedTime]
(
#dt datetime
)
RETURNS varchar(32)
AS
BEGIN
declare #t varchar(32)
set #t = convert( varchar(32), #dt, 108 )
set #t = left(#t,2) + substring(#t,4,2)
RETURN #t
END
then
select dbo.udfStrippedTime(GETDATE())
the logic for the seconds is left as an exercise for the reader

Here's a question. Does the formatting need to happen on the Db Server? The server itself really only care about, and is optimized for storing the data. Viewing the data is usually the responsibility of hte layer above the Db (in a strictly academic sense, the real world is a bit more messy)
For instance, if you are outputting the result into an ASP.NET page bound to a GridControl you would just specify your DataFormattingString when you bind to the column. If you were using c# to write it to a text file, when you pull the data you would just pass the format string to the .ToString() function.
If you need it to be on the DbServer specifically, then yeah pretty much every solution is going to be messy because the time format you need, while compact and logical, is not a time format the server will recognize so you will need to manipulate it as a string.

This handles the 00 - > 24 conversion
SELECT CASE WHEN DATEPART(hh,timecolumn) = 0
THEN '24' + SUBSTRING(REPLACE(CONVERT(varchar(8),timecolumn, 108),':',''),3,4)
ELSE REPLACE(CONVERT(varchar(8),timecolumn, 108),':','') END

Edit 2: updated to handle 0 --> 24 conversion, and a shorter version:
select replace(left(replace(convert(char,getdate(),8),':',''),2),'00','24') + right(replace(convert(char,getdate(),8),':',''),4)
Back to the slightly longer version :)

SELECT replace(convert(varchar(15),datetimefield, 108), ':','')
from Table

SELECT REPLACE('2009-05-27 12:49:19', ':', '')
2009-05-27 124919

Related

SQL Server DateDiff between two dates returns inaccurate values

I am trying to convert a string into DateTimeOffset (in SQL Server) through a ETL job. Basically, my string would look something like '2017-10-15' and I want this to be converted into a DatetimeOffset (from the current DB server).
SELECT
SWITCHOFFSET(DATEADD(mi, DATEDIFF(MI, GETDATE(), GETUTCDATE()), CAST(#DateInString + ' 00:00:00' AS DATETIMEOFFSET)), DATENAME(tzoffset, SYSDATETIMEOFFSET()))
I have been getting some weird issues with this statement as the final output would fall either +1 / -1 minute than the expected ones. This happens for at least every 10 records/million. I tried to nail down the issue and I could see the problem was with the DATEDIFF() method returning +/-1 minute.
SELECT DATEDIFF(MI, GETDATE(), GETUTCDATE())
This should exactly return -600 (since my DB server UTC is +10). However, it returns either -599 or 601 for few records. I execute them as a single select statement in my Stored Procedure and return it as a parameter.
This is weird on how SQL could detect two different datetime values for GETDATE() and GETUTCDATE() on the same select statement.
Is there a way to force SQL to get exactly same dates in those DATEDIFF parameters or am I missing something here? Thanks in advance
I am using SQL Server 2014 (v12.0).
Stored procedure:
CREATE PROCEDURE dbo.SPConvertDateTimeOffset
#DateInString VARCHAR(10),
#DateTimeOffset_Value DATETIMEOFFSET OUTPUT,
#Datediff_Value INT OUTPUT
AS
BEGIN
-- This line returns +/- 1
SELECT #Datediff_Value = DATEDIFF(MI, GETDATE(), GETUTCDATE())
SELECT #DateTimeOffset_Value = SWITCHOFFSET(DATEADD(mi, #Datediff_Value, CAST(#DateInString + ' 00:00:00' AS DATETIMEOFFSET)), DATENAME(tzoffset, SYSDATETIMEOFFSET()))
END
#GordonLinoff has explained why this happens: since functions are executed at slightly different times, they may return a different minute.
To work around, try this:
DECLARE #DateTimeOffset_Value Datetimeoffset
DECLARE #Datediff_Value INT, #DateInString VARCHAR( 10 )
SET #DateInString = CONVERT( VARCHAR, GETDATE(), 112 )
SET #DateTimeOffset_Value = TODATETIMEOFFSET( #DateInString, DATENAME(tzoffset,SYSDATETIMEOFFSET()))
SET #Datediff_Value = DATEDIFF( MI, #DateInString, #DateTimeOffset_Value )
SELECT #DateInString, #DateTimeOffset_Value, #Datediff_Value
It does not use current date comparisons.
Note: that during the time when day light saving changes you may get a different value from the expected, depending on when exactly the code was run.
Have a look at https://dba.stackexchange.com/questions/28187/how-can-i-get-the-correct-offset-between-utc-and-local-times-for-a-date-that-is for more solutions about how to handle DTS changes.
The two functions are not executed simultaneously. So about 1 time in 100,000 (in your test) the times are on opposite sides of a minute boundary.
If you just want the timezone, you could try:
select datepart(tz, SYSDATETIMEOFFSET())

SQL statement between date

This is driving me crazy and not sure what I'm missing here..
so here is my data column looks like:
StartDateTime:
---------------
2012-01-17 11:13:46.530
2012-01-17 11:17:22.530
2012-02-17 11:31:22.223
here is my query trying to get:
select * from tablName
where convert(varchar(10), startDateTime, 101) between '2012-01-17' and '2012-01-17'
based on the above I should be getting TWO rows? but it does not, it return zero rows. what will be the correct way of doing?
PS:
I've looked at the MSDN site too:
Your query would only match dates that are between 2012-01-17 00:00:00 and 2012-01-17 00:00:00. So, the only matches would be when the date is exactly 2012-01-17 00:00:00.
Instead, I would do this:
declare #dateInput as DateTime
set #dateInput = '2012-01-17'
select *
from tablName
where startDateTime >= #dateInput
and startDateTime < dateadd(day, 1, #dateInput)
Note: SQL Server 2008+ has a new data type Date with no time component that can make these types of queries more readable.
There is now more information so I'll add a more appropriate answer.
The requirements are now a stored procedure passed a Date type parameter, not DateTime, and the desire is to return rows from a table based on criterion against a DateTime field named StartDateTime...
create procedure dbo.spGetEntriesForOneDay
#DesiredDate DateTime
as
SET NOCOUNT ON;
SET #DesiredDate = DATEADD(day, DATEDIFF(day, 0, #DesiredDate), 0)
SELECT Field1, Field2 -- see note 1
FROM dbo.TableName
WHERE StartDateTime >= #DesiredDate -- see note 2
AND StartDateTime < DATEADD(day, 1, #DesiredDate) -- see note 3
NOTE 1: Don't use * in production code, especially in a stored procedure. Besides being wasteful by returning columns you probably don't need and precluding the optimization of a covering index on a subset of the columns required you would need to recompile this stored procedure whenever the underlying table is altered in order to avoid unpredictable results.
NOTE 2: Avoid wrapping fields in functions. A field not wrapped in a function can potentially be matched by the optimizer to an index while a field wrapped in a function never will.
NOTE 3: #Martin Smith and #RedFilter are correct in that .997 precision assumes DateTime datatype forever; this approach is more future proof because is makes no assumptions of data type precision.
You're using a datetime field (I'm guessing).
Don't forget the time:
select * from tablName
where startDateTime between '2012-01-17' and '2012-01-17 23:59:59.997'
You can use the DateDiff function in the where clause. It would look like this:
select col1, col2 from tablName where DateDiff(day, startDateTime, #DesiredDate) = 0

Best approach to remove time part of datetime in SQL Server

Which method provides the best performance when removing the time portion from a datetime field in SQL Server?
a) select DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)
or
b) select cast(convert(char(11), getdate(), 113) as datetime)
The second method does send a few more bytes either way but that might not be as important as the speed of the conversion.
Both also appear to be very fast, but there might be a difference in speed when dealing with hundreds-of-thousands or more rows?
Also, is it possible that there are even better methods to get rid of the time portion of a datetime in SQL?
Strictly, method a is the least resource intensive:
a) select DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)
Proven less CPU intensive for the same total duration a million rows by someone with way too much time on their hands: Most efficient way in SQL Server to get a date from date+time?
I saw a similar test elsewhere with similar results too.
I prefer the DATEADD/DATEDIFF because:
varchar is subject to language/dateformat issues
Example: Why is my CASE expression non-deterministic?
float relies on internal storage
it extends to work out first day of month, tomorrow, etc by changing "0" base
Edit, Oct 2011
For SQL Server 2008+, you can CAST to date i.e. CAST(getdate() AS date). Or just use date datatype so no time to remove.
Edit, Jan 2012
A worked example of how flexible this is: Need to calculate by rounded time or date figure in sql server
Edit, May 2012
Do not use this in WHERE clauses and the like without thinking: adding a function or CAST to a column invalidates index usage. See number 2 here Common SQL Programming Mistakes
Now, this does have an example of later SQL Server optimiser versions managing CAST to date correctly, but generally it will be a bad idea ...
Edit, Sep 2018, for datetime2
DECLARE #datetime2value datetime2 = '02180912 11:45' --this is deliberately within datetime2, year 0218
DECLARE #datetime2epoch datetime2 = '19000101'
select DATEADD(dd, DATEDIFF(dd, #datetime2epoch, #datetime2value), #datetime2epoch)
In SQL Server 2008, you can use:
CONVERT(DATE, getdate(), 101)
Of-course this is an old thread but to make it complete.
From SQL 2008 you can use DATE datatype so you can simply do:
SELECT CONVERT(DATE,GETDATE())
In SQL Server 2008, there is a DATE datetype (also a TIME datatype).
CAST(GetDate() as DATE)
or
declare #Dt as DATE = GetDate()
SELECT CAST(FLOOR(CAST(getdate() AS FLOAT)) AS DATETIME)
...is not a good solution, per the comments below.
I would delete this answer, but I'll leave it here as a counter-example since I think the commenters' explanation of why it's not a good idea is still useful.
Here's yet another answer, from another duplicate question:
SELECT CAST(CAST(getutcdate() - 0.50000004 AS int) AS datetime)
This magic number method performs slightly faster than the DATEADD method. (It looks like ~10%)
The CPU Time on several rounds of a million records:
DATEADD MAGIC FLOAT
500 453
453 360
375 375
406 360
But note that these numbers are possibly irrelevant because they are already VERY fast. Unless I had record sets of 100,000 or more, I couldn't even get the CPU Time to read above zero.
Considering the fact that DateAdd is meant for this purpose and is more robust, I'd say use DateAdd.
SELECT CAST(CAST(GETDATE() AS DATE) AS DATETIME)
I really like:
[date] = CONVERT(VARCHAR(10), GETDATE(), 120)
The 120 format code will coerce the date into the ISO 8601 standard:
'YYYY-MM-DD' or '2017-01-09'
Super easy to use in dplyr (R) and pandas (Python)!
BEWARE!
Method a) and b) does NOT always have the same output!
select DATEADD(dd, DATEDIFF(dd, 0, '2013-12-31 23:59:59.999'), 0)
Output: 2014-01-01 00:00:00.000
select cast(convert(char(11), '2013-12-31 23:59:59.999', 113) as datetime)
Output: 2013-12-31 00:00:00.000
(Tested on MS SQL Server 2005 and 2008 R2)
EDIT: According to Adam's comment, this cannot happen if you read the date value from the table, but it can happen if you provide your date value as a literal (example: as a parameter of a stored procedure called via ADO.NET).
See this question:
How can I truncate a datetime in SQL Server?
Whatever you do, don't use the string method. That's about the worst way you could do it.
Already answered but ill throw this out there too...
this suposedly also preforms well but it works by throwing away the decimal (which stores time) from the float and returning only whole part (which is date)
CAST(
FLOOR( CAST( GETDATE() AS FLOAT ) )
AS DATETIME
)
second time I found this solution... i grabbed this code off
CAST(round(cast(getdate()as real),0,1) AS datetime)
This method does not use string function. Date is basically a real datatype with digits before decimal are fraction of a day.
this I guess will be faster than a lot.
For me the code below is always a winner:
SELECT CONVERT(DATETIME, FLOOR(CONVERT(FLOAT,GETDATE())));
select CONVERT(char(10), GetDate(),126)
Strip time on inserts/updates in the first place. As for on-the-fly conversion, nothing can beat a user-defined function maintanability-wise:
select date_only(dd)
The implementation of date_only can be anything you like - now it's abstracted away and calling code is much much cleaner.
I think you mean
cast(floor(cast(getdate()as float))as datetime)
real is only 32-bits, and could lose some information
This is fastest
cast(cast(getdate()+x-0.5 as int)as datetime)
...though only about 10% faster(about 0.49 microseconds CPU vs. 0.58)
This was recommended, and takes the same time in my test just now:
DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)
In SQL 2008, the SQL CLR function is about 5 times faster than using a SQL function would be, at 1.35 microseconds versus 6.5 microsections, indicating much lower function-call overhead for a SQL CLR function versus a simple SQL UDF.
In SQL 2005, the SQL CLR function is 16 times faster, per my testing, versus this slow function:
create function dateonly ( #dt datetime )
returns datetime
as
begin
return cast(floor(cast(#dt as float))as int)
end
How about select cast(cast my_datetime_field as date) as datetime)? This results in the same date, with the time set to 00:00, but avoids any conversion to text and also avoids any explicit numeric rounding.
I think that if you stick strictly with TSQL that this is the fastest way to truncate the time:
select convert(datetime,convert(int,convert(float,[Modified])))
I found this truncation method to be about 5% faster than the DateAdd method. And this can be easily modified to round to the nearest day like this:
select convert(datetime,ROUND(convert(float,[Modified]),0))
Here I made a function to remove some parts of a datetime for SQL Server. Usage:
First param is the datetime to be stripped off.
Second param is a char:
s: rounds to seconds; removes milliseconds
m: rounds to minutes; removes seconds and milliseconds
h: rounds to hours; removes minutes, seconds and milliseconds.
d: rounds to days; removes hours, minutes, seconds and milliseconds.
Returns the new datetime
create function dbo.uf_RoundDateTime(#dt as datetime, #part as char)
returns datetime
as
begin
if CHARINDEX( #part, 'smhd',0) = 0 return #dt;
return cast(
Case #part
when 's' then convert(varchar(19), #dt, 126)
when 'm' then convert(varchar(17), #dt, 126) + '00'
when 'h' then convert(varchar(14), #dt, 126) + '00:00'
when 'd' then convert(varchar(14), #dt, 112)
end as datetime )
end
Just in case anyone is looking in here for a Sybase version since several of the versions above didn't work
CAST(CONVERT(DATE,GETDATE(),103) AS DATETIME)
Tested in I SQL v11 running on Adaptive Server 15.7
If possible, for special things like this, I like to use CLR functions.
In this case:
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlDateTime DateOnly(SqlDateTime input)
{
if (!input.IsNull)
{
SqlDateTime dt = new SqlDateTime(input.Value.Year, input.Value.Month, input.Value.Day, 0, 0, 0);
return dt;
}
else
return SqlDateTime.Null;
}
I, personally, almost always use User Defined functions for this if dealing with SQL Server 2005 (or lower version), however, it should be noted that there are specific drawbacks to using UDF's, especially if applying them to WHERE clauses (see below and the comments on this answer for further details). If using SQL Server 2008 (or higher) - see below.
In fact, for most databases that I create, I add these UDF's in right near the start since I know there's a 99% chance I'm going to need them sooner or later.
I create one for "date only" & "time only" (although the "date only" one is by far the most used of the two).
Here's some links to a variety of date-related UDF's:
Essential SQL Server Date, Time and DateTime Functions
Get Date Only Function
That last link shows no less than 3 different ways to getting the date only part of a datetime field and mentions some pros and cons of each approach.
If using a UDF, it should be noted that you should try to avoid using the UDF as part of a WHERE clause in a query as this will greatly hinder performance of the query. The main reason for this is that using a UDF in a WHERE clause renders that clause as non-sargable, which means that SQL Server can no longer use an index with that clause in order to improve the speed of query execution. With reference to my own usage of UDF's, I'll frequently use the "raw" date column within the WHERE clause, but apply the UDF to the SELECTed column. In this way, the UDF is only applied to the filtered result-set and not every row of the table as part of the filter.
Of course, the absolute best approach for this is to use SQL Server 2008 (or higher) and separate out your dates and times, as the SQL Server database engine is then natively providing the individual date and time components, and can efficiently query these independently without the need for a UDF or other mechanism to extract either the date or time part from a composite datetime type.
I would use:
CAST
(
CAST(YEAR(DATEFIELD) as varchar(4)) + '/' CAST(MM(DATEFIELD) as varchar(2)) + '/' CAST(DD(DATEFIELD) as varchar(2)) as datetime
)
Thus effectively creating a new field from the date field you already have.

How to get records after a certain time using SQL datetime field

If I have a datetime field, how do I get just records created later than a certain time, ignoring the date altogether?
It's a logging table, it tells when people are connecting and doing something in our application. I want to find out how often people are on later than 5pm.
(Sorry - it is SQL Server. But this could be useful for other people for other databases)
For SQL Server:
select * from myTable where datepart(hh, myDateField) > 17
See http://msdn.microsoft.com/en-us/library/aa258265(SQL.80).aspx.
What database system are you using? Date/time functions vary widely.
For Oracle, you could say
SELECT * FROM TABLE
WHERE TO_CHAR(THE_DATE, 'HH24:MI:SS') BETWEEN '17:00:00' AND '23:59:59';
Also, you probably need to roll-over into the next day and also select times between midnight and, say, 6am.
In MySQL, this would be
where time(datetimefield) > '17:00:00'
The best thing I can think would be: don't use a DateTime field; well, you could use a lot of DATEADD/DATEPART etc, but it will be slow if you have a lot of data, as it can't really use an index here. Your DB may offer a suitable type natively - such as the TIME type in SQL Server 2008 - but you could just as easily store the time offset in minutes (for example).
For MSSQL use the CONVERT method:
DECLARE #TempDate datetime = '1/2/2016 6:28:03 AM'
SELECT
#TempDate as PassedInDate,
CASE
WHEN CONVERT(nvarchar(30), #TempDate, 108) < '06:30:00' then 'Before 6:30am'
ELSE 'On or after 6:30am'
END,
CASE
WHEN CONVERT(nvarchar(30), #TempDate, 108) >= '10:30:00' then 'On or after 10:30am'
ELSE 'Before 10:30am'
END
Another Oracle method for simple situations:
select ...
from ...
where EXTRACT(HOUR FROM my_date) >= 17
/
http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions050.htm#SQLRF00639
Tricky for some questions though, like all records with the time between 15:03:21 and 15:25:45. I'd also use the TO_CHAR method there.
In Informix, assuming that you use a DATETIME YEAR TO SECOND field to hold the full date, you'd write:
WHERE EXTEND(dt_column, HOUR TO SECOND) > DATETIME(17:00:00) HOUR TO SECOND
'EXTEND' can indeed contract the set of fields (as well as extend it, as the name suggests).
As Thilo noted, this is an area of extreme variability between DBMS (and Informix is certainly one of the variant ones).
Ok, I've got it.
select myfield1,
myfield2,
mydatefield
from mytable
where datename(hour, mydatefield) > 17
This will get me records with a mydatefield with a time later than 5pm.

What's a good way to check if two datetimes are on the same calendar day in TSQL?

Here is the issue I am having: I have a large query that needs to compare datetimes in the where clause to see if two dates are on the same day. My current solution, which sucks, is to send the datetimes into a UDF to convert them to midnight of the same day, and then check those dates for equality. When it comes to the query plan, this is a disaster, as are almost all UDFs in joins or where clauses. This is one of the only places in my application that I haven't been able to root out the functions and give the query optimizer something it can actually use to locate the best index.
In this case, merging the function code back into the query seems impractical.
I think I am missing something simple here.
Here's the function for reference.
if not exists (select * from dbo.sysobjects
where id = object_id(N'dbo.f_MakeDate') and
type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
exec('create function dbo.f_MakeDate() returns int as
begin declare #retval int return #retval end')
go
alter function dbo.f_MakeDate
(
#Day datetime,
#Hour int,
#Minute int
)
returns datetime
as
/*
Creates a datetime using the year-month-day portion of #Day, and the
#Hour and #Minute provided
*/
begin
declare #retval datetime
set #retval = cast(
cast(datepart(m, #Day) as varchar(2)) +
'/' +
cast(datepart(d, #Day) as varchar(2)) +
'/' +
cast(datepart(yyyy, #Day) as varchar(4)) +
' ' +
cast(#Hour as varchar(2)) +
':' +
cast(#Minute as varchar(2)) as datetime)
return #retval
end
go
To complicate matters, I am joining on time zone tables to check the date against the local time, which could be different for every row:
where
dbo.f_MakeDate(dateadd(hh, tz.Offset +
case when ds.LocalTimeZone is not null
then 1 else 0 end, t.TheDateINeedToCheck), 0, 0) = #activityDateMidnight
[Edit]
I'm incorporating #Todd's suggestion:
where datediff(day, dateadd(hh, tz.Offset +
case when ds.LocalTimeZone is not null
then 1 else 0 end, t.TheDateINeedToCheck), #ActivityDate) = 0
My misconception about how datediff works (the same day of year in consecutive years yields 366, not 0 as I expected) caused me to waste a lot of effort.
But the query plan didn't change. I think I need to go back to the drawing board with the whole thing.
This is much more concise:
where
datediff(day, date1, date2) = 0
You pretty much have to keep the left side of your where clause clean. So, normally, you'd do something like:
WHERE MyDateTime >= #activityDateMidnight
AND MyDateTime < (#activityDateMidnight + 1)
(Some folks prefer DATEADD(d, 1, #activityDateMidnight) instead - but it's the same thing).
The TimeZone table complicates matter a bit though. It's a little unclear from your snippet, but it looks like t.TheDateInTable is in GMT with a Time Zone identifier, and that you're then adding the offset to compare against #activityDateMidnight - which is in local time. I'm not sure what ds.LocalTimeZone is, though.
If that's the case, then you need to get #activityDateMidnight into GMT instead.
where
year(date1) = year(date2)
and month(date1) = month(date2)
and day(date1) = day(date2)
Make sure to read Only In A Database Can You Get 1000% + Improvement By Changing A Few Lines Of Code so that you are sure that the optimizer can utilize the index effectively when messing with dates
this will remove time component from a date for you:
select dateadd(d, datediff(d, 0, current_timestamp), 0)
Eric Z Beard:
I do store all dates in GMT. Here's the use case: something happened at 11:00 PM EST on the 1st, which is the 2nd GMT. I want to see activity for the 1st, and I am in EST so I will want to see the 11PM activity. If I just compared raw GMT datetimes, I would miss things. Each row in the report can represent an activity from a different time zone.
Right, but when you say you're interested in activity for Jan 1st 2008 EST:
SELECT #activityDateMidnight = '1/1/2008', #activityDateTZ = 'EST'
you just need to convert that to GMT (I'm ignoring the complication of querying for the day before EST goes to EDT, or vice versa):
Table: TimeZone
Fields: TimeZone, Offset
Values: EST, -4
--Multiply by -1, since we're converting EST to GMT.
--Offsets are to go from GMT to EST.
SELECT #activityGmtBegin = DATEADD(hh, Offset * -1, #activityDateMidnight)
FROM TimeZone
WHERE TimeZone = #activityDateTZ
which should give you '1/1/2008 4:00 AM'. Then, you can just search in GMT:
SELECT * FROM EventTable
WHERE
EventTime >= #activityGmtBegin --1/1/2008 4:00 AM
AND EventTime < (#activityGmtBegin + 1) --1/2/2008 4:00 AM
The event in question is stored with a GMT EventTime of 1/2/2008 3:00 AM. You don't even need the TimeZone in the EventTable (for this purpose, at least).
Since EventTime is not in a function, this is a straight index scan - which should be pretty efficient. Make EventTime your clustered index, and it'll fly. ;)
Personally, I'd have the app convert the search time into GMT before running the query.
You're spoilt for choice in terms of options here. If you are using Sybase or SQL Server 2008 you can create variables of type date and assign them your datetime values. The database engine gets rid of the time for you. Here's a quick and dirty test to illustrate (Code is in Sybase dialect):
declare #date1 date
declare #date2 date
set #date1='2008-1-1 10:00'
set #date2='2008-1-1 22:00'
if #date1=#date2
print 'Equal'
else
print 'Not equal'
For SQL 2005 and earlier what you can do is convert the date to a varchar in a format that does not have the time component. For instance the following returns 2008.08.22
select convert(varchar,'2008-08-22 18:11:14.133',102)
The 102 part specifies the formatting (Books online can list for you all the available formats)
So, what you can do is write a function that takes a datetime and extracts the date element and discards the time. Like so:
create function MakeDate (#InputDate datetime) returns datetime as
begin
return cast(convert(varchar,#InputDate,102) as datetime);
end
You can then use the function for companions
Select * from Orders where dbo.MakeDate(OrderDate) = dbo.MakeDate(DeliveryDate)
Eric Z Beard:
the activity date is meant to indicate the local time zone, but not a specific one
Okay - back to the drawing board. Try this:
where t.TheDateINeedToCheck BETWEEN (
dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, #ActivityDate)
AND
dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, (#ActivityDate + 1))
)
which will translate the #ActivityDate to local time, and compare against that. That's your best chance for using an index, though I'm not sure it'll work - you should try it and check the query plan.
The next option would be an indexed view, with an indexed, computed TimeINeedToCheck in local time. Then you just go back to:
where v.TheLocalDateINeedToCheck BETWEEN #ActivityDate AND (#ActivityDate + 1)
which would definitely use the index - though you have a slight overhead on INSERT and UPDATE then.
I would use the dayofyear function of datepart:
Select *
from mytable
where datepart(dy,date1) = datepart(dy,date2)
and
year(date1) = year(date2) --assuming you want the same year too
See the datepart reference here.
Regarding timezones, yet one more reason to store all dates in a single timezone (preferably UTC). Anyway, I think the answers using datediff, datepart and the different built-in date functions are your best bet.