2011-08-31 17 views
9

Tengo un problema con algunas consultas de SQL Server. Resulta que tengo una tabla con los campos "Attibute_Name" y "Attibute_Value", que pueden ser de cualquier tipo, almacenados en varchar. (Sí ... lo sé)¿La conversión a datetime falla solo en la cláusula WHERE?

Todas las fechas para un atributo en particular parecen almacenarse en el formato "AAAA-MM-DD hh: mm: ss" (no 100% seguro de eso, hay millones de los registros aquí), para que pueda ejecutar este código sin problemas:

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value) 
from 
    ProductAttributes pa 
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID 
where 
    a.Attribute_Name = 'SomeDate' 

sin embargo, si ejecuto el siguiente código:

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value) 
from 
    ProductAttributes pa 
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID 
where 
    a.Attribute_Name = 'SomeDate' 
    and CONVERT(DATETIME, pa.Attribute_Value) < GETDATE() 

voy a tener el siguiente error: error de conversión al convertir fecha y/o hora de la cadena de caracteres.

¿Cómo es que falla en la cláusula where y no en la seleccionada?

Otra pista:

Si en lugar de filtrado por el nombre_atributo utilizo el attribute_id real almacenado en la base de datos (PK) que va a funcionar sin problema.

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value) 
from 
    ProductAttributes pa 
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID 
where 
    a.Attribute_ID = 15 
    and CONVERT(DATETIME, pa.Attribute_Value) < GETDATE() 

actualización Gracias a todos por las respuestas. Me resultó difícil elegir una respuesta correcta porque todos señalaron algo útil para comprender el problema. Definitivamente tenía que ver con el orden de ejecución. Resulta que mi primera consulta funcionó correctamente porque la cláusula WHERE se ejecutó primero, luego SELECCIONA. Mi segunda consulta falló debido a la misma razón (ya que los Atributos no se filtraron, la conversión falló al ejecutar la misma cláusula WHERE). Mi tercera consulta funcionó porque la ID formaba parte de un índice (PK), por lo que tuvo prioridad y primero profundizó los resultados en esa condición.

Gracias!

+0

hay otro atributo llamado 'sOmeDaTe' - en caso de que esté utilizando una intercalación insensible? Probablemente está estropeando tus uniones. – YetAnotherUser

+1

Parece que está asumiendo algún tipo de evaluación de cortocircuito u orden garantizada de los predicados en la cláusula 'WHERE'. Esto no está garantizado Cuando ha mezclado tipos de datos en una columna como esa, la única forma segura de manejarlos es con una expresión 'CASE'. –

+0

¿Qué tabla es PHA? Si la PHA es una tabla diferente a la de PA, parecería que los datos de la PHA tienen algunos registros inconvertibles, mientras que los de la AP no lo hacen. – N0Alias

Respuesta

2

Si la conversión se encuentra en la cláusula WHERE, se puede evaluar para muchos más registros (valores) de lo que sería si aparece en la proyección lista. Ya he hablado de esto en otro contexto, vea T-SQL functions do no imply a certain order of execution y On SQL Server boolean operator short-circuit. Su caso es incluso más simple, pero es similar y, en última instancia, la causa raíz es la misma: no asuma un orden de ejecución imperativo al tratar con un lenguaje declarativo como SQL.

Su mejor solución, por un margen lejano y grande, es desinfectar los datos y cambiar el tipo de columna a DATETIME o DATETIME2. Todos otras soluciones tendrán una deficiencia u otra, por lo que es mejor que simplemente haga lo correcto.

actualización

Después de un vistazo más de cerca (lo siento, estoy @VLDB y sólo asomándose por lo que entre sesiones) me cuenta que tiene una tienda de EAV con la semántica inherentes libre de tipo (la attribute_value puede bea cadena, una fecha, un int, etc.). Mi opinión es que su mejor opción es usar sql_variant en almacenamiento y hasta llegar al cliente (es decir, proyecto sql_variant). Puede cohercer el tipo en el cliente, todas las API de cliente tienen métodos para extraer el tipo interno de sql_variant, consulte Using sql_variant Data (bueno, casi todas las API de cliente ... Using the sql_variant datatype in CLR). Con sql_variant puede almacenar varios tipos sin los problemas de pasar por una representación de cadena, puede usar SQL_VARIANT_PROPERTY para inspeccionar cosas como el BaseType en los valores almacenados, e incluso puede hacer cosas como restricciones de verificación para aplicar la corrección del tipo de datos.

+0

No me atrevo a usar 'SQL_VARIANT' * a menos que * esté haciendo toda la presentación, el filtrado y las comparaciones en el cliente. En nuestro sistema EAV nos alejamos rápidamente de 'SQL_VARIANT' a favor de columnas dedicadas para cada tipo. Ok, entonces tienes dos NULLs en cada fila, pero no tienes que lidiar con todas las otras cosas desagradables que vienen con eso. Solo para dar una sacudida justa a ambas partes, publiqué un poco sobre las limitaciones aquí: http://sqlblog.com/blogs/aaron_bertrand/archive/2009/10/12/bad-habits-to-kick-using-the- wrong-data-type.aspx ... ¿puede mostrar su consulta si la columna era 'SQL_VARIANT'? –

+0

Veo tu punto. Hacer y agregar en la estructura de EAV sql_variant sería víctima de los problemas de conversión, mientras que una columna/tipo dedicado puede agregar fácilmente los valores porque sabe que están todos en el campo para ese tipo, y el tipo no requiere CAST. Objeción válida –

0

Parece un problema de datos para mí. Eche un vistazo a los datos cuando los seleccione usando los dos métodos diferentes, intente buscar distintas longitudes y luego seleccione los elementos en los diferentes conjuntos y visualícelos. También verifica los nulos? (No estoy seguro de qué pasará si intenta convertir un nulo a una fecha y hora)

+0

la conversión de nulo a una fecha y hora da como resultado un nulo. –

1

Esto tiene que ver con el orden en que se procesa una consulta SELECT. La cláusula WHERE se procesa mucho antes que SELECT. Tiene que determinar qué filas incluir/excluir. La cláusula que usa el nombre debe usar un escaneo que investigue todas las filas, algunas de las cuales no contienen datos de fecha/hora válidos, mientras que la clave probablemente lleve a una búsqueda y ninguna de las filas no válidas se incluye en el punto. La conversión en la lista SELECCIONAR se realiza en último lugar, y es evidente que en este momento no intentará convertir las filas no válidas. Dado que está mezclando datos de fecha/hora con otros datos, puede considerar almacenar datos numéricos o de fecha en columnas dedicadas con los tipos de datos correctos. Mientras tanto, puede diferir la verificación de la siguiente manera:

SELECT /* ... */ 
FROM 
(
    SELECT /* ... */ 
    FROM ProductAttributes AS pa 
    INNER JOIN dbo.Attributes AS a 
    ON a.Attribute_ID = pa.Attribute_ID 
    WHERE a.Attribute_Name = 'SomeDate' 
    AND ISDATE (pa.Attribute_Value) = 1 
) AS z 
WHERE CONVERT(CHAR(8), AttributeValue, 112) < CONVERT(CHAR(8), GETDATE(), 112); 

Pero la mejor respuesta es probablemente utilizar la tecla en lugar del nombre, si es posible Attribute_ID.

+1

Esto no está garantizado para funcionar. El escalar de cálculo en la lista 'SELECT' se puede evaluar antes del filtro' WHERE'. Ver por ejemplo [esta respuesta] (http://stackoverflow.com/questions/5191701/tsql-divide-by-zero-encountered-despite-no-columns-containing-0/5203211#5203211) o [este elemento de conexión] (http://connect.microsoft.com/SQLServer/feedback/details/537419/sql-server-should-not-raise-illogical-errors) –

+0

No, esto no funcionará. Usted está suponiendo que el orden de la declaración (subconsulta) implica un orden de evaluación como en http://rusanu.com/2011/08/10/t-sql-functions-do-no-imply-a-certain- la orden de ejecución/QO puede elegir un plan que evalúe el CONVERTIR * antes * de la comparación de nombre de atributo y active el error de conversión. –

+0

No, debería haber dicho, "puede intentar diferir" ... la mejor respuesta es almacenar los datos en columnas dedicadas del tipo de datos correcto, en lugar de rellenar todo en una columna varchar. –

7

Parece que está asumiendo algún tipo de evaluación de cortocircuito u orden garantizada de los predicados en la cláusula WHERE. Esto no está garantizado Cuando ha mezclado tipos de datos en una columna como esa, la única forma segura de manejarlos es con una expresión CASE.

uso (por ejemplo)

CONVERT(DATETIME, 
     CASE WHEN ISDATE(pa.Attribute_Value) = 1 THEN pa.Attribute_Value END) 

No

CONVERT(DATETIME, pa.Attribute_Value) 
0

Creo que el problema es que hay una mala fecha en su base de datos (obviamente).

En su primer ejemplo, donde no está marcando la fecha en la cláusula WHERE, todas las fechas donde a.attribute.Name = 'SomeDate' son válidas, por lo que nunca intenta convertir una fecha incorrecta.

En su segundo ejemplo, la adición a la cláusula WHERE hace que el plan de consulta realmente convierta todas esas fechas y encuentre la incorrecta y luego busque el nombre del atributo.

En su tercer ejemplo, cambiar para usar Attribute_Id probablemente cambia el plan de consulta para que solo busque aquéllos donde id = 15 Primero, y luego verifica si esos registros tienen una fecha válida, lo cual hacen. (Tal vez Attribute_Id está indexado y no es Attribute_name)

Por lo tanto, usted tiene un mal día en algún lugar, pero no es en ningún registro con Arttribute_id = 15.

0

Puede comprobar los planes de ejecución.Es posible que con la primera consulta el segundo criterio (CONVERT(DATETIME, pa.Attribute_Value) < GETDATE()) se evalúe primero sobre todas las filas, incluidas las que tienen datos no válidos (no la fecha), mientras que en el caso del segundo, se evalúa primero el a.Attribute_ID = 15. Por lo tanto, excluye las filas con valores sin fecha.

por cierto, el segundo podría ser más rápido también, y si no tiene nada de Attributes en la lista de selección, puede deshacerse de inner join Attributes a on a.Attribute_ID = pa.Attribute_ID.

En ese sentido, sería aconsejable para deshacerse de EAV, mientras que no es demasiado tarde :)

+0

puede intentar recalcular las estadísticas de la tabla. Si 'ProductAttributes' contiene millones de filas, la evaluación de' CONVERT (DATETIME, pa.Attribute_Value) nad2000

+0

'ANALYZE TABLE' no suena bien para SQL Server. –

Cuestiones relacionadas