Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Como converter varchar em data somente quando contém uma data válida?


A resposta típica é adicionar uma cláusula WHERE:
WHERE ISDATE(a.valor) = 1

No entanto, isso é problemático na sua situação por alguns motivos:

  1. ISDATE() não corresponderá necessariamente da maneira que você deseja, dependendo das configurações regionais do servidor, do idioma do usuário ou das opções de formato de data etc. Por exemplo:
    SET DATEFORMAT dmy;
    SELECT ISDATE('13/01/2012'); -- 1
    
    SET DATEFORMAT mdy;
    SELECT ISDATE('13/01/2012'); -- 0
    

  2. Você não pode realmente controlar se o SQL Server tentará executar o CONVERT depois do filtro.

Você não pode nem usar subconsultas ou CTEs para tentar separar o filtro do CONVERT porque o SQL Server pode otimizar as operações na consulta na ordem que julgar mais eficiente.

Por exemplo, com uma amostra limitada, você provavelmente descobrirá que isso funciona bem:
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';

Mas já vi casos com essa construção em que o SQL Server tentou avaliar o filtro primeiro, levando ao mesmo erro que você está recebendo no momento.

Algumas soluções alternativas mais seguras:

Adicione uma coluna computada, por exemplo
ALTER TABLE dbo.mytable ADD valor_date
  AS CONVERT(DATE, CASE WHEN ISDATE(valor) = 1 THEN valor 
    ELSE NULL END, 103);

Para se proteger de possíveis interpretações errôneas em tempo de execução, você deve especificar o formato de data antes de emitir uma consulta que faça referência à coluna computada, por exemplo,
SET DATEFORMAT dmy;
SELECT valor, valor_date FROM dbo.mytable WHERE ...;

Crie uma visualização:
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;

Novamente, você desejará emitir um SET DATEFORMAT ao consultar a vista.

Use uma tabela temporária:
SELECT <cols>
INTO #foo
FROM dbo.mytable
WHERE ISDATE(valor) = 1;

SELECT <cols>, CONVERT(DATE, valor) FROM #foo WHERE ...;

Você ainda pode querer usar DATEFORMAT para se proteger de conflitos entre ISDATE e configurações do usuário.

E não, você não tente validar suas strings como datas usando a correspondência de padrões de string, como foi sugerido em outra resposta (agora excluída):
like '%__/%' or like '%/%'

Você terá que ter uma validação bastante complexa e pesada para lidar com todas as datas válidas, incluindo anos bissextos.