Tive um problema muito estranho hoje com a função TSQL.
A função possui vários parâmetros (int e bit). Um dos (int) é definido com valor NULL.
Ao chamar uma função com NULL dentro da função params retorna ERRO (o erro está ok, pois tento converter '%%'
para int
valor). Quando a função é chamada com o parâmetro @p1 cujo valor é NULL
a função que está funcionando.
Alguém pode me explicar porque funciona assim?
Nós estamos usando:
Microsoft SQL Server 2012 (SP1) - 11.0.3000.0 (X64)
Oct 19 2012 13:38:57
Copyright (c) Microsoft Corporation
Business Intelligence Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)
MEU CÓDIGO É: O código que causa o erro:
((@p IS NOT NULL) AND (CONS.IDp = @p)) OR
((@p IS NULL) AND (UFCFD.IDUser IS NOT NULL) AND
(CONS.IDp = CAST(UFCFD.Value1 AS INT))
OBSERVAÇÃO: Editei a parte "causando erro" porque meu colega colocou uma verificação de proteção. Eu removo essa parte para mostrar o estado original da função (a fonte era:(CONS.IDp = CASE WHEN ISNUMERIC(UFCFD.Value1) = 1 THEN CAST(UFCFD.Value1 AS INT) ELSE NULL END))
A chamada que causa o erro:
SELECT * FROM dbo.myFunc(NULL);
A chamada que funciona:
DECLARE @p INT = NULL;
SELECT * FROM dbo.myFunc(@p);
PS Se a pergunta não estiver clara, por favor me avise via comentário.
EDIT2:
Este é o texto completo da função:
CREATE FUNCTION dbo.myFunc
(
@IDUser INT,
@p INT,
@IDGrid INT,
@IsWithLead BIT,
@IDLeadValueResource INT,
@IDLanguage INT,
@IsAllPresent BIT,
@IDAllValueResource INT
)
RETURNS TABLE
AS
RETURN
(
SELECT
-99999999 AS ID,
dbo.funs_GetResourceText(@IDLanguage,@IDAllValueResource) AS [Text],
-99999999 AS Sort
WHERE (@IsAllPresent = 1)
UNION ALL
SELECT
0 AS ID,
dbo.funs_GetResourceText(@IDLanguage,@IDLeadValueResource) AS [Text],
0 AS Sort
WHERE (@IsWithLead = 1)
UNION ALL
SELECT
CONS.IDConsumerType AS ID,
CASE
WHEN @IDLanguage = 40001 THEN CONS.ConsumerType_Name_en
WHEN @IDLanguage = 40002 THEN CONS.ConsumerType_Name_it
WHEN @IDLanguage = 40003 THEN CONS.ConsumerType_Name_de
WHEN @IDLanguage = 40004 THEN CONS.ConsumerType_Name_fr
ELSE CONS.ConsumerType_Name_en END AS [Text],
ROW_NUMBER() OVER(ORDER BY CONS.SegmentOrder ASC) AS Sort
FROM
dbo.V_ConsumerTypes AS CONS
LEFT JOIN dbo.Grids GRD ON (GRD.ID = @IDGrid)
LEFT JOIN dbo.CONFieldCollection_FieldDefinition FCFD ON
(FCFD.IDFieldCollection = GRD.IDGridFieldCollection) AND
(FCFD.FieldName LIKE '%IDp%')
LEFT JOIN dbo.CONUser_FieldCollection_FieldDefinition UFCFD ON
(UFCFD.IDFieldCollection = GRD.IDGridFieldCollection) AND
(UFCFD.IDUser = @IDUser) AND
(UFCFD.IDFieldCollections_FieldDefinitions = FCFD.ID)
WHERE
((@p IS NOT NULL) AND (CONS.IDp = @p)) OR
((@p IS NULL) AND (UFCFD.IDUser IS NOT NULL) AND
(CONS.IDp = CAST(UFCFD.Value1 AS INT))
)
A chamada que funciona é:
DECLARE @IDUser INT = -100;
DECLARE @p INT = NULL;
DECLARE @IDGrid INT = 17;
DECLARE @IsWithLead BIT = 0;
DECLARE @IDLeadValueResource INT = 0;
DECLARE @IDLanguage INT = 40002;
DECLARE @IsAllPresent BIT = 1
DECLARE @IDAllValueResource INT = -177;
DECLARE @dt DATETIME = GetDate();
SELECT * FROM dbo.fun_ClROME_MdFillComboConsumerTypes(@IDUser, @p, @IDGrid, @IsWithLead, @IDLeadValueResource,
@IDLanguage, @IsAllPresent, @IDAllValueResource)
ORDER BY Sort
Se usarmos NULL
em vez de @p
, recebo um erro, porque tento converter '%%'
para int
- isso está correto.
O problema é que obviamente a função se comporta de maneira diferente, o que é um grande problema.
Acho que você está tentando resolver o problema errado. Como sugeri acima, acho que você está obtendo resultados diferentes simplesmente porque atualmente tem planos diferentes (um para o literal e outro para a variável). O plano que você está obtendo atualmente que causa um erro está tentando a conversão antes que as linhas sejam filtradas, mas ambos os métodos podem causar um erro se o plano que atualmente "funciona bem" for recompilado.
O que você deve fazer é usar uma
CASE
expressão para garantir que apenas os valores numéricos que podem ser convertidos em umINT
sejam realmente convertidos. Você precisa controlar isso explicitamente porque nem sempre pode confiar nas linhas de filtragem do SQL Server antes de tentar fazer cálculos.A partir de 2012, você pode simplesmente substituir isso:
Com isso:
Se você também precisa oferecer suporte a versões mais antigas, pode fazer isso (já que sabemos que
ISNUMERIC()
não é suficiente ):Uma
CASE
expressão é bastante confiável para forçar o SQL Server a avaliar expressões em uma ordem específica, mas observe que há exceções .