Portanto, consegui reproduzir o erro depois de perceber que CASTestava 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 CASToperaçã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, NULLnão pode ser convertido para UNIQUEIDENTIFIER, ou talvez NULLesteja 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 INTpara 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 INTpara 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) NULLno 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 INTpara 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 1no 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 INTpara 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 DATEconsulta 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 OPENQUERYcom 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 NULLconstante, igual ao Teste # 1.