Pourquoi cette expression CASE
:
SELECT CASE column
WHEN 'a' THEN '1'
WHEN 'b' THEN '2'
... c -> i
WHEN 'j' THEN '10'
WHEN 'k' THEN '11'
END [col]
FROM LinkedServer.database.dbo.table
Produire ce résultat?
Message d'erreur: Msg 8180, niveau 16, état 1, ligne 1
La ou les déclarations n'ont pas pu être préparées.
Msg 125, niveau 15, état 4, ligne 1
Les expressions de cas ne peuvent être imbriquées qu'au niveau 10.
Clairement, il n'y a pas d'expression CASE
imbriquée ici, bien qu'il y ait plus de 10 "branches".
Encore une bizarrerie. Cette fonction de valeur de table en ligne produit la même erreur:
ALTER FUNCTION [dbo].[fn_MyFunction]
(
@var varchar(20)
)
RETURNS TABLE
AS
RETURN
(
SELECT CASE column
WHEN 'a' THEN '1'
WHEN 'b' THEN '2'
... c -> i
WHEN 'j' THEN '10'
WHEN 'k' THEN '11'
END [col]
FROM LinkedServer.database.dbo.table
)
Mais un TVF multi-déclarations similaire fonctionne très bien:
ALTER FUNCTION [dbo].[fn_MyFunction]
(
@var varchar(20)
)
RETURNS @result TABLE
(
value varchar(max)
)
AS
BEGIN
INSERT INTO @result
SELECT CASE column
WHEN 'a' THEN '1'
WHEN 'b' THEN '2'
... c -> i
WHEN 'j' THEN '10'
WHEN 'k' THEN '11'
END [col]
FROM LinkedServer.database.dbo.table
RETURN;
END
De toute évidence, il n'y a pas d'expression
CASE
imbriquée ici.
Pas dans le texte de la requête, non. Mais l'analyseur étend toujours les expressions CASE
à la forme imbriquée:
SELECT CASE SUBSTRING(p.Name, 1, 1)
WHEN 'a' THEN '1'
WHEN 'b' THEN '2'
WHEN 'c' THEN '3'
WHEN 'd' THEN '4'
WHEN 'e' THEN '5'
WHEN 'f' THEN '6'
WHEN 'g' THEN '7'
WHEN 'h' THEN '8'
WHEN 'i' THEN '9'
WHEN 'j' THEN '10'
WHEN 'k' THEN '11'
END
FROM AdventureWorks2012.Production.Product AS p
Cette requête est locale (pas de serveur lié) et le calcul scalaire définit l'expression suivante:
C'est très bien lorsqu'il est exécuté localement, car parser ne voit pas une instruction CASE
imbriquée sur 10 niveaux de profondeur (bien qu'elle en transmette une aux étapes ultérieures de la compilation de la requête locale).
Cependant, avec un serveur lié, le texte généré peut être envoyé au serveur distant pour compilation. Si tel est le cas, le analyseur distant voit une instruction CASE
imbriquée de plus de 10 niveaux de profondeur et vous obtenez l'erreur 8180.
ne autre bizarrerie. Cette fonction de table en ligne produit la même erreur
La fonction en ligne est développée sur place dans le texte de requête d'origine, il n'est donc pas surprenant que les mêmes résultats d'erreur avec le serveur lié.
Mais un TVF multi-déclarations similaire fonctionne très bien
Similaire, mais pas le même. Le msTVF implique une conversion implicite en varchar(max)
, ce qui empêche l'envoi de l'expression CASE
au serveur distant. Étant donné que le CASE
est évalué localement, un analyseur ne voit jamais un CASE
sur-imbriqué et il n'y a pas d'erreur. Si vous changez la définition de table de varchar(max)
en type implicite du résultat CASE
- varchar(2)
- l'expression est distante avec msTVF et vous obtiendrez une erreur.
Finalement, l'erreur se produit lorsqu'un CASE
sur-imbriqué est évalué par le serveur distant. Si le CASE
n'est pas évalué dans l'itérateur de requête distante, aucune erreur ne se produit. Par exemple, ce qui suit inclut un CONVERT
qui n'est pas distant, donc aucune erreur ne se produit même si un serveur lié est utilisé:
SELECT CASE CONVERT(varchar(max), SUBSTRING(p.Name, 1, 1))
WHEN 'a' THEN '1'
WHEN 'b' THEN '2'
WHEN 'c' THEN '3'
WHEN 'd' THEN '4'
WHEN 'e' THEN '5'
WHEN 'f' THEN '6'
WHEN 'g' THEN '7'
WHEN 'h' THEN '8'
WHEN 'i' THEN '9'
WHEN 'j' THEN '10'
WHEN 'k' THEN '11'
END
FROM SQL2K8R2.AdventureWorks.Production.Product AS p
Mon intuition est que la requête est réécrite quelque part en cours de route pour avoir une structure CASE
légèrement différente, par exemple.
CASE WHEN column = 'a' THEN '1' ELSE CASE WHEN column = 'b' THEN '2' ELSE ...
Je crois que c'est un bogue dans le fournisseur de serveur lié que vous utilisez (en fait peut-être tous - je l'ai vu contre plusieurs). Je crois également que vous ne devriez pas retenir votre souffle en attendant un correctif, que ce soit dans la fonctionnalité ou dans le message d'erreur déroutant expliquant le comportement - cela a été signalé depuis longtemps, implique des serveurs liés (qui n'ont pas eu beaucoup d'amour depuis SQL Server 2000), et affecte beaucoup moins de personnes que ce message d'erreur déroutant , qui n'a pas encore été corrigé après la même longévité.
Comme souligne Paul , SQL Server étend votre expression CASE
à la variété imbriquée et le serveur lié ne l'aime pas. Le message d'erreur est déroutant, mais uniquement parce que la conversion sous-jacente de l'expression n'est pas immédiatement visible (ni intuitive en aucune façon).
Une solution de contournement (autre que la modification de fonction que vous avez ajoutée à votre question) consisterait à créer une vue ou une procédure stockée sur le serveur lié et à y faire référence au lieu de passer la requête complète via le fournisseur de serveur lié.
Un autre (en supposant que votre requête est vraiment simpliste et que vous voulez juste le coefficient numérique des lettres a-z) est d'avoir:
SELECT [col] = RTRIM(ASCII([column])-96)
FROM LinkedServer.database.dbo.table;
Si vous avez absolument besoin que cela fonctionne tel quel, je vous suggère de contacter directement le support et d'ouvrir un dossier, bien que je ne puisse pas garantir les résultats - ils peuvent simplement vous fournir des solutions de contournement auxquelles vous avez déjà accès sur cette page.
vous pouvez contourner cela en
SELECT COALESCE(
CASE SUBSTRING(p.Name, 1, 1)
WHEN 'a' THEN '1'
WHEN 'b' THEN '2'
WHEN 'c' THEN '3'
WHEN 'd' THEN '4'
WHEN 'e' THEN '5'
WHEN 'f' THEN '6'
WHEN 'g' THEN '7'
WHEN 'h' THEN '8'
WHEN 'i' THEN '9'
ELSE NULL
END,
CASE SUBSTRING(p.Name, 1, 1)
WHEN 'j' THEN '10'
WHEN 'k' THEN '11'
END)
FROM SQL2K8R2.AdventureWorks.Production.Product AS p
Une autre solution à ce problème consiste à utiliser une logique basée sur un ensemble, en remplaçant l'expression CASE
par une jointure gauche (ou une application externe) à une table de référence (ref
dans le code ci-dessous), qui peut être soit permanente, temporaire ou une table dérivée/CTE. Si cela est nécessaire dans plusieurs requêtes et procédures, je préférerais l'avoir comme table permanente:
SELECT ref.result_column AS [col]
FROM LinkedServer.database.dbo.table AS t
LEFT JOIN
( VALUES ('a', '1'),
('b', '2'),
('c', '3'),
---
('j', '10'),
('k', '11')
) AS ref (check_col, result_column)
ON ref.check_col = t.column ;