Já me deparei com isso várias vezes e tenho certeza de que há uma boa razão para isso - mas como evitá-lo?
Tenho certeza que tem a ver com as peculiaridades ao redor isnumeric
. Em inglês, tenho uma visão que filtra em isnumeric(somefield) = 1
. Em seguida, tento consultá-lo usando um int
na minha where
cláusula e, como algum outro campo na tabela tem valores de caractere, a coisa toda falha.
Eu criei um violino SQL que mostra o erro.
Tentei fazer um cast/convert
no select na view, mas o mecanismo de consulta parece ignorá-lo.
Então - por que isso acontece e existe uma maneira limpa de lidar com isso?
O violino original teve um erro muito semelhante devido à falta de aspas na inserção. Esse não é o problema que eu estava tentando destacar. Corrigi a visão adicionando aspas a todos os valores que estão sendo inseridos.
A visualização não está sendo usada, pois está sendo "otimizada" pelo otimizador de consulta. Você pode ver isso observando o plano de consulta:
Você pode criar uma exibição materializada indexada e usar a
NOEXPAND
dica de tabela na consulta de destino para evitar essa otimização.Um exemplo:
Crie uma visualização, com SCHEMABINDING:
Crie um índice na exibição:
Insira os dados do teste:
Consulte a visualização:
Resultados:
O plano de consulta:
Observe a exclamação triangular no nó SELECT no plano de consulta acima - isso é um aviso:
O aviso de conversão de tipo pode ser eliminado se modificarmos a visualização assim:
Agora, a view retornará a
TestField
coluna digitada como um inteiro, e como estamos usandoNOEXPAND
em combinação com dados persistentes, a conversão de tipo implícita é eliminada:Como outras respostas explicaram, o problema é com a
INSERT
instrução e - quando isso é corrigido - com a exibição sendo otimizada.O resultado é que a consulta
where Testfield = 1000
tentará converterTestField
para numérico (devido às regras de precedência de tipo de dados ) para todas as linhas e, portanto, todos os valores da coluna.A ordem de execução é muito difícil de forçar, pois o otimizador é livre para reescrever uma consulta e não fornece garantias sobre o tempo, a ordem ou o número de avaliações de expressões escalares.
Uma maneira de contornar isso é usar uma
CASE
expressão. Ainda não é absolutamente garantido evitar esse tipo de problema, mas quase sempre funciona. Testado em sqlfiddle.com :Veja também esta resposta de Aaron Bertrand para uma explicação mais detalhada: CTE Error (nvarchar to numeric) e a sugestão de usar o mais robusto
TRY_CONVERT()
se você estiver em uma versão recente o suficiente.Ok - pode não ter ficado claro na minha pergunta, mas a principal coisa que eu queria fazer era consultar minha visão sem precisar usar aspas.
Consegui isso graças às versões mais recentes do SQL e da
try_convert
função. Se eu adicionar isso à minha visão no campo em questão, posso consultá-lo com sucesso sem usar aspas.Violino mostrando a mudança.
Curiosamente, o violino não retorna resultados - mas meu código real está funcionando dessa maneira, então vou chamar de vitória.
Não tem nada a ver com a vista. Tem a ver com o sindicato. O SQL Server está digitando sua união para
(int, int)
ele precisa digitar para(int, nvarchar(24))
. Embora contra-intuitivos, os tipos em sindicatos são sempre interessantes. No seu exemplo, a inserção está realmente falhando.Para corrigir isso, apenas certifique-se de consultar o TestField como se fosse uma coluna de texto (porque foi isso que você fez), e no insert verifique se está digitando corretamente os valores para não confundir o SQL Server.
Eu criei uma versão ligeiramente modificada do seu link sqlfiddle.com .
Os dados são preenchidos corretamente, podem ser selecionados usando a mesma instrução da exibição, mas a seleção na exibição em que
id
= 1000 ainda falha.Por quê? A culpa é do otimizador.
O otimizador vê através da visão e basicamente trata:
Como se fosse:
Quando vai otimizar, tem que decidir qual limitará melhor os dados retornados - e a cardinalidade de
TestField
é muito melhor do queIsNumeric(TestField)
.Eu admito, eu estou supondo lá. Ele pode ignorar a função porque apenas um valor numérico pode corresponder ou simplesmente avaliá-lo em segundo lugar porque não pode determinar facilmente a cardinalidade do resultado da função sem examinar a tabela e executar a função.
Ainda assim, a realidade básica é que o otimizador não filtrará as linhas não numéricas primeiro, mesmo que seja isso que você deseja que ele faça.
Se você transformou a exibição em uma exibição indexada e usou a
NOEXPAND
dica de tabela, poderá obter os resultados esperados; o otimizador deve se recusar a ir direto para a tabela e usar o índice na visão para o que precisa.A solução mais simples é evitar a conversão implícita. Se você não deixar de lado a conversão para SQL, então você tem o controle do que está acontecendo.
Antes do SQL 2012, forçar sua entrada para uma string (usando
'1000'
em vez de1000
) é provavelmente a solução mais simples.A partir do SQL 2012, você pode usar
TRY_CONVERT
(veja a última consulta no sqlfiddle ). Isso retorna um valor convertido onde pode e um NULL onde não pode. Claro, nesse ponto, você poderia acabar com a vista.Então:
WHERE
cláusula da exibição não seja avaliada antes daWHERE
cláusula da consulta.Você precisa curto-circuitar o teste e não o valor usando
case
algo assim:Colocando
na visão e testar contra isso no seu
select
também funcionaria.Vamos ser fundamentais.
Armadilha típica de iniciante. Função sobre campo (em geral) não significa otimização (a menos que use recursos como um índice sobre um campo calculado). A conversão neste cenário forçou a verificação de tabela E a conversão de cada linha. Tabela ruim e design de dados voltando para mordê-lo.
Como diz o RDFozz , sua melhor maneira é forçar uma comparação de string (
LIKE '1000'
) que é nativa de seus dados.