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:
-
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
-
Você não pode realmente controlar se o SQL Server tentará executar oCONVERT
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.