Convert only the year part of the datetime in SQL server - sql

Any other easier way to bring the year part of a datetime to current year? If not, which one is better performance wise?
Attempt #1:
CAST(DATEPART(MONTH, mydate) AS VARCHAR) + '/' +
CAST(DATEPART(DAY, mydate) AS VARCHAR) + '/' +
CAST(YEAR(GETDATE()) AS VARCHAR) AS DATETIME
Attempt #2:
DATEADD(YEAR, DATEDIFF(YEAR, mydate, GETDATE()), mydate)

The performance differences are negligible, whichever one is faster. The differences will be in the microsecond range.
I normally use DATEADD + DATEDIFF as a matter of preference. The correct format of the VARCHAR version is to use the ISO-8601 format YYYYMMDD, e.g.
CAST(STUFF(CONVERT(char(8),mydate,112),1,4,YEAR(GETDATE())) as DATETIME)
CONVERT(,,112) => YYYYMMDD. STUFF replaces the first 4 characters with current year. YEAR() returns a number but in the context of STUFF is automatically cast as a varchar.

Don't use your first method. It can create unreliable results.
1/2/2012 can be interpreted as February 1st or January 2nd, depending on settings.
Avoid strings, especially for dates.
Your second method is much more reliable

Related

How to subtract one month from a date using SQL Server

I have a date in format dd/mm/yyyy. I want to subtract one month from it.
I am using this code but the output is "09/10/2020" I don't know why my code does the subtraction of the year -2 also.
This is my request
SELECT
FORMAT(CONVERT (DATE, DATEADD(MONTH, -1, CONVERT(char(9), GETDATE()))), 'dd/MM/yyyy')
you need to change it to:
select format(CONVERT (date,DATEADD(MONTH, -1,GETDATE())), 'dd/MM/yyyy' )
but as Larnu stated. it seems like you need to change the column.
Your current code doesn't work as expected because:
SELECT CONVERT(char(9), GETDATE());
Returns this (at least in my language):
Nov 9 20
Which is, unfortunately, and again in my language, a valid date (but in {20}20, not {20}22).
Even in the right style (103), char(9) would yield 10/11/202 tomorrow, since 9 digits is only enough if either the day or month is a single digit.
Don't know why you are converting GETDATE() to a string. Just perform date math on it and then format it if you need to (using a specific style number, e.g. 103 for d/m/y):
SELECT CONVERT(char(10), DATEADD(MONTH, -1, GETDATE()), 103);
I really wouldn't use FORMAT() for such simple output, as the CLR overhead really isn't worth it. Ideally you leave it as a date/time type until presentation time - surely your presentation layer can present your date as d/m/y if that's really a wise idea.
And if you are storing or passing dates as strings (and worse, in regional formats like d/m/y) you really should consider fixing that.
First of all,
You should be storing your Date as a string for easier manipulation. If you don't want to change the column, you can always convert from Date to Varchar and then (re)convert it.
Example:
First, convert Date to varchar using the style code '112' ISO for formatting as yyyyMMdd:
DECLARE #date DATE = GETDATE();
DECLARE #dateConverted as VARCHAR (8) = (SELECT CONVERT(VARCHAR, #date, 112));
Then you just subtract the month using DATEADD():
DECLARE #previousMonth AS VARCHAR (8) = (SELECT FORMAT(DATEADD(month, -1, #dateConverted), 'yyyyMMdd'));
Finally, convert varchar do Date again:
DECLARE #previousMonthConverted AS DATE = (SELECT CONVERT(CHAR(10), CONVERT(date, #previousMonth), 120));

New to SQL Server looking for assistance for a datetime conversion

this is my first submission on Stack Overflow, and I am open to any suggestions on structuring my questions.
I am new to SQL Server, and I have a line of code I that I don't understand.
Can someone please explain this?
declare #prior_year datetime = convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
This is a problematic way to determine the last day of the year before the date(time) stored in some variable.
convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
In English, working outward:
datepart(yy, #start_date)
Tell me the year of the variable #start_date. This is poor form because, if we mean year, we should spell out YEAR (see "Date Parts" in this post). I said this already once today, but this is just laziness, it's like "I'm going to type just enough characters to avoid an unexpected result, but not enough characters to make my intent clear." Worse is YYYY, which I see often - you have a choice between YYYY and YEAR, why not type the one that's actually a word?
datepart(yy, #start_date) - 1
--------------------------- ^^^
Subtract one from that year.I don't have any issues here, but it may be clearer to use an explicit DATEADD() against the variable first, and then extracting the year from that, since things like <some date thing> - 1 can be misread as an attempt to subtract a day (also covered in the shorthand post referenced above).
convert(varchar, datepart(yy, #start_date) - 1))
--^^^^^^^^^^^^^^^^
Convert that explicitly to a string.
Also poor form here because we should always specify the length of variable-width data. In some cases this can lead to unexpected truncation. See this post.
'12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
--^^^^^^^^^^
Prefix that year with 12/31 to produce a mm/dd/yyyy string.
More poor form, because this then assumes the user has US English regional settings, MDY dateformat, etc. If you're going to insist on building a string that represents a date, always use a standard, unambiguous format: YYYYMMDD. (And FWIW YYYY-MM-DD is ambiguous, try it with SET LANGUAGE FRENCH;). See this post.
convert(date, <the rest>)
Converts the whole expression to a date.
A better solution
Ideally we should not be using strings anywhere along the line for any of this. We have built-in functions that provide all kinds of native date handling capabilities without having to worry about languages, regional settings, date format preferences, or string lengths:
SELECT DATEFROMPARTS(YEAR(#start_date) - 1, 12, 31);
-- or, more explicitly:
SELECT DATEFROMPARTS(DATEPART(YEAR, #start_date) - 1, 12, 31);
The other way you can think about this is that the last day of last year is the same day as the day before the first day of this year. Thinking about it in these terms can make it much easier to conceptualize when you are doing things like prior month, where determining the last day of the previous month is more tedious. Or if you are finding endpoints for range queries, because the end of the current reporting period is never as deterministic as the start of the next reporting period. More on that here.
SELECT DATEADD(DAY, -1, DATEFROMPARTS(YEAR(#start_date), 1, 1));
Also of potential use:
Dating Responsibly
Simplify Date Period Calculations in SQL Server
SQL Server DateTime Best Practices
Four short videos in this series
Reading this from the inside out:
We're first finding the year of the date using DATEPART and then subtracting 1 so for a value for today's date: 2020-08-18 we'd be getting an integer value of 19
datepart(yy, '2020-08-18') - 1)
We're then using convert on that value to change it to a varchar:
convert(varchar, 19)
We're then using that new varchar to create a string:
'12/31/' + '19'
Finally we're using convert again to create a date from the string
convert(date, '12/31/19')
Tony,
For completeness please provide the definition and assignment for #start_date. Assuming the statement you provided works, it is probably defined something like
declare #start_date date
set #start_date = '07-01-2020'
Working from the inside out, we can then break the statement down like so ...
This will extract the year value
datepart(yy, #start_date)
This will subtract 1 from the #start_date year value, assuming 2020, this returns 2019
convert(varchar, datepart(yy, #start_date) - 1)
And then this will convert that to the last day of the previous year.
convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
So the statement simply sets the new field to the last day of the prior year.
declare #prior_year datetime = convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
This SQL code declares a #prior_year datetime variable that is hard coded to 12/31/. datepart is used to extract the previous year from another datetime variable #start_date` that is passed in and tacks the returned value onto the end of the '12/31/' hard coded string. So; its really just a formula of 12/31/(#start_date prior year)
So if #start_date is 8/20/2020
You will end up with the output of 12/31/2019
declare #start_date datetime = getdate()
declare #prior_year datetime = convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
select #prior_year
Result:

Convert date format doesn't take effect on self made date string in SQL Server

I have a rather strange issue here. I have a date string, which I've created partly by myself to incorporate a variable. The problem is, that I'm setting another language settings. In this case, I have to also convert the string to fit the language settings format. I'm using this code:
cast(convert(varchar, cast(cast(getdate() as date) as varchar) + ' ' + RIGHT('0' + CAST(#HR as varchar), 2) + ':00:00.000', 120) as datetime)
I get the error "The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.", which is normal if you assign wrong date format before casting.
The problem is, that when I try to convert the personally created date string, it doesn't change its format no matter what format code I set it in. That doesn't change even when I hardcode a number instead of my variable:
convert(varchar, cast(cast(getdate() as date) as varchar) + ' 0' + CAST(2 as varchar) + ':00:00.000', 101)
results in 2016-09-14 02:00:00.000
For example,
convert(varchar, dateadd(Hour, 2, getdate()), 101) as datetime
Results in 09/14/2016.
Even though I have a different language setting, isn't SQL server supposed to always recognize the date format in the standard format?
Please give me an advice so I can overcome this issue.
Thanks in advance!
PS: I managed to solve my issue by inserting converted datetime column in a variable before setting bulgarian language. I'm still very interested to know what causes the problem though.
Ok I may have a soution for the question: Why is the format differently handled in SQL-SERVER when converting.
CONVERT(data_type(length),expression,style)
The STYLEvalue only applies for date/time.
So it's because of the datatype that the output is different.
See following example:
SELECT convert(varchar, dateadd(Hour, 2, getdate()), 101) as datetime
You get the result:
09/14/2016
Here your are converting a datetime datatype into a varchar and the STYLE-value with 101 applies for CONVERT and the output is converted in that format.
Example 2 is the same but the inner most part is casted into a varchar before converting it:
SELECT convert(varchar, CAST(dateadd(Hour, 2, getdate()) AS varchar), 101) as datetime
The result you get is:
Sep 14 2016 4:09PM
So because we are trying to convert a varchar into a varchar the STYLE-value doesn't apply for the conversion.
That is also why the first query is handled diffrent then the other:
SELECT convert(varchar, cast(cast(getdate() as date) as varchar) + ' 0' + CAST(2 as varchar) + ':00:00.000', 101)
Here you cast into varchar cast(cast(getdate() as date) as varchar) before converting. So the STYLE-value is not applying because it's not from datatype date/time.
I hope it made it a bit clear. Let me know if this helped.
When you use convert to format the datetime, you can pass a style number to it.
Looky here or here for those numbers.
The query below converts custom created datetimes to the 126 (ISO8601) format.
declare #d int = 2;
SELECT
CONVERT(varchar,
CONVERT(datetime,
CONCAT(FORMAT(GETDATE(),'yyyy-MM-dd'),' ',#d,':0')
)
,126) AS MyDateStamp1,
CONVERT(varchar,
CONVERT(datetime,
CONVERT(varchar,GETDATE(),102)+' '+convert(varchar,#d)+':0'
)
,126) AS MyDateStamp2;
The FORMAT & CONCAT functions can be used in SQL Server 2012 and beyond.
But if you have an earlier version then CONVERT should work instead.
Additional tip:
If you're using the CONVERT solution above, note that
"convert(varchar, CAST(dateadd(Hour, 2, getdate()) AS varchar), 101)" calls for you to set datatype to varchar.
I just came across code
"Convert(date,ML.StartDate,101)"
and since style 101 is mm/dd/yyyy, and the output was yyyy-mm-dd, I knew something was wrong. By changing the code to
"Convert(varchar,ML.StartDate,101)"
the proper date style was displayed in the result set.

cast an integer converted to varchar and appended with a string to a datetime

I can't figure out why this sql code snippet does not work:
cast(cast(b.remodelyear as varchar(5)) + '-01-01' as datetime)
Remodel year is an integer consisting of a year (ex: 2012). I cast it to varchar and append a month and a day to it and then I cast the whole thing to a datetime.
This one works:
cast(cast(Yr as varchar(5)) + '-' + cast(Mth as varchar(5)) + '-' + '01' as datetime)
Where did I go wrong?
Thanks!
Personally, I wouldn't concatenate strings for this, I'd perform some (trivial) date math:
SELECT DATEADD(year, b.remodelyear - 1, '00010101')
FROM <sometable> b
(note that I'm assuming SQL Server - read this blog post for the reasoning behind the date format. If you're using a different RDBMS this'll need to be translated).
I'm curious as to why you're casting to varchar(5), as pretty much every RDBMS I'm aware of would throw an error if it encountered a fifth digit.

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.