Eugene
Eugene

Reputation: 1093

SQL Server : function precedence and short curcuiting in where clause

Consider this setup:

create table #test (val varchar(10))
insert into #test values ('20100101'), ('1')

Now if I run this query

select * 
from #test
where ISDATE(val) = 1
  and CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00'

it will fail with

Conversion failed when converting date and/or time from character string

which tells me that the where conditions are not short-circuited and both functions are evaluated. OK.

However if I run

select * 
from #test
where LEN(val) > 2
  and CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00'

it doesn't fail, which tells me that where clause is short-circuited in this case.

This

select * 
from #test
where ISDATE(val) = 1
  and CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00'
  and LEN(val) > 2

fails again, but if I move length check to before cast, it work. So looks like the functions are evaluated in the order they appear in query.

Can anyone explain why first query fails?

Upvotes: 0

Views: 58

Answers (3)

LoztInSpace
LoztInSpace

Reputation: 5697

It fails because SQL is declarative so the order of your conditions is not taken into account when the plan is generated (nor is it required to do so).

The usual way to get around this is to use CASE which has strict rules about sequence and when to stop.

In your case you will probably need nested CASEs, something like this:

WHERE
    (
        case when ISDATE(val) = 1 then 
          case when CAST(val as datetimeoffset) > '2005-03-01 00:00:00 +00:00' and
                    LEN(val) > 2 
                    THEN 1 ELSE 0 END
        ELSE 0
        END
    ) = 1

(note this is unlikely to be actually correct SQL as I just typed it in).

By the way, even if you get it "working" by rearranging the conditions, I'd advise you don't. Accept that SQL simply doesn't work in that way. As the data changes & stats change, SQL is upgraded, workload varies, indexes are added the query plan could change. Any attempts to "get it working" are going to be short-lived at best so go with the CASE which will continue to work once you've got it right (provided you nest CASE statements where required and don't fall into the same precedence trap in the CASE conditions!)

Upvotes: 2

James K. Lowden
James K. Lowden

Reputation: 7837

which tells me that where conditions are not short-circuited and both functions are evaluated.

To expand on LoztInSpace's answer, your terminology suggests you are not interpreting SQL correctly, on its own terms.

The various parts of a SELECT statement are not "functions". The entire statement is atomic. You supply the query as unit, and the DBMS responds. There is no "before" and no "after". There is just the query.

Those are the rules. Your job in formulating the query is to supply one that is valid. It's a logical progression: valid question, valid answer, etc. The moment you step out of that frame, you might as well be asking, "why is the sky seven?".

One a small clarification to @LoztInSpace's answer. When he refers to the order of your statements, he's presumably talking about the phrasing of your query, which for purposes of evaluation is inconsequential. Sequential SQL statements are executed sequentially, as presented. That is guaranteed by the SQL standard.

Upvotes: 0

Laughing Vergil
Laughing Vergil

Reputation: 3756

The mystery is answered if you examine the Execution Plan. Both the CAST() and the LEN() are applied as part of the Table Scan step, while the test for IsDate() is a separate Filter test after the Table Scan.

It appears that the SQL Engine's internal optimizations use certain filtering functions as part of the retrieval of the data, and others as separate filters, almost certainly as a form of query optimization to minimize the load from disk into main memory. However, more complex functions, such as IsDate(), which is dependent on system variables such as system date format in some cases (is '01/02/2017' Jan 2nd or Feb 1st?), need to have the data retrieved before the filter is applied.

Although I have no hard information on this, I strongly suspect that any filter more resource intensive than a certain level is delegated to the Filter steps in the query plan, and anything simple/fast enough to be checked as the data is being read in is applied during the Scan/Seek steps. Also, if a filter could be applied on the data in the index, I am certain that it will be tested before any non-index data is tested, solely to minimize disk reads, which are bad performance juju (this may not apply on the Clustered index of the table). In these cases, the short-circuiting might not be straightforward, with an IsDate() test specified on a non-index field being executed after a similar test on an indexed field, no matter where they are in the list of conditions.

That said, it appears to be true that conditions short-circuit when they are executed in the same step of the query plan. If you insert a string like '201612123' into the temp table, then add a check on Len(val) < 9 after the date comparison, it still generates an error, instead of checking both LEN() conditions at the same time in a tiny optimization.

Upvotes: 1

Related Questions