tags:

views:

2955

answers:

5

I am using DATEDIFF in an SQL statement. I am selecting it, and I need to use it in WHERE clause as well. This statement does not work...

SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable
WHERE InitialSave <= 10

It gives the message: Invalid column name "InitialSave"

But this statement works fine...

SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable
WHERE DATEDIFF(ss, BegTime, EndTime) <= 10

The programmer in me says that this is inefficient (seems like I am calling the function twice).

So two questions. Why doesn't the first statement work? Is it inefficient to do it using the second statement?

+2  A: 

You have to use the function instead of the column alias - it is the same with count(*), etc. PITA.

Otávio Décio
Is it smart enough to *not* go through the same extensive calculations multiple times? I am doubtful.
Josh Stodola
+1  A: 

As an alternate, you can use computed columns.

Khurram Aziz
+2  A: 

You can't access columns defined in the select statement in the where statement, because they're not generated until after the where has executed.

You can do this however

select InitialSave from 
(SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable) aTable
WHERE InitialSave <= 10

As a sidenote - this essentially moves the DATEDIFF into the where statement in terms of where it's first defined. Using functions on columns in where statements causes indexes to not be used as efficiently and should be avoided if possible, however if you've got to use datediff then you've got to do it!

Dan Fuller
So if I am selecting 20 columns in addition to this, I will need to SELECT them in both statements (inner and outer), correct? Is there a better way?
Josh Stodola
If you want, you can just use select * on the first line, rather than select InitialSave, Col2, Col3 etc etc.
Dan Fuller
Oh, duh! Thanks! *slaps self on head*
Josh Stodola
-1 because this is going to guarantee a scan: it has to calculate DateDiff on every row. Please see my post which will at the very least be a simple comparison without a date calculation, and at best with an index on one of the date columns could be a seek. Which means (generally) way better performance.
Emtucifor
+1  A: 

You may find this to be a better performer. Especially if you have an index on one of those columns.

SELECT DATEDIFF(ss, BegTime, EndTime) AS InitialSave
FROM MyTable
WHERE EndTime <= BegTime + '00:00:10'

If your index is on BegTime, it may help to move it to be alone on its side of the comparison.

UPDATE

I should also point out that doing a DateDiff like this will not return the elapsed time but counts the number of boundaries crossed. If DateDiff returns one second, this could mean 3 ms, or it could mean 1997 ms (assuming datetime data type), as long as it crosses a whole second boundary. For more accurate counting, you would want the following query:

SELECT DATEDIFF(ss, 0, EndTime - BegTime) AS InitialSave
FROM MyTable
WHERE EndTime <= BegTime + '00:00:10'

This now has only a rounding error of one second (in effect, a floor() operation).

This principle is especially important when counting larger units such as hours or days.

Emtucifor
-1 No need to call people uninformed, we are all here to learn and share what we can.
jvanderh
That's just silly. I wasn't affecting a snooty air when I made that comment, but actually trying to be really helpful to someone who might not know the practical effects of the things I was describing. Please reread what I wrote: was I perhaps just a tiny bit too exact, but not actually insulting?
Emtucifor
Oh... and -1 on my *better-performing* query because of a single word you misunderstood is... fill in the blank here, but it's not complimentary. Hey, man, I was trying to share what I can.
Emtucifor
+2  A: 

beyond making it "work", you need to use an index

use a computed column with an index, or a view with an index, otherwise you will table scan. when you get enough rows, you will feel the PAIN of the slow scan!

computed column & index:

ALTER TABLE MyTable ADD
    ComputedDate  AS DATEDIFF(ss,BegTime, EndTime)
GO
CREATE NONCLUSTERED INDEX IX_MyTable_ComputedDate  ON MyTable 
    (
    ComputedDate
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

create a view & index:

CREATE VIEW YourNewView
AS
SELECT
    KeyValues
        ,DATEDIFF(ss, BegTime, EndTime) AS InitialSave
    FROM MyTable
GO
CREATE CLUSTERED INDEX IX_YourNewView
    ON YourNewView(InitialSave)
GO
KM
Creating indexes on views is not always the magic bullet. I think you will find that an index on the EndTime column will perform well enough and won't have the update overhead. It does come down a little to the update/select pattern.
Emtucifor
This is a really good point, and using a computed column really depends on the data and the frequency of the column being used.If the table that the column is being used for is likely to be large (as in, millions of rows +) and the column in question is either:1. Going to be used extremely frequently in the general running of the application2. Going to be accessed infrequently be executive level staff that require quick access.Then the computed column should be used. Otherwise it might be better to not use it and not slow down your inserts/updates.
Dan Fuller