2009-05-15 21 views
6

Estoy usando DATEDIFF en una declaración de SQL. Lo estoy seleccionando, y necesito usarlo en la cláusula WHERE también. Esta declaración no funciona ...Usando DATEDIFF en T-SQL

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

Se da el mensaje: nombre de columna no válido "InitialSave"

Pero esta afirmación funciona bien ...

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

El programador de me dice que esto es ineficiente (parece que estoy llamando a la función dos veces).

Dos preguntas. ¿Por qué no funciona la primera declaración? ¿Es ineficiente hacerlo usando la segunda declaración?

Respuesta

5

No puede acceder a las columnas definidas en la instrucción select en la instrucción where, porque no se generan hasta después de que se haya ejecutado.

Usted puede hacer esto sin embargo

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

Como comentario - En esencia, esto mueve el DATEDIFF en el comunicado, donde en función de dónde se definió por primera vez. El uso de funciones en columnas en donde las declaraciones hacen que los índices no se utilicen de la manera más eficiente y deberían evitarse, si es posible, sin embargo, si tiene que usar la fecha, debe hacerlo.

+0

Entonces, si estoy seleccionando 20 columnas además de esto, tendré que SELECCIONARlas en ambas declaraciones (interna y externa), ¿correcto? ¿Hay una mejor manera? –

+2

Si lo desea, puede usar select * en la primera línea, en lugar de seleccionar InitialSave, Col2, Col3, etc. –

+0

¡Oh, duh! ¡Gracias! * se da palmadas en la cabeza * –

2

Tiene que usar la función en lugar del alias de la columna, es lo mismo con el conteo (*), etc. PITA.

+0

¿Es lo suficientemente inteligente como para no * * pasar por las mismas extensos cálculos varias veces? Estoy dudoso –

7

Nota: Cuando escribí originalmente esta respuesta me dijeron que un índice en una de las columnas podría crear una consulta que se comporta mejor que otras respuestas (y mencionado Dan Fuller). Sin embargo, no estaba pensando 100% correctamente. El hecho es que, sin una columna calculada o vista indexada (materializada), una exploración de tabla completa va a ser requerida, porque las dos columnas de fecha que se comparan son de la misma tabla !

Creo que todavía hay valor en la información a continuación, a saber 1) la posibilidad de un mejor rendimiento en la situación correcta, cuando la comparación es entre columnas de diferentes tablas, y 2) promover el hábito en los desarrolladores SQL de las mejores prácticas y remodelar su pensamiento en la dirección correcta.

Haciendo Condiciones sargable

La mejor práctica que me refiero es uno de mover una columna a estar solo en un lado del operador de comparación, así:

SELECT InitialSave = DateDiff(second, T.BegTime, T.EndTime) 
FROM dbo.MyTable T 
WHERE T.EndTime <= T.BegTime + '00:00:10' 

Mientras dijo, esto no evitará una exploración en una sola tabla, sin embargo, en una situación como esta podría hacer una gran diferencia:

SELECT InitialSave = DateDiff(second, T.BegTime, T.EndTime) 
FROM 
    dbo.BeginTime B 
    INNER JOIN dbo.EndTime E 
     ON B.BeginTime <= E.EndTime 
     AND B.BeginTime + '00:00:10' > E.EndTime 

EndTime está en ambas condiciones ahora solo en un lado de la comparación.Suponiendo que la tabla BeginTime tiene muchas menos filas, y la mesa EndTime tiene un índice en la columna EndTime, esto llevará a cabo mucho, mucho mejor que cualquier cosa usando DateDiff(second, B.BeginTime, E.EndTime). Es ahora sargable, lo que significa que hay un "argumento de búsqueda" válido - así como el motor exploracionesBeginTime la tabla, se pueden buscar en la tabla EndTime. La selección cuidadosa de los cuales la columna es de por sí se requiere en un lado del operador - que puede ser vale la pena experimentar poniendo BeginTime por sí mismo haciendo algo de álgebra para cambiar a AND B.BeginTime > E.EndTime - '00:00:10'

precisión de DifFecha

I también debería señalar que DateDiff no vuelve transcurrido el tiempo, sino que cuenta el número de límites cruzados. Si una llamada a DateDiff utilizando segundos vuelve 1, esto podría significar 3 ms tiempo transcurrido, o podría significar 1997 ms! Esto es esencialmente una precisión de + - 1 unidades de tiempo. Para la mejor precisión de + - 1/2 unidad de tiempo, le gustaría que la siguiente consulta comparando 0-EndTime - BegTime:

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

Esto ahora tiene un error máximo redondeo de sólo un segundo conjunto, no dos (en efecto, una operación de piso()). Tenga en cuenta que solo puede restar el tipo de datos datetime - para restar un valor de date o time que debe convertir a datetime o utilizar otros métodos para obtener la mejor precisión (un lote de DateAdd, DateDiff y posiblemente otra basura, o quizás usando una unidad de tiempo de mayor precisión y división).

Este principio es especialmente importante cuando contar unidades más grandes, tales como horas, días o meses. Un DateDiff de 1 month podría tener 62 días de diferencia (1 de julio de pensar, 2013 - Aug 31 2013)!

+1

Eso es una tontería. No estaba afectando un aire presuntuoso cuando hice ese comentario, pero en realidad trataba de ser realmente útil para alguien que quizás no conocía los efectos prácticos de las cosas que estaba describiendo. Por favor, vuelva a leer lo que escribí: ¿acaso era un poco demasiado exacto, pero en realidad no insultante? – ErikE

+0

Oh ...y -1 en mi consulta de * mejor rendimiento * debido a una sola palabra que no entendió ... complete el espacio en blanco aquí, pero no es gratuito. Oye, hombre, estaba tratando de compartir lo que puedo. – ErikE

+0

+1 para compensar a jvanderh por marcar injustamente –

3

más allá de lo que es "trabajo", es necesario utilizar un índice

uso de una columna calculada con un índice o una vista con un índice, de lo contrario recorrido de tabla. cuando obtenga suficientes filas, sentirá el DOLOR del escaneo lento!

columna calculada & índice:

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 

crear una vista & índice:

CREATE VIEW YourNewView 
AS 
SELECT 
    KeyValues 
     ,DATEDIFF(ss, BegTime, EndTime) AS InitialSave 
    FROM MyTable 
GO 
CREATE CLUSTERED INDEX IX_YourNewView 
    ON YourNewView(InitialSave) 
GO 
+1

Crear índices en las vistas no siempre es la bala mágica. Creo que encontrará que un índice en la columna EndTime funcionará lo suficientemente bien y no tendrá la sobrecarga de actualización. Se reduce un poco al patrón de actualización/selección. – ErikE

+0

Este es un buen punto, y el uso de una columna calculada realmente depende de los datos y la frecuencia de la columna que se utiliza. Si la tabla que se utiliza para la columna es probable que sea grande (como en, millones de filas +) y la columna en cuestión sea: 1. Va a ser utilizado con mucha frecuencia en el funcionamiento general de la aplicación 2. Ir a acceder con poca frecuencia será el personal de nivel ejecutivo que requiera acceso rápido. Luego se debe usar la columna calculada. De lo contrario, podría ser mejor no usarlo y no ralentizar sus inserciones/actualizaciones. –