Ainsi, j'ai pu reproduire l'erreur après avoir réalisé que cela CAST
se faisait localement, pas sur l'instance distante. J'avais précédemment recommandé de passer au SP3 dans l'espoir de résoudre ce problème (en partie parce que je ne pouvais pas reproduire l'erreur sur SP3, et en partie parce que c'était une bonne idée malgré tout). Cependant, maintenant que je peux reproduire l'erreur, il est clair que passer au SP3, bien que probablement une bonne idée, ne va pas résoudre ce problème. Et j'ai également reproduit l'erreur dans SQL Server 2008 R2 RTM et 2014 SP1 (en utilisant un serveur lié local "loop-back" dans les trois cas).
Il semble que ce problème soit lié à emplacement d' exécution de la requête, ou au moins à l'exécution de certaines parties . Je dis cela parce que j'ai réussi à faire CAST
fonctionner l' opération, mais uniquement en incluant une référence à un objet DB local:
SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (SELECT TOP (1) 1 FROM [sys].[data_spaces]) tmp(dummy);
Cela fonctionne réellement. Mais ce qui suit obtient l'erreur d'origine:
SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (VALUES (1)) tmp(dummy);
Je suppose que lorsqu'il n'y a pas de références locales, l'intégralité de la requête est expédiée vers le système distant pour être exécutée, et pour une raison quelconque, NULL
il ne peut pas être converti en UNIQUEIDENTIFIER
, ou peut-être que le NULL
pilote OLE DB n'est pas traduit correctement.
Sur la base des tests que j'ai effectués, cela semble être un bogue, mais je ne sais pas si le bogue se trouve dans SQL Server ou le pilote SQL Server Native Client / OLEDB. Cependant, l'erreur de conversion se produit dans le pilote OLEDB et n'est donc pas nécessairement un problème de conversion à partir deINT
vers UNIQUEIDENTIFIER
(une conversion qui n'est pas autorisée dans SQL Server) car le pilote n'utilise pas SQL Server pour effectuer des conversions (SQL Server ne fait pas non plus permettre la conversion INT
en DATE
, mais le pilote OLEDB gère cela avec succès, comme indiqué dans l'un des tests).
J'ai effectué trois tests. Pour les deux qui ont réussi, j'ai regardé les plans d'exécution XML qui montrent la requête qui est exécutée à distance. Pour les trois, j'ai capturé toutes les exceptions ou événements OLEDB via SQL Profiler:
Événements:
- Erreurs et avertissements
- Attention
- Exception
- Avertissements d'exécution
- Message d'erreur utilisateur
- OLEDB
- TSQL
- tous sauf :
- SQL: StmtRecompile
- Type statique XQuery
Filtres de colonne:
- Nom de l'application
- PAS COMME % Intellisense%
- SPID
LES TESTS
Test 1
CAST(NULL AS UNIQUEIDENTIFIER)
ça marche
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
, (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
Partie pertinente du plan d'exécution XML:
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="NULL">
<Const ConstValue="NULL" />
</ScalarOperator>
</DefinedValue>
...
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT 1 FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
Test 2
CAST(NULL AS UNIQUEIDENTIFIER)
qui échoue
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(Remarque: j'ai conservé la sous-requête là-dedans, commenté, de sorte que ce serait une différence de moins lorsque je comparais les fichiers de trace XML)
Test 3
CAST(NULL AS DATE)
ça marche
SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(Remarque: j'ai conservé la sous-requête là-dedans, commenté, de sorte que ce serait une différence de moins lorsque je comparais les fichiers de trace XML)
Partie pertinente du plan d'exécution XML:
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="[Expr1002]">
<Identifier>
<ColumnReference Column="Expr1002" />
</Identifier>
</ScalarOperator>
</DefinedValue>
...
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
Si vous regardez le test n ° 3, il fait un SELECT TOP (2) NULL
sur le système "distant". La trace du Générateur de profils SQL montre que le type de données de ce champ distant est en fait INT
. La trace montre également que le champ côté client (c'est-à-dire où j'exécute la requête) est DATE
, comme prévu. La conversion de INT
à DATE
, quelque chose qui obtiendra une erreur dans SQL Server, fonctionne très bien dans le pilote OLEDB. La valeur distante est NULL
, elle est donc retournée directement, d'où<ColumnReference Column="Expr1002" />
.
Si vous regardez le test n ° 1, il fait un SELECT 1
sur le système "distant". La trace du Générateur de profils SQL montre que le type de données de ce champ distant est en fait INT
. La trace montre également que le champ côté client (c'est-à-dire où j'exécute la requête) est GUID
, comme prévu. La conversion de INT
à GUID
(rappelez-vous, cela se fait dans le pilote, et OLEDB l'appelle "GUID"), quelque chose qui obtiendra une erreur dans SQL Server, fonctionne très bien dans le pilote OLEDB. La valeur distante ne l'est pas NULL
, elle est donc remplacée par un littéral NULL
, d'où<Const ConstValue="NULL" />
.
Le test n ° 2 échoue, il n'y a donc pas de plan d'exécution. Cependant, il interroge le système "distant" avec succès, mais ne peut tout simplement pas renvoyer l'ensemble de résultats. La requête capturée par SQL Profiler est:
SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001"
C'est exactement la même requête qui est effectuée dans le test n ° 1, mais ici, elle échoue. Il existe d'autres différences mineures, mais je ne peux pas interpréter complètement la communication OLEDB. Cependant, le champ distant s'affiche toujours sous la forme INT
(wType = 3 = adInteger / entier signé sur quatre octets / DBTYPE_I4) tandis que le champ "client" s'affiche toujours sous la forme GUID
(wType = 72 = adGUID / identificateur global unique / DBTYPE_GUID). La documentation OLE DB ne nous aide pas beaucoup comme GUID Type de données Conversions , DBDATE Type de données Conversions et I4 des types de données Conversions montrent que la conversion de I4 soit GUID ou DBDATE est non pris en charge, mais les DATE
travaux de la requête.
Les fichiers XML de trace pour les trois tests se trouvent sur PasteBin. Si vous voulez voir les détails de la différence entre chaque test et les autres, vous pouvez les enregistrer localement puis faire un "diff" sur eux. Les fichiers sont:
- NullGuidSuccess.xml
- NullGuidError.xml
- NullDateSuccess.xml
ERGO?
Que faire à ce sujet? Probablement juste la solution de contournement que j'ai notée dans la section supérieure, étant donné que SQL Native Client - SQLNCLI11
- est obsolète à partir de SQL Server 2012. La plupart des pages MSDN sur le sujet de SQL Server Native Client ont l'avis suivant sur le Haut:
Attention
SQL Server Native Client (SNAC) n'est pas pris en charge au-delà de SQL Server 2012. Évitez d'utiliser SNAC dans les nouveaux travaux de développement et prévoyez de modifier les applications qui l'utilisent actuellement. Le pilote Microsoft ODBC pour SQL Server fournit une connectivité native de Windows à Microsoft SQL Server et à Microsoft Azure SQL Database.
Pour plus d'informations, veuillez consulter:
ODBC ??
J'ai configuré un serveur lié ODBC via:
EXEC master.dbo.sp_addlinkedserver
@server = N'LocalODBC',
@srvproduct=N'{my_server_name}',
@provider=N'MSDASQL',
@provstr=N'Driver={SQL Server};Server=(local);Trusted_Connection=Yes;';
EXEC master.dbo.sp_addlinkedsrvlogin
@rmtsrvname=N'LocalODBC',
@useself=N'True',
@locallogin=NULL,
@rmtuser=NULL,
@rmtpassword=NULL;
Et puis essayé:
SELECT CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
FROM [LocalODBC].[tempdb].[sys].[objects] rmt;
et a reçu l'erreur suivante:
Le fournisseur OLE DB "MSDASQL" pour le serveur lié "LocalODBC" a renvoyé le message "La conversion demandée n'est pas prise en charge.".
Msg 7341, niveau 16, état 2, ligne 53
Impossible d'obtenir la valeur de ligne actuelle de la colonne "(expression générée par l'utilisateur) .Expr1002" à partir du fournisseur OLE DB "MSDASQL" pour le serveur lié "LocalODBC".
PS
En ce qui concerne le transport des GUID entre les serveurs distants et locaux, les valeurs non NULL sont gérées via une syntaxe spéciale. J'ai remarqué les informations d'événement OLE DB suivantes dans la trace du Générateur de profils SQL lorsque j'ai exécuté CAST(0x00 AS UNIQUEIDENTIFIER)
:
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT {guid'00000000-0000-0000-0000-000000000000'} "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
PPS
J'ai également testé via OPENQUERY
avec la requête suivante:
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
--, (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM OPENQUERY([Local], N'SELECT 705 AS [dummy] FROM [TEMPTEST].[sys].[objects];') rmt;
et il a réussi, même sans la référence d'objet local. Le fichier XML de trace de SQL Profiler a été publié dans PasteBin à l'adresse suivante:
NullGuidSuccessOPENQUERY.xml
Le plan d'exécution XML le montre en utilisant une NULL
constante, comme dans le test n ° 1.