sql >> Base de Datos >  >> RDS >> Sqlserver

¿Cómo convertir varchar a la fecha solo cuando contiene una fecha válida?

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:

  1. 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
    
  2. 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.