Quindi, sono stato in grado di riprodurre l'errore dopo aver realizzato che CAST
era stato fatto localmente, non sull'istanza remota. In precedenza avevo consigliato di passare a SP3 nella speranza di risolvere questo problema (in parte a causa della mancata riproduzione dell'errore su SP3 e in parte perché era una buona idea a prescindere). Tuttavia, ora che posso riprodurre l'errore, è chiaro che passare a SP3, anche se probabilmente è una buona idea, non risolverà questo problema. E ho anche riprodotto l'errore in SQL Server 2008 R2 RTM e 2014 SP1 (utilizzando un server collegato locale "loop-back" in tutti e tre i casi).
Sembra che questo problema ha a che fare con cui l'esecuzione della query, o almeno dove parte (s) di esso sono in esecuzione. Dico questo perché sono stato in grado di far CAST
funzionare l' operazione, ma solo includendo un riferimento a un oggetto DB locale:
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);
Funziona davvero. Ma quanto segue ottiene l'errore originale:
SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (VALUES (1)) tmp(dummy);
Immagino che quando non ci sono riferimenti locali, l'intera query viene spedita al sistema remoto per essere eseguita e per qualche motivo NULL
non è possibile convertirla in s UNIQUEIDENTIFIER
, o forse NULL
viene tradotta in modo errato dal driver OLE DB.
In base ai test eseguiti, questo sembra essere un bug, ma non sono sicuro che il bug si trovi all'interno di SQL Server o del driver SQL Server Native Client / OLEDB. Tuttavia, l'errore di conversione si verifica all'interno del driver OLEDB e quindi non è necessariamente un problema di conversione da INT
a UNIQUEIDENTIFIER
(una conversione non consentita in SQL Server) poiché il driver non utilizza SQL Server per eseguire conversioni (anche SQL Server non consentire la conversione INT
in DATE
, ma il driver OLEDB lo gestisce correttamente, come mostrato in uno dei test).
Ho eseguito tre prove. Per i due che hanno avuto successo, ho esaminato i piani di esecuzione XML che mostrano la query che viene eseguita in remoto. Per tutti e tre, ho acquisito eventuali eccezioni o eventi OLEDB tramite SQL Profiler:
eventi:
- Errori e avvisi
- Attenzione
- Eccezione
- Avvertenze di esecuzione
- Messaggio di errore dell'utente
- OLE DB
- TSQL
- tutti tranne :
- SQL: StmtRecompile
- Tipo statico XQuery
Filtri a colonna:
- Nome dell'applicazione
- SPID
I TEST
Test 1
CAST(NULL AS UNIQUEIDENTIFIER)
che funzioni
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
, (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
Parte pertinente del piano di esecuzione 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)
ciò fallisce
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(nota: ho mantenuto la sottoquery lì, commentata, in modo che sarebbe una differenza in meno quando ho confrontato i file di traccia XML)
Test 3
CAST(NULL AS DATE)
che funzioni
SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(nota: ho mantenuto la sottoquery lì, commentata, in modo che sarebbe una differenza in meno quando ho confrontato i file di traccia XML)
Parte pertinente del piano di esecuzione 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""
/>
Se si guarda al Test n. 3, sta eseguendo una operazione SELECT TOP (2) NULL
sul sistema "remoto". La traccia di SQL Profiler mostra che il tipo di dati di questo campo remoto è in realtà INT
. La traccia mostra anche che il campo sul lato client (ovvero da dove sto eseguendo la query) è DATE
, come previsto. La conversione da INT
a DATE
, qualcosa che otterrà un errore in SQL Server, funziona perfettamente nel driver OLEDB. Il valore remoto è NULL
, quindi viene restituito direttamente, quindi il <ColumnReference Column="Expr1002" />
.
Se si guarda al Test n. 1, sta eseguendo una operazione SELECT 1
sul sistema "remoto". La traccia di SQL Profiler mostra che il tipo di dati di questo campo remoto è in realtà INT
. La traccia mostra anche che il campo sul lato client (ovvero da dove sto eseguendo la query) è GUID
, come previsto. La conversione da INT
a GUID
(ricorda, questo viene fatto all'interno del driver, e OLEDB lo chiama "GUID"), qualcosa che otterrà un errore in SQL Server, funziona bene all'interno del driver OLEDB. Il valore remoto non NULL
lo è, quindi viene sostituito con un valore letterale NULL
, quindi il <Const ConstValue="NULL" />
.
Il test n. 2 ha esito negativo, quindi non esiste un piano di esecuzione. Tuttavia, interroga correttamente il sistema "remoto", ma non riesce a restituire il set di risultati. La query acquisita da SQL Profiler è:
SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001"
Questa è esattamente la stessa query che viene eseguita nel Test n. 1, ma qui non sta funzionando. Ci sono altre differenze minori, ma non riesco a interpretare completamente la comunicazione OLEDB. Tuttavia, il campo remoto viene ancora visualizzato come INT
(wType = 3 = adInteger / intero con segno a quattro byte / DBTYPE_I4) mentre il campo "client" viene ancora visualizzato come GUID
(wType = 72 = adGUID / identificatore univoco globale / DBTYPE_GUID). La documentazione OLE DB non aiuta molto poiché le conversioni di tipi di dati GUID , le conversioni di tipi di dati DBDATE e le conversioni di tipi di dati I4 mostrano che la conversione da I4 a GUID o DBDATE non è supportata, tuttavia la DATE
query funziona.
I file XML di traccia per i tre test si trovano su PasteBin. Se vuoi vedere i dettagli di dove ogni test differisce dagli altri, puoi salvarli localmente e quindi fare un "diff" su di essi. I file sono:
- NullGuidSuccess.xml
- NullGuidError.xml
- NullDateSuccess.xml
QUINDI?
Cosa fare al riguardo? Probabilmente solo la soluzione che ho notato nella sezione in alto, dato che SQL Native Client - SQLNCLI11
- è obsoleto a partire da SQL Server 2012. La maggior parte delle pagine MSDN sull'argomento di SQL Server Native Client presenta la seguente nota nella superiore:
avvertimento
SQL Server Native Client (SNAC) non è supportato oltre SQL Server 2012. Evitare di utilizzare SNAC nel nuovo lavoro di sviluppo e pianificare di modificare le applicazioni che attualmente lo utilizzano. Il driver Microsoft ODBC per SQL Server fornisce connettività nativa da Windows a Microsoft SQL Server e al database SQL di Microsoft Azure.
Per maggiori informazioni, consultare:
ODBC ??
Ho impostato un server collegato ODBC tramite:
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;
E poi provato:
SELECT CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
FROM [LocalODBC].[tempdb].[sys].[objects] rmt;
e ricevuto il seguente errore:
Il provider OLE DB "MSDASQL" per il server collegato "LocalODBC" ha restituito il messaggio "La conversione richiesta non è supportata".
Messaggio 7341, livello 16, stato 2, riga 53
Impossibile ottenere il valore di riga corrente della colonna "(espressione generata dall'utente) .Expr1002" dal provider OLE DB "MSDASQL" per il server collegato "LocalODBC".
PS
Per quanto riguarda il trasporto di GUID tra server remoti e locali, i valori non NULL vengono gestiti tramite una sintassi speciale. Ho notato le seguenti informazioni sugli eventi OLE DB nella traccia di SQL Profiler durante l'esecuzione CAST(0x00 AS UNIQUEIDENTIFIER)
:
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT {guid'00000000-0000-0000-0000-000000000000'} "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
PPS
Ho anche testato OPENQUERY
con la seguente query:
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;
e ha avuto successo, anche senza il riferimento all'oggetto locale. Il file XML di traccia di SQL Profiler è stato pubblicato su PasteBin in:
NullGuidSuccessOPENQUERY.xml
Il piano di esecuzione XML lo mostra usando una NULL
costante, come nel Test n. 1.