SQL getdate() - not the same in one statement - sql-server

The getDate() statement always returns the same value anywhere in one statement.
However, in one SQL Server 2017, I'm seeing otherwise.
To set this up, create a table and put two rows into it:
CREATE TABLE Test
(
TestDate datetime2(0) NULL,
OtherValue varchar(5) NULL
)
INSERT INTO Test (OtherValue) VALUES ('x')
INSERT INTO Test (OtherValue) VALUES ('x')
Then run this query a number of times:
SELECT
CASE
WHEN GETDATE() < COALESCE(TestDate, GETDATE())
THEN 'less'
WHEN GETDATE() > COALESCE(TestDate, GETDATE())
THEN 'greater'
ELSE 'same'
END [Compare]
FROM
Test
Both rows always return matching results.
When I do this in SQL Server 2008 R2 (v10.50) and other SQL Server 2017 machines, the result is always 'same'.
However, on one of my SQL Server 2017 instances, it varies randomly between 'same', 'less' and 'greater':
Why is this happening? Is there a server setting that can cause this?
Edit:
Using SYSDATETIME in place of GETDATE works as expected on the 'bad' server, always returning 'same'.
Edit #2:
If I test GETDATE as above on a column defined as DATETIME (which is what GETDATE() generates), then it works as expected. So it seems to be related to converting between DATETIME and DATETIME2.

Interesting enough question.
The behaviour in your example can be explaned by the following:
Since SQL Server 2016, datetime rounding have been changed. In short: since 2016 SQL Server, value doesn't round before comparison and comparison executes with raw value. Before 2016 SQL Server, value is rounded and then compare.
By default, comparison datetime and datetime2 performs with conversion datetime to datetime2(7). You can see that in execution plan.
datetime variable with 3 at the end - for example .003 - gets converted in .0033333. 007 gets converted in .0066667.
And the most interest part: nanoseconds. During comparison SQL Server uses 8 (or more!) digits in fractional part. I just show two examples to explane.
DECLARE #DateTime datetime = '2016-01-01T00:00:00.003';
DECLARE #DateTime2 datetime2(7) = #DateTime;
select datepart(NANOSECOND,#DateTime) as "DateTimeRes",
datepart(nanosecond,#DateTime2) as "DateTime2Res"
go
DECLARE #DateTime datetime = '2016-01-01T00:00:00.007';
DECLARE #DateTime2 datetime2(7) = #DateTime;
select datepart(NANOSECOND,#DateTime) as "DateTimeRes",
datepart(nanosecond,#DateTime2) as "DateTime2Res"
Results:
+-------------+--------------+
| DateTimeRes | DateTime2Res |
+-------------+--------------+
| 3333333 | 3333300 |
+-------------+--------------+
+-------------+--------------+
| DateTimeRes | DateTime2Res |
+-------------+--------------+
| 6666666 | 6666670 |
+-------------+--------------+
I took it all from this article.
Also, there is a similar question on SO.
I believe this behaviour is independent of your server repformance (virtual machine or etc).
Good luck!

Turns out the behaviour of getdate changed from SQL 2000 to SQL 2005.
See https://stackoverflow.com/a/3620119/32429 explaining the old behaviour:
In practice, GETDATE() is only evaluated once for each expression
where it is used -- at execution time rather than compile time.
However, Microsoft puts rand() and getdate() into a special category,
called non-deterministic runtime constant functions.
and the following discussion:
In SQL 2000 if you did something like this
INSERT INTO tbl (fields, LOADDATE) SELECT fields, GETDATE() FROM tblb
you would get the same date/time for all records inserted.
This same command In SQL 2005, reruns GETDATE() for every single
record selected from tblb and gives you potentially unique values for
each record. Also causes HUGE performance problems if you are
inserting say, 17 million rows at a time.
This has caused me many a headache, as we use this code to do batch
date/times in many tables. This was a very simple way to back out a
"batch" of transactions, because everything had the same date/time.
Now in 2005, that is not true.

Related

Datetime value in Excel unexpectedly changes when imported into SQL Server 2012

I have an issue with my data import from Excel to SQL Server. The datetime value being imported into the destination table is different from the datetime value in the Excel source file.
With or without any formatting the value is always .003 milliseconds less than the actual time in Excel. This causes values that should be marked for 1 AM to be marked for 12 AM when attempting to GROUP BY hour.
Notice my sample query & results to see the exact values.
If someone could tell me why this is happening and how to get my expected results it would be greatly appreciated.
I would also like to resolve this without any additional steps. (No staging tables please)
SELECT
Timestamp,
CAST(Timestamp AS DATE) Date,
CAST(Timestamp AS DATETIME) Datetime,
CAST(Timestamp AS DATETIME2) Datetime2,
CAST(Timestamp AS TIME) Time
FROM
OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0 Xml;HDR=Yes;
Database=\\server\share\160322.xlsx;',
'SELECT * FROM [160322$]')
/* Query Results (ALL WRONG):
Timestamp : 2016-03-22 00:59:59.997 -- Imported Value without formatting
Date : 2016-03-22 -- Formatted Values
Datetime : 2016-03-22 00:59:59.997
Datetime2 : 2016-03-22 00:59:59.9970000
Time : 00:59:59.9970000
*/
Value in Excel:
3/22/2016 12:15:00 AM
Value in SQL Server table:
2016-03-22 00:14:59.997
Expected SQL Server value:
2016-03-22 00:15:00.000
Value in Excel:
3/22/2016 01:00:00 AM
Value in SQL Server table:
2016-03-22 00:59:59.997
Expected SQL Server value:
2016-03-22 01:00:00.000
The DATETIME datatype in SQL Server has an accuracy of 0.003 seconds - 3.33 milliseconds - that's a well-known and documented fact (see here on MSDN and here a blog post ).
You only get values like .000, .003, .007, .010, .013 etc. - DATETIME does not support values down to the millisecond.
However, using DATETIME2(3) should fix that problem (unless the importing from Excel using OPENROWSET somehow mangles that)

Why can't I use a datetime parameter in ssrs?

I have an SSRS Date/Time parameter generated from a shared dataset query against a SQL Server datetime field. The parameter displays correctly in a report textbox but it will not work in an embedded dataset query, even against the same table that the datetime value was generated from.
In order to use the parameter for a dataset query I have to parse both sides of a where clause to get it to work in Preview in SSDT:
(convert(varchar,invoice.snapshot_datetime,120)) = (convert(varchar,#snapshotdatetime,120))
This is tremendously inefficient.
How can I get my where clause to work without parsing the invoice.snapshot_datetime column?
Server Details
The SQL Server Language is English (United States).
SQL Server dateformat is mdy (from dbcc useroptions).
Getdate() returns '2015-05-20 10:27:56.687' in SSMS
Assuming your date range is between 1900-01-01 and 2079-06-06 you can cast to SmallDateTime to truncate the seconds out of your datetime variable:
DECLARE #DateTime datetime
SET #DateTime = CAST(CAST(#snapshotdatetime as SmallDateTime) as DateTime)
(thanks to t-clausen.dk for his answer here)
Now, since your actual column is of type DateTime, it does keep seconds (and milliseconds), and you will need to eliminate them as well.
However, using functions on your column will prevent the SQL Server from using any indexes you might have on this column, so a better approach would be to use a DateTime range:
DECLARE #FromDateTime datetime, #ToDateTime datetime
SET #FromDateTime = CAST(CAST(#snapshotdatetime as SmallDateTime) as DateTime)
Since the cast will round the minutes of the small date time up if it's over 29.998 seconds, and down if it's below 29.999 seconds. You always want to round down since it's From datetime, you need to cheke if you need to decrease a minute:
IF datepart(second, #snapshotdatetime) > 29
OR (datepart(second, #snapshotdatetime) = 29
AND datepart(millisecond, #snapshotdatetime) > 998)
SET #FromDateTime = DATEADD(minute, -1, #FromDateTime)
SET #ToDateTime = DATEADD(minute, 1, #FromDateTime)
and then, in your where clause, use this:
invoice.snapshot_datetime <= #FromDateTime
AND invoice.snapshot_datetime >= #ToDateTime
If you haven't found solution yet, try this:
select (convert(varchar,GETDATE(),112))
it will return 20180206 (yyymmdd)

SQL DATEDIFF coercion differences between databases on same SQL instance?

I have a data coercion mystery. I am seeing two different behaviors for the same query, and I cannot understand why.
This is an extract of the relevant part of the query in question, with fixed values. The first value represents "today" in our query, and was set up with the same data type with an explicit CAST:
-- edited to change dates to ISO 8601 literal format to avoid ambiguity
SELECT DATEDIFF(dd,CAST('2014-03-24' AS SmallDateTime),'0001-01-01')
ISO 8601 date literal format citation: https://msdn.microsoft.com/en-us/library/ms187819.aspx
We have two different databases on the same SQL server instance.
One of them returns zero rows, as you would expect.
Server one returns an error about the date range of the '1/1/0001':
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.
Server two returns a value that looks ballpark correct:
-735315
The problematic date is almost certainly '1/1/0001' and it fails as I expect for a default datetime, as it is below the minimum SQL datetime of Jan 1, 1753 (https://msdn.microsoft.com/en-us/library/ms187819.aspx).
According to the MSDN page for datediff (https://msdn.microsoft.com/en-US/library/ms189794(v=SQL.105).aspx), it can accept the following values:
startdate is an expression that can be resolved to a time, date, smalldatetime, datetime, datetime2, or datetimeoffset
Cast results for each of these are identical between servers, and are listed here:
SELECT CAST('0001-01-01' As time) -- works: 00:00:00.0000000
SELECT CAST('0001-01-01' As date) -- works: 0001-01-01
SELECT CAST('0001-01-01' As smalldatetime) -- error: The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.
SELECT CAST('0001-01-01' As DateTime) -- error: The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
SELECT CAST('0001-01-01' As datetime2) -- works: 0001-01-01 00:00:00.0000000
SELECT CAST('0001-01-01' As datetimeoffset) -- works: 0001-01-01 00:00:00.0000000 +00:00
The error explicitly calls out a failed DateTime conversion, so that appears to be the coercion of choice on database one.
It seems database two uses a different coercion that succeeds and does the datediff math correctly.
Because both of these databases are on the same SQL instance, I am ruling out instance settings.
Here are a few database settings we thought to check, and they appear identical between the two databases as well (checked in SQL Server Management Studio):
Database Collation (should be per server, but included for clarity):
(database) > Right Click > Properties > General > Maintenance > Collation
Database one: SQL_Latin1_General_CP1_CI_AS
Database two: SQL_Latin1_General_CP1_CI_AS
Date Correlation Optimization Enabled:
(database) > Right Click > Properties > Options > Misc. > Date Correlation Optimization Enabled
Database one: False
Database two: False
Two Digit Year Cutoff:
(database) > Right Click > Properties > Options > Containment > Two Digit Year Cutoff
Database one: 2049
Database two: 2049
User options date format
DBCC USEROPTIONS
Database one, dateformat: mdy
Database two, dateformat: mdy
(other settings appear identical)
I'm happy to provide other settings, or test query results, let me know what you'd like to see.
Why are the two databases behaving differently for this identical query? Why does it appear that the coercion chosen is different?
Edits:
Converted query to ISO date literals to avoid any ambiguity on formatting. Still seeing the same behavior.
Added DBCC USEROPTOINS check for dateformat, both mdy
https://dba.stackexchange.com/questions/96101/sql-datediff-coercion-differences-between-databases-on-same-sql-instance
As answered by Aaron Bertrand on the DBA stack exchange site, this was rooted in compatibility levels.
SELECT compatibility_level
FROM sys.databases WHERE name = 'FirstDatabase'
90
VS
SELECT compatibility_level
FROM sys.databases WHERE name = 'SecondDatabase'
110
Aaron has an excellent write up of why in this question:
https://dba.stackexchange.com/questions/44908/what-is-the-actual-behavior-of-compatibility-level-80
See Conversions involving new date/time types
The higher compatibility level likely means DateDiff uses a DateTime2 or other 'wider' data type and works. At 90 or below, it probably uses the old DateTime, and thus has conversion errors.
Thanks to Tab Alleman for the suggestion to cross post.

Excel incorrectly converts Date into Int

I'm pulling the data from SQL database. I have a couple columns with date which need to be converted into Int type, but when I do this the date changes (-2 days). I tried Cast and Convert and it's always the same.
Converting to other type works fine and returns the correct date, but doesn't work for me. I need only the date part from datetime and it needs to be recognised as a date by Excel.
Why is this happening? Any ideas how to get it sorted?
I'm using the following query:
SELECT wotype3, CONVERT(INT,wo_date2 ,103), CAST(duedate AS int) FROM Tasks WHERE
duedate > DATEADD(DAY,1, GETDATE())
AND wo_date2>0
AND wo_date2<DATEADD(WEEK,3,GETDATE())
ORDER BY wotype3
I've had big problems with this, checking my SQL Server's calculation results with "expected results" which a user had created using Excel.
We had discrepancies just because of this 2-day date difference.
Why does it happen ?
Two reasons:
SQL Server uses a zero-based date count from Jan 1 1900, but Excel uses a 1-based date count from Jan 1 1900.
Excel has a bug in it (gasp!) which makes it think that the year 1900 was a leap year. It wasn't. SQL Server correctly refuses to let you have a date value containing "29-Feb-1900".
Combine these two discrepancies, and this is why all dates, from March 1 1900 onwards, are always 2-days out.
Apparently, this Excel bug is a known issue, to keep it in line with Lotus 1-2-3.
The Intentional Date Bug
Microsoft's own explanation
From now on, I think I'll justify bugs in my code with the same excuse.
;-)
For SQL Server 2008 and above, you can use the DATE datatype.
declare #dt datetime = '12/24/2013 10:45 PM' -- some date for example
SELECT #dt as OriginalDateTime, CAST(#dt as DATE) as OnlyDate
For versions prior to SQL Server 2008, you would need to truncate the time part using one or the other functions. Here is one way to do that:
declare #dt datetime = '12/24/2013 10:45 PM' -- some date for example
SELECT #dt as OriginalDateTime, CAST(FLOOR(CAST(#dt AS FLOAT)) as DATETIME) as OnlyDate

Why is a T-SQL variable comparison slower than GETDATE() function-based comparison?

I have a T-SQL statement that I am running against a table with many rows. I am seeing some strange behavior. Comparing a DateTime column against a precalculated value is slower than comparing each row against a calculation based on the GETDATE() function.
The following SQL takes 8 secs:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
DECLARE #TimeZoneOffset int = -(DATEPART("HH", GETUTCDATE() - GETDATE()))
DECLARE #LowerTime DATETIME = DATEADD("HH", ABS(#TimeZoneOffset), CONVERT(VARCHAR, GETDATE(), 101) + ' 17:00:00')
SELECT TOP 200 Id, EventDate, Message
FROM Events WITH (NOLOCK)
WHERE EventDate > #LowerTime
GO
This alternate strangely returns instantly:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
SELECT TOP 200 Id, EventDate, Message
FROM Events WITH (NOLOCK)
WHERE EventDate > GETDATE()-1
GO
Why is the second query so much faster?
EDITED: I updated the SQL to accurately reflect other settings I am using
After doing a lot of reading and researching, I've discovered the issue here is parameter sniffing. Sql Server attempts to determine how best to use indexes based on the where clause, but in this case it isnt doing a very good job.
See the examples below :
Slow version:
declare #dNow DateTime
Select #dNow=GetDate()
Select *
From response_master_Incident rmi
Where rmi.response_date between DateAdd(hh,-2,#dNow) AND #dNow
Fast version:
Select *
From response_master_Incident rmi
Where rmi.response_date between DateAdd(hh,-2,GetDate()) AND GetDate()
The "Fast" version runs around 10x faster than the slow version. The Response_Date field is indexed and is a DateTime type.
The solution is to tell Sql Server how best to optimise the query. Modifying the example as follows to include the OPTIMIZE option resulted in it using the same execution plan as the "Fast Version". The OPTMIZE option here explicitly tells sql server to treat the local #dNow variable as a date (as if declaring it as DateTime wasnt enough :s )
Care should be taken when doing this however because in more complicated WHERE clauses you could end up making the query perform worse than Sql Server's own optimisations.
declare #dNow DateTime
SET #dNow=GetDate()
Select ID, response_date, call_back_phone
from response_master_Incident rmi
where rmi.response_date between DateAdd(hh,-2,#dNow) AND #dNow
-- The optimizer does not know too much about the variable so assumes to should perform a clusterd index scann (on the clustered index ID) - this is slow
-- This hint tells the optimzer that the variable is indeed a datetime in this format (why it does not know that already who knows)
OPTION(OPTIMIZE FOR (#dNow = '99991231'));
The execution plans must be different, because SQL Server does not evaluate the value of the variable when creating the execution plan in execution time. So, it uses average statistics from all the different dates that can be stored in the table.
On the other hand, the function getdate is evaluated in execution time, so the execution plan is created using statistics for that specific date, which of course, are more realistic that the previous ones.
If you create a stored procedure with #LowerTime as a parameter, you will get better results.

Resources