Eu tenho o seguinte esquema/dados no MSSQL Server 2019 ( SQLFiddle ):
CREATE TABLE products(
idn NUMERIC(9) PRIMARY KEY
);
CREATE TABLE sales(
idn NUMERIC(9) PRIMARY KEY,
pid VARCHAR(50) NOT NULL,
type VARCHAR(10) NOT NULL
);
INSERT INTO products (idn) VALUES (1);
INSERT INTO sales (idn, pid, type) VALUES (1, 1, 'number');
INSERT INTO sales (idn, pid, type) VALUES (2, 'Colgate', 'word');
sales
tem dados mistos, ou seja, VARCHAR
e NUMERIC
. O filtro de transações cuida JOIN
corretamente.
Por que o seguinte SELECT
falha (por algum motivo além do meu controle, a consulta gerada tem N
literais para strings.)?
SELECT
*
FROM
products
INNER JOIN sales ON products.idn = sales.pid
AND sales.type = N'number'
WHERE
products.idn in (1);
Não vejo por que transmitir NVARCHAR
para NUMERIC
é um problema:
SELECT CAST (N'1' as NUMERIC);
Se eu modificar um pouco a consulta, funciona:
SELECT
*
FROM
products
INNER JOIN sales ON products.idn = sales.pid
AND sales.type = N 'number'
WHERE
-- Selecting the same data from `sales`.
sales.pid in (1);
SELECT
*
FROM
products
INNER JOIN sales ON products.idn = sales.pid
-- Dropping the `N` prefix.
AND sales.type = 'number'
WHERE
products.idn in (1);
Ocorrerá um erro de conversão em tempo de execução quando for feita uma tentativa de converter o
sales.pid
valor 'Colgate' para numeric(9) para avaliar os critérios de junção. Se isso realmente acontece ou não, depende da ordem de avaliação no plano de execução.Abaixo está o predicado de verificação de índice clusterizado da tabela de vendas do plano de execução literal Unicode , mostrando que a conversão ocorre antes que a condição 'number' seja avaliada:
O plano com o literal não Unicode tem a mesma forma, exceto que a ordem do predicado no operador de varredura é invertida:
Embora o literal não Unicode (ou parâmetro) possa solucionar o problema, a consulta ainda estará vulnerável a erros de tempo de execução. Isso pode ser resolvido com
TRY_CAST
,TRY_CONVERT
,CASE
expressão etc., mas seria melhor corrigir o modelo de dados para garantir que apenas tipos de dados semelhantes ou compatíveis sejam comparados.Isso ocorre porque você está usando uma coluna que contém valores de texto (pid) para unir em uma coluna numérica
Quando você faz uma junção dessas 2 tabelas, o SQL precisa comparar todas as linhas de idn com todas as linhas de pid. Para compará-los, ele precisa converter seu varchar em numérico.
Como o SQL pode converter 'Colgate' em um número? é por isso que falha.
Você deve ler sobre normalização de banco de dados.
Sua coluna PID não parece boa e você provavelmente precisaria de outra tabela com um ID do produto e uma descrição do produto (onde 'Colgate' será a descrição) para que funcione como você espera.
Porque está recebendo um plano de execução que avalia
para a linha com
antes de avaliar o predicado
int
tem uma precedência de tipo de dados mais alta do quevarchar
para executar a comparação, o 'varchar' é convertido implicitamente emint
. Esta conversão falha para "Colgate".A maneira de bloquear a ordem de avaliação para essas duas condições é com uma expressão CASE. POR EXEMPLO
Isso pode causar problemas de desempenho em escala, mas esse é um dos muitos motivos pelos quais cada coluna deve ter apenas um tipo de dados.
O otimizador de consulta pode alterar a ordem em que as coisas são feitas para obter seu resultado da maneira mais rápida entre as opções que ele apresenta (dado que o resultado da consulta não é alterado).
Sabendo disso, se você verificar o plano de execução, isso ajudará você a entender o motivo pelo qual você obtém o erro com uma consulta e não com a outra, considerando os dados de exemplo que você forneceu:
Pergunta 1:
Plano de consulta
Pergunta 2:
Plano de consulta
A consulta 1 funcionou porque quando você removeu o N (que é usado para converter uma string para
nvarchar
) o SQL Server não precisou realizar uma conversão implícita desales.type
varchar para nvarchar. Neste casosales.pid
tem uma conversão implícita em ambas as consultas pois está sendo comparada aproducts.idn
qual tem um tipo de dado diferente. O otimizador de consultas decide que a comparação de uma coluna sem conversão implícita é mais barata, então ele começa executando o predicadosales.type = 'number'
e gera um subconjunto composto pelas linhas que contém todos os valores desales.type
como números (e você pode converter números reais de um string cloumn sem nenhum problema). Assim, a consulta funciona.A consulta 2 gera um erro porque ambos os predicados de
sales
têm conversões implícitas e o otimizador escolheu começar desales.pid
antes de filtrar apenas as linhas que mantêm os números. Quando ele tenta converter o valor'Colgate'
em umnumeric(9,0)
tipo de dados, você recebe o erro.