AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 182728
Accepted
IronicMuffin
IronicMuffin
Asked: 2017-08-05 10:02:19 +0800 CST2017-08-05 10:02:19 +0800 CST 2017-08-05 10:02:19 +0800 CST

Por que o SQL tenta converter linhas que são excluídas pela minha cláusula where?

  • 772

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 intna minha whereclá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/convertno 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.

sql-server view
  • 7 7 respostas
  • 2810 Views

7 respostas

  • Voted
  1. Best Answer
    Hannah Vernon
    2017-08-05T11:05:44+08:002017-08-05T11:05:44+08:00

    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:

    insira a descrição da imagem aqui

    Você pode criar uma exibição materializada indexada e usar a NOEXPANDdica de tabela na consulta de destino para evitar essa otimização.

    Um exemplo:

    USE tempdb;
    
    IF OBJECT_ID(N'dbo.TestView', N'V') IS NOT NULL
    BEGIN
        DROP VIEW dbo.TestView;
        DROP TABLE dbo.Test;
    END
    CREATE TABLE dbo.Test 
    (
        TableKey int
        , TestField nvarchar(24)
    );
    GO
    

    Crie uma visualização, com SCHEMABINDING:

    CREATE VIEW dbo.TestView 
    WITH SCHEMABINDING
    AS
        SELECT 
            t.TableKey
            , t.TestField
        FROM
           dbo.Test t
        WHERE ISNUMERIC(t.TestField) = 1;
    GO
    

    Crie um índice na exibição:

    CREATE UNIQUE CLUSTERED INDEX PK_TestView
    ON dbo.TestView (TableKey);
    GO
    

    Insira os dados do teste:

    INSERT INTO dbo.Test
    select 0,        '0'
    union select 1,  'Rejected'
    union select 2,  'Unlinked'
    union select 0,  '0'
    union select 3,  '1'
    union select 162,'1000'
    union select 16, '10000'
    union select 17, '10010'
    union select 18, '10011'
    union select 19, '10012'
    union select 20, '10031'
    union select 21, '10041'
    

    Consulte a visualização:

    SELECT * 
    FROM dbo.TestView WITH (NOEXPAND)
    WHERE Testfield = 1000
    

    Resultados:

    ╔══════════╦═══════════╗
    ║ TableKey ║ TestField ║
    ╠══════════╬═══════════╣
    ║ 162 ║ 1000 ║
    ╚══════════╩═══════════╝
    

    O plano de consulta:

    insira a descrição da imagem aqui

    Observe a exclamação triangular no nó SELECT no plano de consulta acima - isso é um aviso:

    A conversão de tipo na expressão (CONVERT_IMPLICIT(int,[tempdb].[dbo].[TestView].[TestField],0)) pode afetar "CardinalityEstimate" na escolha do plano de consulta, a conversão de tipo na expressão (CONVERT_IMPLICIT(int,[tempdb] .[dbo].[TestView].[TestField],0)=CONVERT_IMPLICIT(int,[@1],0)) pode afetar "SeekPlan" na escolha do plano de consulta

    O aviso de conversão de tipo pode ser eliminado se modificarmos a visualização assim:

    CREATE VIEW dbo.TestView 
    WITH SCHEMABINDING
    AS
        SELECT 
            t.TableKey
            , TestField = TRY_CONVERT(int, t.TestField)
        FROM
           dbo.Test t
        WHERE ISNUMERIC(t.TestField) = 1;
    

    Agora, a view retornará a TestFieldcoluna digitada como um inteiro, e como estamos usando NOEXPANDem combinação com dados persistentes, a conversão de tipo implícita é eliminada:

    insira a descrição da imagem aqui

    • 9
  2. ypercubeᵀᴹ
    2017-08-05T11:23:40+08:002017-08-05T11:23:40+08:00

    Como outras respostas explicaram, o problema é com a INSERTinstrução e - quando isso é corrigido - com a exibição sendo otimizada.

    O resultado é que a consulta where Testfield = 1000tentará converter TestFieldpara 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 CASEexpressão. Ainda não é absolutamente garantido evitar esse tipo de problema, mas quase sempre funciona. Testado em sqlfiddle.com :

    CREATE VIEW dbo.TestView 
    AS
      SELECT  
        TableKey,
        TestField = CASE WHEN IsNumeric(TestField) = 1 THEN TestField END
      FROM
        dbo.Test
      WHERE
        IsNumeric(TestField) = 1 ;
    

    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.

    • 7
  3. IronicMuffin
    2017-08-05T11:12:17+08:002017-08-05T11:12:17+08:00

    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_convertfunçã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.

    • 2
  4. Evan Carroll
    2017-08-05T10:19:27+08:002017-08-05T10:19:27+08:00

    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.

    insert into dbo.Test
    select 0,   '0'
    union select 1, 'Rejected'
    union select 2, 'Unlinked'
    union select 0, '0'
    union select 3, '1'
    union select 162,   '1000'
    union select 16,    '10000'
    union select 17,    '10010'
    union select 18,    '10011'
    union select 19,    '10012'
    union select 20,    '10031'
    union select 21,    '10041'
    ;
    select * from dbo.Test
    where Testfield = '1000';
    
    • 1
  5. RDFozz
    2017-08-05T11:52:03+08:002017-08-05T11:52:03+08:00

    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:

    SELECT TableKey, TestField
      FROM dbo.TestView
     WHERE TestField = 1000
    ;
    

    Como se fosse:

    SELECT TableKey, TestField
      FROM dbo.Test
     WHERE IsNumeric(TestField) = 1
       AND TestField = 1000
    ;
    

    Quando vai otimizar, tem que decidir qual limitará melhor os dados retornados - e a cardinalidade de TestFieldé muito melhor do que IsNumeric(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 NOEXPANDdica 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 de 1000) é 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:

    1. O SQL Server não trata as exibições como tabelas e pode reorganizá-las para que a WHEREcláusula da exibição não seja avaliada antes da WHEREcláusula da consulta.
    2. Não confie em conversões implícitas para fazer o que você quer do jeito que você quer.
    • 1
  6. LoztInSpace
    2017-08-05T18:37:59+08:002017-08-05T18:37:59+08:00

    Você precisa curto-circuitar o teste e não o valor usando casealgo assim:

      SELECT TableKey, TestField
      FROM dbo.Test
     WHERE 
       case when isnumeric(testfield)=1 then testfield else null end = 1000
    

    Colocando

    case when isnumeric(testfield)=1 then testfield else null end as newTestField
    

    na visão e testar contra isso no seu selecttambém funcionaria.

    • 1
  7. TomTom
    2017-08-05T23:10:18+08:002017-08-05T23:10:18+08:00

    Vamos ser fundamentais.

    • Uma visão é - da maneira que você fez - avaliada em tempo de execução.
    • Para verificar o filtro, ele deve comparar todas as linhas, sejam elas numéricas ou não. Não há "otimização" na exibição. A exibição forçaria uma conversão de cada linha para uma verificação numérica.
    • O mesmo com sua consulta. A consulta torna a visualização redundante - a verificação é, portanto, eliminada logicamente. Mas ao comparar com o valor numérico, você AINDA deve converter todas as linhas.

    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.

    • 1

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve