Falha na conversão de DateTime do SQL Server
-
09-06-2019 - |
Pergunta
Eu tenho uma tabela grande com mais de 1 milhão de registros.Infelizmente, a pessoa que criou a tabela decidiu colocar as datas em um varchar(50)
campo.
Preciso fazer uma comparação simples de datas -
datediff(dd, convert(datetime, lastUpdate, 100), getDate()) < 31
Mas falha no convert()
:
Conversion failed when converting datetime from character string.
Aparentemente há algo nesse campo que ele não gosta, e como há tantos registros, não posso dizer só de olhar.Como posso higienizar adequadamente todo o campo de data para que não falhe no convert()
?Aqui está o que tenho agora:
select count(*)
from MyTable
where
isdate(lastUpdate) > 0
and datediff(dd, convert(datetime, lastUpdate, 100), getDate()) < 31
Não estou preocupado com o desempenho neste caso.Esta será uma consulta única.Alterar a tabela para um campo de data e hora não é uma opção.
Tentei adicionar o terceiro argumento e não faz diferença.
O problema provavelmente é como os dados são armazenados; existem apenas dois formatos seguros;ISO AAAAMMDD;ISO 8601 aaaa-mm-dd Thh:mm:ss:mmm (sem espaços)
Não seria isdate()
verifique cuidar disso?
Não preciso de 100% de precisão.Eu só quero obter a maioria dos registros dos últimos 30 dias.
select isdate('20080131') -- returns 1
select isdate('01312008') -- returns 0
Coloque CASE e ISDATE dentro da função CONVERT().
Obrigado!Isso resolveu.
Solução
Coloque o CASE
e ISDATE
dentro de CONVERT()
função.
SELECT COUNT(*) FROM MyTable
WHERE
DATEDIFF(dd, CONVERT(DATETIME, CASE IsDate(lastUpdate)
WHEN 1 THEN lastUpdate
ELSE '12-30-1899'
END), GetDate()) < 31
Substituir '12-30-1899'
com a data padrão de sua escolha.
Outras dicas
Que tal escrever um cursor para percorrer o conteúdo, tentando converter cada entrada? Quando ocorrer um erro, imprima a chave primária ou outros detalhes de identificação para o registro do problema.Não consigo pensar em uma maneira baseada em conjunto de fazer isso.
Não totalmente baseado em set, mas se apenas 3 linhas em 1 milhão forem ruins, você economizará muito tempo
select * into BadDates
from Yourtable
where isdate(lastUpdate) = 0
select * into GoodDates
from Yourtable
where isdate(lastUpdate) = 1
então basta olhar a tabela BadDates e corrigir isso
O ISDATE() cuidaria das linhas que não foram formatadas corretamente se realmente estivessem sendo executadas primeiro.No entanto, se você observar o plano de execução, provavelmente descobrirá que o predicado DATEDIFF está sendo aplicado primeiro - portanto, a causa do seu problema.
Se você estiver usando o SQL Server Management Studio, acesse CTRL+eu para visualizar o plano de execução estimado para uma consulta específica.
Lembre-se, SQL não é uma linguagem processual e a lógica de curto-circuito pode funcionar, mas apenas se você for cuidadoso ao aplicá-la.
Que tal escrever um cursor para percorrer o conteúdo, tentando converter cada entrada?
Quando ocorrer um erro, imprima a chave primária ou outros detalhes de identificação para o registro do problema.
Não consigo pensar em uma maneira baseada em conjunto de fazer isso.
Editar - ah sim, esqueci de ISDATE().Definitivamente, uma abordagem melhor do que usar um cursor.+1 para SQLMenace.
Na sua chamada convert, você precisa especificar um terceiro parâmetro de estilo, por exemplo, o formato dos dados e horas armazenados como varchar, conforme especificado neste documento: CAST e CONVERT (T-SQL)
Imprima os registros.Dê a cópia impressa ao idiota que decidiu usar varchar(50) e peça-lhe para encontrar o registro do problema.
Da próxima vez, eles poderão entender a necessidade de escolher um tipo de dados apropriado.
O problema provavelmente é como os dados são armazenados, existem apenas dois formatos seguros
ISO AAAAMMDD
ISO 8601 aaaa-mm-dd Thh:mm:ss:mmm(sem espaços)
eles funcionarão independentemente do seu idioma.
Pode ser necessário fazer um SET DATEFORMAT YMD (ou como os dados estão armazenados) para que funcione
A verificação isdate() não cuidaria disso?
Execute isso para ver o que acontece
select isdate('20080131')
select isdate('01312008')
Tenho certeza de que alterar a tabela/coluna pode não ser uma opção devido a quaisquer requisitos do sistema legado, mas você já pensou em criar uma visualização que tenha a lógica de conversão de data incorporada, se estiver usando uma versão mais recente do sql, então você pode até usar uma visualização indexada?
Eu sugeriria limpar a bagunça e mudar a coluna para data e hora porque fazer coisas assim
WHERE datediff(dd, convert(datetime, lastUpdate), getDate()) < 31
não pode usar um índice e será muitas vezes mais lento do que se você tivesse uma coluna datetime,n e fizesse
where lastUpdate > getDate() -31
Você também precisa levar em consideração horas e segundos, é claro