Difference in DATEPART MILLISECONDS from COMPATIBILITY_LEVEL 120 to 130 - sql-server-2016

I've got a SQL Server 2016 database running in compatibility level 120. When I run the following query:
ALTER DATABASE testdb SET COMPATIBILITY_LEVEL = 120
DECLARE #d VARCHAR(30) = '2019-11-05 07:31:47.677'
DECLARE #dt DATETIME = #d
SELECT #d as [#d], #dt as [#dt], DATEPART(MILLISECOND, #d) as [#ms_d], DATEPART(MILLISECOND, #dt) as [#ms_dt]
I get the expected result:
#d #dt #ms_d #ms_dt
------------------------------ ----------------------- ----------- -----------
2019-11-05 07:31:47.677 2019-11-05 07:31:47.677 677 677
However, when I change the compatibility level to 130, I get:
#d #dt #ms_d #ms_dt
------------------------------ ----------------------- ----------- -----------
2019-11-05 07:31:47.677 2019-11-05 07:31:47.677 677 676
Any thoughts as to why I'm getting 676 under compatibility level 130?
I've reviewed the Changes in compatibility level 130, and see the rounding changes involving datetime conversions, but nothing appears to me to explain this.
Thanks for your help.

Related

How do I stop SQL Server swapping the month and day?

I have the following query:
DECLARE #someDateTime DATETIME = '2019-12-01 00:00:00.000'; -- 1st December 2019
SELECT #someDateTime;
Result
2019-01-12 00:00:00.000
I expected the result to be 2019-12-01 00:00:00.000 (1st December 2019) - The month and the date swap for some unknown reason.
Until recently, I never had an issue with this format.
How do I enter the date in the format "YYYY-MM-DD HH:mm:ss.000", and have it maintain that format once assigned to a variable/displayed in a select?
What setting determines this format that may have changed?
Potentialy useful information
dbcc useroptions
Result:
Set Option | Value
---------- -----
...
language | British
dateformat | dmy
...
Things I tried:
Query 1
Chaning the date to something that would be invalid if swapped
DECLARE #someDateTime DATETIME = '2019-12-20 00:00:00.000';
SELECT #someDateTime;
Result:
Msg 242, Level 16, State 3, Line 4
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
Query 2
Setting the variable after it's been declared
DECLARE #someDateTime DATETIME;
SET #someDateTime = '2019-12-01 00:00:00.000';
SELECT #someDateTime;
Result - UNDESIRED:
2019-01-12 00:00:00.000
Query 3
Inserting the variable into a table variable
DECLARE #someDateTime DATETIME = '2019-12-01 00:00:00.000';
DECLARE #someTable TABLE (someDateTimeColumn DATETIME);
INSERT #someTable VALUES (#someDateTime);
SELECT * FROM #someTable
Result - UNDESIRED:
someDateTimeColumn
------------------
2019-01-12 00:00:00.000
Query 4
Inserting the data directly into a table variable
DECLARE #someTable TABLE (someDateTimeColumn DATETIME);
INSERT #someTable VALUES ('2019-12-01 00:00:00.000');
SELECT * FROM #someTable
Result: UNDESIRED
someDateTimeColumn
------------------
2019-01-12 00:00:00.000
Query 5
Changing the format of the entered string
DECLARE #someDateTime DATETIME = '01/12/2019';
SELECT #someDateTime;
Result - DESIRED
2019-12-01 00:00:00.000
Query 6
Changing the format of the entered string
DECLARE #someDateTime DATETIME = '2019-12-01T00:00:00.00';
SELECT #someDateTime;
Result - DESIRED:
2019-12-01 00:00:00.000
Query 7
Changing the format of the entered string
DECLARE #someDateTime DATETIME = '2019-12-01';
SELECT #someDateTime;
Result - UNDESIRED:
2019-01-12 00:00:00.000
Query 8
Using SET DATEFORMAT
SET DATEFORMAT ymd
DECLARE #someDateTime DATETIME = '2019-12-01';
SELECT #someDateTime;
Result - DESIRED
2019-12-01 00:00:00.000
You may use the literal format YYYYMMDD which is always intepreted as year-month-day, regardless of the locale settings of your SQL Server:
DECLARE #someDateTime DATETIME = '20191201 00:00:00.000';
One solution is to use the full ISO8601 format, ie 2019-12-01T00:00:00.000, another to use the unseparated date format, ie 20191201 00:00:00.000. A better solution though would be to switch to datetime2.
datetime2 was introduced 15 (or was it 11) years ago to get rid of datetime's quirks, like the millisecond inaccuracy, arbitrary precision, weird arithmetic and ... parsing idiosyncrasies. For example, datetime2's parsing of the ISO8601 format isn't affected by DATEFORMAT :
SET DATEFORMAT ydm
DECLARE #someDateTime DATETIME2(0) = '2019-12-01 00:00:00.000'
select #someDateTime
------
2019-12-01 00:00:00
This secures your code from unfortunate server setting modifications
After reading this article from #SMor's comment, a comment my colleague made from a google search, and remembering something I did a few months ago, I think I've worked out what's happening and what's changed.
My db user was, by default, set to:
language | us_english
dateformat | mdy
So when reading the string '2019-12-01' SQL Server was expecting mm-dd-yyyy.
SQL server is clever though. It sees the first part 2019 and realises that it's actually the year, so it shifts the year to the end, then tries again.*
It now has 12-01-2019 which matches the format it's expecting.
When you open your account properties in SSMS, the language drop down defaults to the first language in the list (Arabic).
I happend to be in there a few months ago for something unrelated and decided, to make sure I didn't accidently set it to Arabic, I'd change that as well.
I chose British.
My db user is now set to:
language | British
dateformat | dmy
So when reading the string '2019-12-01' SQL Server is expecting dd-mm-yyyy.
Once it's shifted the year, it becomes 12-01-2019 which it interprets as dd-mm-yyyy thus leaving me with January 12th 2019 instead of December 1st 2019
Today has been a long day.
*Please note this is a rather simplified explanation of how I understand this issue. This may not be how it actually functions in reality but works to satisfy my curiosity on this issue.

Compare 2 dates with diff datatypes

I have a temp_table where I have to update status to Invalid date if Create_date is a future date.
Following is the update statement I am using :
Set dateformat dmy
UPDATE [Temp_table]
SET [Status]= 'InvalidCreateDate-Rejected'
WHERE CAST(Create_date AS DATETIME) >=getdate() and isdate(create_date)=1
But whenever I execute it I get error message as:
Msg 242, Level 16, State 3, Line 1 The conversion of a varchar data
type to a datetime data type resulted in an out-of-range value. The
statement has been terminated.
I tried converting Getdate and Create_date in diff formats and then compared still the same issue.
Sample Create_Dates:
05/15/1800
04/06/2011
23/04/2015
13/08/2016
02/21/2017
15/06/2017
Any solution on this?
your cast of create_date is coming across an invalid date and causing the error. the is_date function is a good idea, but it isn't going to prevent the cast from hitting an error earlier.
Set dateformat dmy
UPDATE [Temp_table]
SET [Status]= 'InvalidCreateDate-Rejected'
WHERE CAST(case when isdate(create_date)=1 then Create_date else null AS DATETIME) >=getdate()
You can use a date purposefully in the past instead of the null if the null causes issues, but casting a null to date and comparing it to a date should result in a false and have it removed from results
However, you could use case expression which could handle your different date formats
SELECT CONVERT(DATETIME, Create_Dates,
CASE
WHEN CAST(SUBSTRING(Create_Dates, 4, 2) AS INT) > 12
THEN 101
ELSE 103
END);
Result :-
1800-05-15 00:00:00.000
2011-06-04 00:00:00.000
2015-04-23 00:00:00.000
2016-08-13 00:00:00.000
2017-02-21 00:00:00.000
2017-06-15 00:00:00.000

SQL Server 2014 insert/update smalldatetime value with seconds

I'm having a strange issue with the smalldatetime data type in SQL Server.
I have a very basic table
create table datetest (
value smalldatetime not null
)
And when I run the following
insert into datetest
values ('2016-12-29 21:30:00');
I see the value is 2016-12-29 21:30:00
Then when I run the following
update datetest
set value = '2016-12-29 21:31:30'
I see the value is 2016-12-29 21:31:00
It did not include the seconds. Why is this?
This is happening because precision of smalldatetime is 1 minute. It discards any seconds in datetime value by rounding off. For e.g:
'2014-10-10 12:13:29' is rounded off to '2014-10-10 12:13:00' and '2014-10-10 12:13:30' is rounded off to '2014-10-10 12:14:00'
This is one of the characteristics of smalldatetime over datetime.
Microsoft documentation on smalldatetime
The main differentation is that it rounds to the nearest minute. If you want to see seconds (and milliseconds) then you need to consider the datetime data type.
In your example however, it should return the value 2016-12-29 21:32:00 because it rounds up from 30 seconds to the next minute. anything less than 30 seconds gets rounded down. Example;
CREATE TABLE #DateTest (ID int, DateValue smalldatetime)
INSERT INTO #DateTest (ID, DateValue)
VALUES
(1,'2016-12-29 21:31:29')
,(2,'2016-12-29 21:31:30')
SELECT * FROM #DateTest
Output
ID DateValue
1 2016-12-29 21:31:00
2 2016-12-29 21:32:00
Some further reading links;
http://blog.sqlauthority.com/2010/06/01/sql-server-precision-of-smalldatetime-a-1-minute-precision/
http://sqlcoach.blogspot.co.uk/2007/08/sql-server-storing-time-coming-soon.html
http://sqlhints.com/2016/10/10/difference-between-smalldatetime-and-datetime-data-types-in-sql-server/
When the conversion is to datetime, the smalldatetime value is copied to the datetime value. The fractional seconds will make next nearest minutes. The following code shows the results of converting a smalldatetime value to a datetime value.
DECLARE #smalldatetime smalldatetime = '1955-12-13 12:43:10';
DECLARE #datetime datetime = #smalldatetime;
SELECT #smalldatetime AS '#smalldatetime', #datetime AS 'datetime';
--Result
--#smalldatetime datetime
------------------------- -----------------------
--1955-12-13 12:43:00 1955-12-13 12:43:00.000
Take a look MSDN Link
It rounds all seconds to minutes:
Time range:
00:00:00 through 23:59:59
2007-05-09 23:59:59 will round to
2007-05-10 00:00:00
see chapter smalldatetime Description: https://msdn.microsoft.com/en-us/library/ms182418(v=sql.120).aspx

NVARCHAR TO DATETIME conversion

I have a parameter that has a value of 14-Sep-2012 15:47:27 that I would like to update a table column with which is in 2012-08-10 05:00:00.000 format.
What would be the query needed '
#UpdateTime = '14-Sep-2012 15:47:27'
Update tbl
Set Datetimecol = CONVERT(datetime,#UpdateTime,110) ??
I am using SQL Server 2008. Thank you !
For the edited question, you only need to drop the 110 specification. There really isn't a specification for the format you have shown, but English installations of SQL Server will convert it.
e.g.
declare #UpdateTime datetime = '14-Sep-2012 15:47:27'
select CONVERT(datetime,#UpdateTime)
-- result
September, 14 2012 15:47:27
Assuming your month portion is at least 3 characters long, e.g. Mar, Marc, March, Sept, you can convert that very bad text datetime to a normal 3-char month format using the following
declare #updatetime nvarchar(20) = '18-Sept-2012'
declare #fixedtime nvarchar(20)
set #fixedtime = stuff(#updatetime,1,charindex('-',#updatetime),'')
set #fixedtime = Left(#updatetime,charindex('-',#updatetime))
+ stuff(#fixedtime,4,len(#fixedtime)-8,'')
-- #fixedtime contains `18-Sep-2012`
Update tbl
Set Datetimecol = #fixedtime
Yes, I deliberately left out the CAST/CONVERT in the update statement.
As long as your language settings are always English and your regional settings don't change, here is another approach (along with sample data of various potential formats):
DECLARE #x TABLE(y NVARCHAR(15));
INSERT #x VALUES('18-Sept-2012'),('9-May-2012'),('19-Oct-2012'),('04-March-2012');
SELECT z, CONVERT(DATETIME,z) FROM
(
SELECT SUBSTRING(y,s+1,3) + ' ' + LEFT(y,s-1) + ', ' + RIGHT(y,4) FROM
(
SELECT y, CHARINDEX('-',y) FROM #x
) AS y(y,s)
) AS z(z);
Results:
Sep 18, 2012 2012-09-18 00:00:00.000
May 9, 2012 2012-05-09 00:00:00.000
Oct 19, 2012 2012-10-19 00:00:00.000
Mar 04, 2012 2012-03-04 00:00:00.000
You can use the same calculation for a variable:
DECLARE
#y NVARCHAR(15) = N'18-Sept-2012',
#z DATETIME;
SELECT #z = CONVERT(DATETIME,z) FROM
(
SELECT SUBSTRING(y,s+1,3) + ' ' + LEFT(y,s-1) + ', ' + RIGHT(y,4) FROM
(
SELECT #y, CHARINDEX('-',#y)
) AS y(y,s)
) AS z(z);
SELECT #z;
-- UPDATE ...
(Now try with SET LANGUAGE FRENCH; and watch it all go to, well, somewhere.)
For this reason I highly recommend you stop storing / passing dates using the nvarchar type. We have strongly typed date/time types for a reason. When you need to pass a string literal, you should be using unambiguous, standard formats that aren't prone to differences in language, regional and dateformat settings. In this case the right format should be YYYYMMDD:
20120918
For more info, see:
Bad habits to kick : mis-handling date / range queries

SQL cast datetime

In SQL Server 2005, why does:
PRINT Cast('' AS datetime)
display:
Jan 1 1900 12:00AM
I would have thought it should be null?
It's because empty string '' is not NULL. If you do:
select Cast(null AS datetime)
OUTPUT:
-----------------------
NULL
(1 row(s) affected)
CAST and CONVERT (Transact-SQL)
When character data that represents
only date or only time components is
cast to the datetime or smalldatetime
data types, the unspecified time
component is set to 00:00:00.000, and
the unspecified date component is set
to 1900-01-01.
The empty string is casted to 0 which is later casted to the era date.
Unlike Oracle, SQL Server distinguishes between NULL and an empty string.
From experimentation, it looks like SQL Server attempts to cast directly to DateTime, and failing that attempts to cast to int and then to DateTime:
PRINT Cast('2009-1-1' AS datetime)
go
PRINT Cast('2009/1/1' AS datetime)
go
PRINT Cast('1.1' AS datetime)
go
PRINT Cast('1/2009/1' AS datetime)
go
PRINT Cast('' AS int)
go
PRINT Cast(' ' AS int)
go
PRINT Cast(0 AS datetime)
go
PRINT Cast('X' AS datetime)
go
PRINT Cast('X' AS int)
Output:
Jan 1 2009 12:00AM
Jan 1 2009 12:00AM
Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.
Jan 1 2009 12:00AM
0
0
Jan 1 1900 12:00AM
Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'X' to data type int.