Portanto, consegui reproduzir o erro depois de perceber que CAST
estava sendo feito localmente, não na instância remota. Anteriormente, eu recomendara a migração para o SP3 na esperança de corrigir isso (parcialmente devido à impossibilidade de reproduzir o erro no SP3 e parcialmente devido a ser uma boa ideia, independentemente). No entanto, agora que posso reproduzir o erro, fica claro que a migração para o SP3, embora ainda seja provavelmente uma boa ideia, não vai corrigir isso. E também reproduzi o erro no SQL Server 2008 R2 RTM e no 2014 SP1 (usando um servidor vinculado local de "loopback" nos três casos).
Parece que esse problema tem a ver com o local em que a consulta está sendo executada ou pelo menos com parte (s) dela. Digo isso porque consegui fazer a CAST
operação funcionar, mas apenas incluindo uma referência a um objeto de banco de dados 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);
Isso realmente funciona. Mas o seguinte obtém o erro original:
SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (VALUES (1)) tmp(dummy);
Eu estou supondo que quando não há referências locais, toda a consulta é enviada para o sistema remoto para ser executada e, por algum motivo, NULL
não pode ser convertido para UNIQUEIDENTIFIER
, ou talvez NULL
esteja sendo traduzido incorretamente pelo driver OLE DB.
Com base nos testes que eu fiz, isso parece ser um erro, mas não tenho certeza se o erro está no SQL Server ou no driver OLEDB / SQL Server Native Client. No entanto, o erro de conversão ocorre no driver OLEDB e, portanto, não é necessariamente um problema de conversão de INT
para UNIQUEIDENTIFIER
(uma conversão que não é permitida no SQL Server), pois o driver não está usando o SQL Server para fazer conversões (o SQL Server também não permitir a conversão INT
para DATE
, mas o driver OLEDB lida com êxito, conforme mostrado em um dos testes).
Eu fiz três testes. Para os dois que foram bem-sucedidos, observei os planos de execução XML que mostram a consulta que está sendo executada remotamente. Para todos os três, capturei todos os eventos Exceptions ou OLEDB via SQL Profiler:
Eventos:
- Erros e avisos
- Atenção
- Exceção
- Avisos de execução
- Mensagem de erro do usuário
- OLEDB
- TSQL
- todos exceto :
- SQL: StmtRecompile
- Tipo estático XQuery
Filtros de coluna:
- Nome da Aplicação
- NÃO GOSTO % Intellisense%
- SPID
OS TESTES
Teste 1
CAST(NULL AS UNIQUEIDENTIFIER)
isso funciona
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
, (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
Parte relevante do plano de execução XML:
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="NULL">
<Const ConstValue="NULL" />
</ScalarOperator>
</DefinedValue>
...
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT 1 FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
Teste 2
CAST(NULL AS UNIQUEIDENTIFIER)
que falha
SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(observação: mantive a subconsulta lá, comentei, para que houvesse uma diferença a menos ao comparar os arquivos de rastreamento XML)
Teste 3
CAST(NULL AS DATE)
isso funciona
SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
-- , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
(observação: mantive a subconsulta lá, comentei, para que houvesse uma diferença a menos ao comparar os arquivos de rastreamento XML)
Parte relevante do plano de execução 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 você observar o Teste nº 3, ele está executando um SELECT TOP (2) NULL
no sistema "remoto". O rastreamento do SQL Profiler mostra que o tipo de dados desse campo remoto é de fato INT
. O rastreamento também mostra que o campo no lado do cliente (ou seja, de onde estou executando a consulta) é DATE
, conforme o esperado. A conversão de INT
para DATE
, algo que receberá um erro no SQL Server, funciona muito bem no driver OLEDB. O valor remoto é NULL
, portanto, ele é retornado diretamente, daí o <ColumnReference Column="Expr1002" />
.
Se você olhar para o Teste 1, ele está executando um SELECT 1
no sistema "remoto". O rastreamento do SQL Profiler mostra que o tipo de dados desse campo remoto é de fato INT
. O rastreamento também mostra que o campo no lado do cliente (ou seja, de onde estou executando a consulta) é GUID
, conforme o esperado. A conversão de INT
para GUID
(lembre-se, isso é feito no driver e o OLEDB chama de "GUID"), algo que receberá um erro no SQL Server, funciona perfeitamente no driver OLEDB. O valor remoto não é NULL
, portanto é substituído por um literal NULL
, daí o <Const ConstValue="NULL" />
.
O teste nº 2 falha, portanto, não há plano de execução. No entanto, ele consulta o sistema "remoto" com êxito, mas simplesmente não pode devolver o conjunto de resultados. A consulta que o SQL Profiler capturou é:
SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001"
Essa é exatamente a mesma consulta que está sendo feita no Teste 1, mas aqui está falhando. Existem outras pequenas diferenças, mas não consigo interpretar completamente a comunicação OLEDB. No entanto, o campo remoto ainda é exibido como INT
(wType = 3 = adInteger / número inteiro assinado de quatro bytes / DBTYPE_I4) enquanto o campo "cliente" ainda é exibido como GUID
(wType = 72 = adGUID / identificador globalmente exclusivo / DBTYPE_GUID). A documentação do OLE DB não ajuda muito, pois conversões de tipo de dados GUID , conversões de tipo de dados DBDATE e conversões de tipo de dados I4 mostram que a conversão de I4 em GUID ou DBDATE não é suportada, mas a DATE
consulta funciona.
Os arquivos XML de rastreamento para os três testes estão localizados no PasteBin. Se você quiser ver os detalhes de onde cada teste difere dos outros, salve-os localmente e faça um "diff" neles. Os arquivos são:
- NullGuidSuccess.xml
- NullGuidError.xml
- NullDateSuccess.xml
ERGO?
O que fazer sobre isso? Provavelmente, apenas a solução alternativa que observei na seção superior, considerando que o SQL Native Client - SQLNCLI11
- foi preterido no SQL Server 2012. A maioria das páginas do MSDN sobre o tópico do SQL Server Native Client tem o seguinte aviso no topo:
Atenção
O SQL Server Native Client (SNAC) não é suportado além do SQL Server 2012. Evite usar o SNAC em novos trabalhos de desenvolvimento e planeje modificar os aplicativos que atualmente o usam. O driver ODBC da Microsoft para SQL Server fornece conectividade nativa do Windows para o Microsoft SQL Server e o Banco de Dados SQL do Microsoft Azure.
Para mais informações, consulte:
ODBC ??
Eu configurei um servidor vinculado 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;
E então tentei:
SELECT CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
FROM [LocalODBC].[tempdb].[sys].[objects] rmt;
e recebeu o seguinte erro:
O provedor OLE DB "MSDASQL" para o servidor vinculado "LocalODBC" retornou a mensagem "A conversão solicitada não é suportada.".
Mensagem 7341, nível 16, estado 2, linha 53
Não é possível obter o valor atual da linha da coluna "(expressão gerada pelo usuário) .Expr1002" do provedor OLE DB "MSDASQL" para o servidor vinculado "LocalODBC".
PS
No que se refere ao transporte de GUIDs entre servidores remotos e locais, valores não NULL são tratados por meio de uma sintaxe especial. Observei as seguintes informações do evento OLE DB no rastreamento do SQL Profiler quando executei CAST(0x00 AS UNIQUEIDENTIFIER)
:
<RemoteQuery RemoteSource="Local" RemoteQuery=
"SELECT {guid'00000000-0000-0000-0000-000000000000'} "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001""
/>
PPS
Também testei via OPENQUERY
com a seguinte consulta:
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 conseguiu, mesmo sem a referência de objeto local. O arquivo XML de rastreamento do SQL Profiler foi postado no PasteBin em:
NullGuidSuccessOPENQUERY.xml
O plano de execução XML mostra isso usando uma NULL
constante, igual ao Teste # 1.