How to set part of a datetime - sql

I need to set just the hour of a datetime to a certain value. I know I can add it the difference to the desired value, or create a new datetime with parts from the original one, but isn't there a cleaner way? Something like:
declare #d datetime = '09/08/2012 09:14:55'
set #d = SETDATEPARTORSOMETHINGLIKETHAT (hour, #d, 23)
Thanks a lot.

DECLARE #TargetHour TINYINT = 23;
DECLARE #d datetime = '09/08/2012 09:14:55';
SET #d = DATEADD(HOUR, #TargetHour - DATEPART(HOUR, #d), #d);
SELECT #d;
Result:
2012-09-08 23:14:55.000
(And yes, this will work if the #TargetHour is less than the current hour, e.g. 5 or 7.)
I'd be very careful though about using ambiguous and error-prone formats like m/d/y. Case in point: I don't even know if you meant September 8th or August 9th, and SQL Server isn't going to know either - it needs to use regional settings etc. to figure it out, and if you give your code to someone with different settings, it will generate an error or, even worse, accept the wrong date silently. You should use:
DECLARE #d datetime = '2012-09-08T09:14:55';
Bad habits to kick : mis-handling date / range queries

Related

Is there a more efficient method of getting end of day?

I have a DATETIME variable #DateEnd = '1/4/2011 16:43:22 PM'. I want to get the end of day: 1/4/2011 23:59:59.997.
I have the following and it works fine, but it has lots of conversions and doesn't seem efficient:
DECLARE #DateString VARCHAR(25)
DECLARE #DateEnd DATETIME = '1/4/2011 16:43:22 PM'
SET #DateString = CONVERT(VARCHAR(10), #DateEnd, 101) + ' 23:59:59.997'
SET #DateEnd = CAST(DateString AS DATETIME)
Is there a more efficient method of accomplishing this?
Typically what we do is get the 0/midnight value for the start of the next day, and then use an exclusive inequality boundary (<) for that end of the range instead of an inclusive equality boundary (<=).
DECLARE #DateEnd DATETIME = '20110104 16:43:22'
SET #DateEnd = DATEADD(day, 1, Cast(#DateEnd as Date))
It's also NEVER okay to format a datetime literal for SQL like in the question. Different cultures and languages have their own norms and expectations around how dates should look, and it's not good to force your own personal or cultural norms into those languages. SQL, as its own language, is no different. If you're writing SQL and not using SQL's format for the date values (yyyyMMdd, yyyy-MM-ddTHH:mm:ss[.fff], or yyyyMMdd HH:mm:ss[.fff]), you have a WRONG format for that context.
For example, my own preferred format, coming to SQL later after first learning a few other (procedural) languages, is yyyy-MM-dd HH:mm:ss, which doesn't quite match those acceptable formats. I admit I've used that format quite a lot over the years; you can probably find examples in my answers here on Stack Overflow. But every place where I did that was wrong, and I no longer use it. So don't take it too hard; everybody has to learn this.
Probably not more efficient, but I am a fan of using the correct functions for this sort of thing.
Also using the correct datatype, datetime2 is the recommended datatype to use these days, and removes the pesky 3ms interval.
DECLARE #DateEnd DATETIME2(3) = '1/4/2011 16:43:22 PM';
SET #DateEnd = DATEADD(millisecond, -1, CAST(DATEADD(day, 1, CAST(#DateEnd AS DATE)) AS DATETIME2(3)));
One little cheat is format(). However, if #DateEnd is being used as a date range, you may be better off using < TheNextDay
Declare #DateEnd datetime = '1/4/2011 16:43:22 PM'
Set #DateEnd = format(#DateEnd,'yyyy-MM-dd 23:59:59.997')
Just another option avoiding Format()
Declare #DateEnd datetime = '1/4/2011 16:43:22 PM'
Set #DateEnd = concat(convert(date,#DateEnd),' 23:59:59.997')

Query: Assigning error datetime2

I want to assign '1392-04-31' using this code:
DECLARE #t DATETIME
SET #t = '92-04-31'
I see this error:
Conversion failed when converting date and/or time from character string.
Any one know why?
The solution is:
use datetime2!
DECLARE #t datetime2
SET #t = '1392-04-30'
Because you can't use datetime:
The minimum date stored in datetime is January 1, 1753. So 1392 is not storeable.
April has 30 days.
Using formatted date with datetime:
Second, when you write a date in Sql Server, the format I prefer is {d 'YYYY-MM-DD'}, so in your case becomes:
DECLARE #t DATETIME
SET #t = {d '1992-04-30'}
To complete this discussion, if you want use hh mm ss so you must use this format: {ts 'yyy-mm-dd hh:mm:ss.mmm'}
DECLARE #t DATETIME
SET #t = {ts '1992-04-30 23:59:59.123'}
try this :
declare #t DATETIME
set #t = '1992-04-30 10:54:30'
The date you are trying to set is probably invalid.
Also there are several ways of representing dates in SQL as a string, depending on Language, Dateformat and other setting. Typically the safest way to do this is to use the 'YYYYMMDD' format.
The article below will also answer the question : Why is 1753 the earliest date for datetime?
You should read this if you would like some detailed information:
http://karaszi.com/the-ultimate-guide-to-the-datetime-datatypes
First of all, April only has 30 days. I'm not going to take the time to look up historically whether that was the case in 1392, but either way I'm pretty sure the date 4/31/1392 is invalid for a SQL Server DATETIME.
Also, you should use the full year in the format '01-01-2013'.
Try the following and you'll get the output Jan 1 2013 12:00AM.
declare #t DATETIME
set #t = '01-01-2013'
PRINT #t
The above should work for any valid date.

SQL - Date Query Issue - varchar to datetime conversion resulted in out-of-range value

I have a SQL query written by a colleague who is no longer here. The query runs as part of an SSIS job, and as of this month has started failing with the following error:
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
The query itself is just a basic select with a where clause that looks for values in a certain time range (the date range between #startdate and #enddate)
The code to determine the time range is below:
DECLARE #RunDateTime datetime
DECLARE #RunDate datetime
DECLARE #StartDate datetime
DECLARE #EndDate datetime
DECLARE #Month int
DECLARE #Year int
DECLARE #strStartDate varchar(10)
SET #RunDateTime = GetDate()
SET #RunDate = cast(round(convert(real, #RunDateTime),0,1) as datetime)
IF DATEPART(d, #RunDate) = 16
BEGIN
SET #StartDate = DATEADD(d, -15, #RunDate)
SET #EndDate = #RunDate
END
ELSE
BEGIN
IF Month(#RunDate) = 1
SET #Month = 12
ELSE
SET #Month = Month(#RunDate) - 1
IF Month(#RunDate) = 1
SET #Year = Year(#RunDate) - 1
ELSE
SET #Year = Year(#RunDate)
SET #strStartDate = CONVERT(varchar(2), #Month)+ '/16/' + CONVERT(varchar(4), #Year)
SET #StartDate = CONVERT(datetime, #strStartDate, 101)
SET #EndDate = #RunDate
END
This job runs twice a month. Once on the 16th for data from the 1st to the 15th of the month, and once on the 1st of the next month for data from the 16th to the end of the previous month.
From what I can find online, the use of the varchar for strStartDate is the likely culprit? I'm not familiar enough with SQL to know how to replace all that convert stuff that's going on there? Also, is there a better way to determine the end of the month date than just getting the run time? Any help at all would be greatly appreciated.
(PS, we run this job on SQL Server 2008 R2) And I checked with the DBA and he said nothing about localization or regional settings has changed on the SQL server.
There are many formats supported by SQL Server - see the MSDN Books Online on CAST and CONVERT. Most of those formats are dependant on what settings you have - therefore, these settings might work some times - and sometimes not.
The way to solve this is to use the (slightly adapted) ISO-8601 date format that is supported by SQL Server - this format works always - regardless of your SQL Server language and dateformat settings.
The ISO-8601 format is supported by SQL Server comes in two flavors:
YYYYMMDD for just dates (no time portion); note here: no dashes!, that's very important! YYYY-MM-DD is NOT independent of the dateformat settings in your SQL Server and will NOT work in all situations!
or:
YYYY-MM-DDTHH:MM:SS for dates and times - note here: this format has dashes (but they can be omitted), and a fixed T as delimiter between the date and time portion of your DATETIME.
This is valid for SQL Server 2000 and newer.
If you use SQL Server 2008 or newer and the DATE datatype (only DATE - not DATETIME!), then you can indeed also use the YYYY-MM-DD format and that will work, too, with any settings in your SQL Server.
Also: with SQL Server 2008, it is recommended to use DATETIME2 (instead of DATETIME) if at all ever possible. DATETIME2 parsing of strings is a lot more forgiving for error and/or different formats (like US AM/PM formatting etc.) .
Don't ask me why this whole topic is so tricky and somewhat confusing - that's just the way it is. But with the YYYYMMDD format, you should be fine for any version of SQL Server and for any language and dateformat setting in your SQL Server.

convert int values to datetime in sql server

I need to convert the int values of a year, month and day in a datetime using sql server.
actually i' am using this
DECLARE #MONTH INT
DECLARE #YEAR INT
DECLARE #DAY INT
DECLARE #MAXDATE DATETIME
DECLARE #MINDATE DATETIME
SET #MONTH=12
SET #YEAR=2010
SET #DAY=1
SET #MINDATE=CONVERT(DATE,CAST (#YEAR AS VARCHAR)+ RIGHT ('0'+ CAST (#MONTH AS VARCHAR),2) + RIGHT ('0'+ CAST (#DAY AS VARCHAR),2))
SELECT #MINDATE
and works ok, but i'm wondering if exist a better way to convert these values.
This would be a little simpler:
select dateadd(month,(#YEAR-1900)* 12 + #MONTH - 1,0) + (#DAY-1)
I think you definitely have the right way to do this. You're already padding the zeros for single digit months and dates; your solution will work well and is not untidy.
The only other way would be to being with some arbitrary date, and 'add' years, months and days to that starting point. However this is not a great plan, because you would need to start at 31 December, 0 AD by my reckoning. That makes less sense than simply parsing the components, as you are currently doing.
Depending on what you mean by 'better way', you may find different answers acceptable.
For example, converting from a string could be simpler (and thus better) than how it is done in your script. Like this:
SET #MINDATE = CAST(#YEAR AS varchar) + '-' +
CAST(#MONTH AS varchar) + '-' +
CAST(#DAY AS varchar)
That is, if your variable is datetime, the conversion will be implicit, so no need to use CONVERT. The format chosen here is YYYY-MM-DD, or rather YYYY-M-D, which is, as far as I know, acceptable for implicit conversion regardless of locale settings.
EDIT: The format you've chosen, YYYYMMDD, is locale independent too, if I'm not mistaken. My point was only that it required a more complex expression to build the proper string from integers, which seemed to inconvenience you.
Untill i know there´s no way con convert it with a built in function.
If it´s possible to declare the year, month anda day as varchar, it can be easier:
SET DATEFORMAT YMD;
SET #MAXDATE = #YEAR+'/'+#MONTH+'/'+#DAY
SELECT #MAXDATE

Limiting a date range with exactness in MS SQL / SQL Server 2005

I want to limit a report to return records from Date A through Date B. This is what I have been doing:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008'
set #endDate = '04/01/2008'
-- test what are the start and end dates
select min(date),max(date) from view_Inspections
where date between #startDate and #endDate
... which I was told returned records from 12 am Jan 1st through 11:59 pm March 31st (that midnight is the default when no time is indicated). But I noticed a discrepancy, which is if a record has a time of 00:00:00 that it will be part of this set.
Is there a more exact way of doing this so it will return exactly the date range I want?*
I tried using time:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008 00:00:00'
set #endDate = '04/01/2008 11:59:59'
-- test what are the start and end dates
select min(date),max(date) from view_Inspections
where date between #startDate and #endDate
... but I noticed something wonky: SQL Server will ROUND the hundreth-second up by half. So I get that April 1st record (ha! April Fool's record! grr) if I use any time later than 11:59:29. Why is that?
(I feel sure there is. I'm new at this. Thanks for your help!)
There's always the easy option:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008'
set #endDate = '04/01/2008'
-- test what are the start and end dates
select min(date),max(date) from view_Inspections
where date >= #startDate
and date < #endDate
I suspect that the date column in view_Inspections is a SmallDateTime data type. This data type has 1 minute accuracy, which explains your unexpected results (rounding the seconds to the nearest minute).
The method Roland Shaw suggests is the best way to modify your query to accommodate your requirements.
The BETWEEN operator is inclusive, which is why you're seeing the results that you are in your first query. The rounding that you're seeing in your second query is going to be dependent on what exact datetime data type you are using in your table. (BTW, I think you're confusing seconds with hundredths of seconds). It looks like you're probably using a smalldatetime in your table, in which case the time is rounded to the nearest minute.
If your table is using datetime, try explicitly converting your #startDate and #endDate to DATETIME values (CAST(#endDate AS DATETIME)).
A quick note... even for DATETIME values, SQL Server is only accurate to the 3/100ths of a second, so 11:59:59.999 will get rounded up to 12:00:00.000.
You basically have three choices:
1) BETWEEN CAST('01/01/2008 00:00:00.000' AS DATETIME) AND CAST('03/31/2008 12:59:59.997' AS DATETIME)
2) YEAR(my_date) = 2008 AND MONTH(my_date) BETWEEN 1 AND 3
3) my_date >= CAST('01/01/2008 00:00:00.000' AS DATETIME) AND my_date < CAST('04/01/2008 00:00:00.000' AS DATETIME)
The first method isn't very intuitive and is error-prone in my opinion. The second method kills performance since indexes can't be used and it becomes much more complex if you can have searches that span years or begin/end in the middle of months. The third method, which Rowland suggested, is the best I think.
Simply try removing the time from the date field like so:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008'
set #endDate = '04/01/2008'
SELECT min(date),max(date) FROM view_Inspections
WHERE CAST(FLOOR(CAST(date AS FLOAT)) AS DATETIME) BETWEEN CAST(#startDate AS DATETIME) And CAST(#startDate AS DATETIME))
This will return everything from 01/01/2008 00:00:00 to 04/01/2008 11:59:59.999.
If you don't want 04/01 included, change your end date to 03/31/2008.
Your best solution is just create a BIGINT(10) field that called "julian", and store it in YYYYMMDD.
Then do the query
where julian >= '20120103' AND julian <= '20120203'