1

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':

enter image description here

enter image description here

enter image description here

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Glen Little
  • 6,951
  • 4
  • 46
  • 68
  • 2
    Every call to `getdate()` results in a *new* value. So it is possible the same value is not returned between calls even in the same statement. – Igor Jan 28 '20 at 21:04
  • 1
    *"The `getDate()` statement always returns the same value anywhere in one statement."* This statement is fundamentally wrong, which means that the rest of the problem falls apart. `SYSDATETIME` will have the same "issue". If you want a consistent value through your query, assign the value to a variable, and then reference that variable in your query. – Thom A Jan 28 '20 at 21:21
  • `GETDATE` is evaluated once per query as described here: https://dba.stackexchange.com/questions/18459/does-sql-server-evaluate-functions-once-for-every-row – Salman A Jan 29 '20 at 08:59
  • @SalmanA As I've learned, `GETDATE` used to be "once per query" but it is not anymore. – Glen Little Feb 03 '20 at 20:41
  • @Glen nope. The question I linked says otherwise. Also if `GETDATE` were once per row then why `SYSDATETIME` isn't? The answer below (the one with +1 score) seems to provide a good explanation of what is going on although I cannot verify it myself. – Salman A Feb 04 '20 at 06:03
  • @salmana Not to belabor the point, but the answer linked above explains that "the result is cached and the cached result" is used in the rest of the query. That is what I'm referring to. – Glen Little Feb 04 '20 at 16:49

2 Answers2

1

Interesting enough question.

The behaviour in your example can be explaned by the following:

  1. 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.
  2. By default, comparison datetime and datetime2 performs with conversion datetime to datetime2(7). You can see that in execution plan.
  3. datetime variable with 3 at the end - for example .003 - gets converted in .0033333. 007 gets converted in .0066667.
  4. 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!

Max Zolotenko
  • 1,082
  • 7
  • 13
  • Running the same test on three different SQL 2017 servers... only the slow VM was returning different values during that one statement. It also shows up only when comparing to a DATETIME2 column value - a DATETIME column was fine. – Glen Little Feb 03 '20 at 20:45
  • *Running the same test on three different SQL 2017 servers* - which database did you use when running the tests? master? – Max Zolotenko Feb 03 '20 at 20:51
  • Not master... Existing tables (same db on each server) or new tables just for testing this. – Glen Little Feb 04 '20 at 16:45
0

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.

Glen Little
  • 6,951
  • 4
  • 46
  • 68
  • 1
    The original problem likely was because the SQL 2017 server was running in a VM and was slow enough that `getDate()` was returning different values during that one statement. All the other tested servers were much faster. – Glen Little Jan 28 '20 at 21:56
  • 1
    Another explanation: https://dba.stackexchange.com/a/18461/18099 – Glen Little Jan 28 '20 at 22:02
  • 1
    On my home server (SQL Server Developer Edition) I got the next results: when I run your query on database with compatibility level 130+ (2016+) then it gives me the same strange behaviour: when I run the query on database with compatibility level less than 130, it gives "same" all time. – Max Zolotenko Jan 28 '20 at 22:15
  • 2
    Moreover, I noticed that "same" appears when my `getdate()` returns 0 at the end, "less" - when 7 at the end and "greater" when 3 at the end. `datetime` values can end only with these 3 values: 0, 3, 7. – Max Zolotenko Jan 28 '20 at 22:20