La respuesta típica es agregar una cláusula WHERE:
WHERE ISDATE(a.valor) = 1
Sin embargo, esto es problemático en su situación por un par de razones:
-
ISDATE()
no coincidirá necesariamente con la forma que usted desea según la configuración regional del servidor, el idioma del usuario o las opciones de formato de fecha, etc. Por ejemplo:SET DATEFORMAT dmy; SELECT ISDATE('13/01/2012'); -- 1 SET DATEFORMAT mdy; SELECT ISDATE('13/01/2012'); -- 0
-
Realmente no puede controlar que SQL Server intente realizar
CONVERT
después del filtro.
Ni siquiera puede usar subconsultas o CTE para tratar de separar el filtro de CONVERT porque SQL Server puede optimice las operaciones en la consulta en el orden que considere más eficiente.
Por ejemplo, con una muestra limitada, probablemente encontrará que esto funciona bien:
SET DATEFORMAT dmy;
SELECT valor, valor_date FROM (
SELECT valor, valor_date = CONVERT(DATE,
CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
FROM dbo.mytable
WHERE ISDATE(valor) = 1
) AS sub WHERE valor_date BETWEEN '01/01/2012' AND '01/03/2012';
Pero he visto casos incluso con esta construcción donde SQL Server ha intentado evaluar el filtro primero, lo que lleva al mismo error que está recibiendo actualmente.
Un par de soluciones alternativas más seguras:
Agregue una columna calculada, por ejemplo,
ALTER TABLE dbo.mytable ADD valor_date
AS CONVERT(DATE, CASE WHEN ISDATE(valor) = 1 THEN valor
ELSE NULL END, 103);
Para protegerse de posibles interpretaciones erróneas en tiempo de ejecución, debe especificar el formato de fecha antes de emitir una consulta que haga referencia a la columna calculada, por ejemplo,
SET DATEFORMAT dmy;
SELECT valor, valor_date FROM dbo.mytable WHERE ...;
Crear una vista:
CREATE VIEW dbo.myview
AS
SELECT valor, valor_date = CONVERT(DATE,
CASE WHEN ISDATE(valor) = 1 THEN valor ELSE NULL END, 103)
FROM dbo.mytable
WHERE ISDATE(valor) = 1;
Nuevamente, querrá emitir un SET DATEFORMAT
al consultar la vista.
Usa una tabla temporal:
SELECT <cols>
INTO #foo
FROM dbo.mytable
WHERE ISDATE(valor) = 1;
SELECT <cols>, CONVERT(DATE, valor) FROM #foo WHERE ...;
Es posible que aún desee utilizar DATEFORMAT
para protegerse de conflictos entre ISDATE
y configuración de usuario.
Y no, no deberías no intente validar sus cadenas como fechas usando la coincidencia de patrones de cadena como se sugirió en otra respuesta (ahora eliminada):
like '%__/%' or like '%/%'
Tendrá que tener una validación bastante compleja y pesada allí para manejar todas las fechas válidas, incluidos los años bisiestos.