Por que essa conversão explícita está causando problemas apenas com um servidor vinculado?

21

Estou consultando dados de um servidor vinculado por meio de uma exibição no servidor de origem. A visualização deve incluir algumas colunas padronizadas, como Created, Modifiede Deleted, mas, neste caso, a tabela no servidor de origem não possui nenhuma informação adequada. As colunas são, portanto, explicitamente convertidas em seus respectivos tipos. Atualizei a visualização, alterando uma coluna de

NULL AS Modified

para

CAST(NULL as DateTime) as Modified

No entanto, após executar esta atualização, a exibição está acionando a seguinte mensagem de erro:

A mensagem 7341, nível 16, estado 2, linha 3 não pode obter o valor atual da linha da coluna "(expressão gerada pelo usuário) .Expr1002" do provedor OLE DB "SQLNCLI11" para o servidor vinculado "".

Fizemos essa alteração "conversão explícita" geralmente no servidor de origem sem preocupações, e suspeito que o problema esteja relacionado à versão dos servidores envolvidos. Realmente não precisamos aplicar esse elenco, mas parece mais limpo. No momento, estou curioso para saber por que isso está acontecendo.

Versão do servidor (origem):

Microsoft SQL Server 2012 - 11.0.5058.0 (X64) 14 de maio de 2014 18:34:29 Direitos autorais (c) Microsoft Corporation Enterprise Edition (64 bits) no Windows NT 6.1 (Build 7601: Service Pack 1) (Hypervisor)

Versão do servidor (vinculada):

Microsoft SQL Server 2008 R2 (SP1) - 10.50.2500.0 (X64) 17 de junho de 2011 00:54:03 Direitos autorais (c) Microsoft Corporation Enterprise Edition (64 bits) no Windows NT 6.1 (Build 7601: Service Pack 1) (Hypervisor )

Editar
Acabei de perceber que cometi um erro ao não postar todas as colunas em questão e devo me desculpar por deixar de fora um detalhe importante. Não sei como não percebi isso antes. A questão ainda permanece, no entanto.

A conversão incorreta não acontece com a conversão para DateTime, mas com uma coluna sendo convertida em UniqueIdentifier.

Este é o culpado:

CAST(NULL AS UniqueIdentifier) AS [GUID]

UniqueIdentifiers são suportados no SQL Server 2008 R2 e, conforme mencionado nos comentários, a consulta executada pela exibição é executada corretamente no servidor vinculado.

Krystah
fonte
Você tem uma configuração ANSI NULL diferente em cada servidor? Agrupamento diferente?
Randolph West
Ambos os servidores têm ANSI NULL = 0. O servidor de origem possui o agrupamento Danish_Norwegian_CI_ASe o servidor vinculado o agrupamento SQL_Danish_Pref_CP1_CI_AS, mas a COLLATEcláusula não pode ser aplicada às DateTimecolunas, por isso não fui muito longe!
precisa saber é o seguinte
Isso falharia se houvesse select Null from ...na consulta WITH ou aninhada e CASTem outra?
Stoleg 22/08/16
Sem a conversão explícita, ela será tratada como se INTvocê alterasse o tipo de dados ao fazê-lo. Não sei por que isso lhe daria essa mensagem de erro.
Martin Smith
Eu já havia tentado agrupar os valores selecionados com valores reais em um CTE, depois selecioná-los e aplicar os NULLs fundidos na declaração após o CTE sem sorte. Tentei sua sugestão, mantendo os NULLs no CTE e os convertendo na instrução que consulta o CTE, mas também gera o mesmo erro.
precisa saber é o seguinte

Respostas:

13

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
    • todos
  • TSQL
    • todos exceto :
      • SQL: StmtRecompile
      • Tipo estático XQuery

Filtros de coluna:

  • Nome da Aplicação
    • NÃO GOSTO % Intellisense%
  • SPID
    • Maior ou igual a 50

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 &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;"
     />
  • 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 &quot;Expr1002&quot; FROM &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;" 
     />

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:

  1. NullGuidSuccess.xml
  2. NullGuidError.xml
  3. 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'} &quot;Expr1002&quot; FROM &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;" 
 />

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.

Solomon Rutzky
fonte
Recriei esse problema em 2012 sp3-cu1.
Bob Klimes
@BobKlimes Interesting. Isso está usando um servidor vinculado a uma instância ou loopback de 2008 R2?
Solomon Rutzky
O servidor vinculado remoto é 2008 R2 sp2 + MS15-058 (10.50.4339.0).
Bob Klimes
1
não parece estar relacionado à versão. Testaram vários combos de 2008r2,2012,2014,2016 e todos até agora produziram erros, até 2008r2-2008r2.
precisa saber é o seguinte
2
Que problema extremamente obscuro. Muito obrigado pela compreensão e pesquisa, @srutzky, é muito apreciado. Manterei em mente a decriptação do SNAC para trabalhos futuros e o fallback da solução alternativa acima mencionada. Ótimo trabalho!
precisa saber é o seguinte
4

Existe apenas uma solução alternativa feia - use alguma data constante como em '1900-01-01'vez de null.

CAST('1900-01-01' as DateTime) as Modified

Após a importação, você pode atualizar as colunas com 1900-01-01volta para Nulo.

Esse é o tipo de recurso / bug do SQL 2012, conforme aqui .

Editar: substituído 1900-00-00por data válida 1900-01-01conforme o comentário @a_horse_with_no_name abaixo.

Anton Krouglov
fonte
Vale a pena mencionar esta solução alternativa, mas pode não ser mais relevante agora que o OP esclareceu que o culpado do problema é uma uniqueidentifiercoluna. Ou talvez pudesse ser adaptado - algo como CAST('00000000-0000-0000-0000-000000000000' AS UniqueIdentifier) AS [GUID], talvez?
Andriy M
Obrigado rapazes. A conversão para um GUID ou DateTime vazio funciona, mas preciso entender por que isso está acontecendo. Também vale mencionar que não estou importando nada, portanto não há possibilidade de alterar os dados de origem.
precisa saber é o seguinte
1
1900-00-00é uma data inválida e não será aceita.
A_horse_with_no_name 23/08
@a_horse_with_no_name: erro estúpido, corrigido.
Anton Krouglov 23/08/16
2

O problema está relacionado às conversões de tipo de dados (conforme encontrado nos comentários).

Considere o seguinte:

SELECT NULL as NullColumn INTO SomeTable;
EXEC sp_help SomeTable;
DROP TABLE SomeTable;

Observe que o NullColumné do tipo int. O SQL Server não gosta de converter intvalores para uniqueidentifier. Esta SELECTdeclaração falhará em uma conversão de tipo de dados:

--Just a SELECT from nothing
SELECT CAST(CAST(NULL as int) as uniqueidentifier);
--
--or to see it from a physical table:
SELECT NULL as NullColumn INTO SomeTable;
SELECT CAST(NullColumn as uniqueidentifier) FROM SomeTable;
DROP TABLE SomeTable;

Msg 529, Nível 16, Estado 2, Linha 3

A conversão explícita do tipo de dados int para uniqueidentifier não é permitida.

Embora esse valor específico (NULL) possa ser convertido em um GUID, o SQL Server lança o erro com base na conversão do tipo de dados, antes mesmo de analisar os valores específicos. Em vez disso, você precisará executar uma CASToperação de várias etapas para alterar o implícito intpara um tipo de dados que pode ser convertido corretamente em - o uniqueidentiferque significa converter primeiro para varchardepois uniqueidentifier:

--Just a SELECT from nothing
SELECT CAST(CAST(CAST(NULL as int) as varchar) as uniqueidentifier);
--
--or to see it from a physical table:
SELECT NULL as NullColumn INTO SomeTable;
SELECT CAST(CAST(NullColumn as varchar(32)) as uniqueidentifier) FROM SomeTable;
DROP TABLE SomeTable;
AMtwo
fonte
Esse problema não ocorre devido a conversões de tipo de dados, pelo menos não no SQL Server. Os detalhes estão na minha resposta , mas a conversão está sendo feita no driver OLEDB, não no SQL Server, e as regras de conversão não são as mesmas.
Solomon Rutzky
1

O OP pode finalmente decidir se essa é uma resposta apropriada.

Não tenho provas 'absolutas', mas desconfio que o problema decorra do fato de um UniqueIdentifer ser dependente do servidor e talvez o provedor esteja tendo dificuldade em descobrir em qual servidor (local ou remoto) obterá esse identificador exclusivo, mesmo que seja nulo. É por isso que você provavelmente pode converter qualquer outro tipo de dados com êxito nesse cenário, mas não o identificador exclusivo. Os tipos de dados dependentes do 'servidor', como UNIQUEIDENTIFIERS e DATETIMEOFFSET, fornecerão o erro que você está encontrando.

Usar OPENQUERY em vez de nome de 4 partes funciona.

set nocount on  
DECLARE @cmd nVARCHAR(max)
DECLARE @datatype SYSNAME

DECLARE _CURSOR CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR
SELECT NAME
FROM sys.types 

OPEN _CURSOR

FETCH NEXT
FROM _CURSOR
INTO @datatype

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        SET @cmd = 'select top 1 cast(null as ' + @Datatype + ') as CastedData from remoteserver.remotedatabase.remoteschema.remotetable'
        PRINT @cmd
        EXECUTE sp_executesql @cmd
    END TRY

    BEGIN CATCH
        PRINT Error_message()
    END CATCH

FETCH NEXT
FROM _CURSOR
INTO @datatype
END --End While

CLOSE _CURSOR

DEALLOCATE _CURSOR
Scott Hodgin
fonte
O que exatamente você quer dizer com Uniqueidentifiere DateTimeOffsetser servidor dependente? Você tem alguma fonte para isso?
krystah
@krystah - A partir deste link ( technet.microsoft.com/en-us/library/ms190215(v=sql.105).aspx ) "O tipo de dados uniqueidentifier armazena valores binários de 16 bytes que operam como identificadores globalmente exclusivos (GUIDs) Um GUID é um número binário exclusivo; nenhum outro computador no mundo irá gerar uma duplicata desse valor GUID. O principal uso de um GUID é atribuir um identificador que deve ser exclusivo em uma rede que possui muitos computadores em vários sites. "
21716 Scott Hodgin
For DateTimeOffSet ( msdn.microsoft.com/en-us/library/bb630289.aspx ) Define uma data combinada com uma hora do dia com reconhecimento de fuso horário e com base em um relógio de 24 horas
Scott Hodgin
Estou 'deduzindo' que, como esses dois tipos de dados 'parecem' os únicos que apresentam o erro no seu cenário e esses tipos de dados são 'dependentes do servidor', essa é a razão do seu problema. Se você usar OPENQUERY para recuperar seus resultados a partir de seu ponto de vista e adicionar os moldes nulos adicionais, ele deve funcionar porque o provedor sabe 'onde' para obter essa informação
Scott Hodgin
@ krystah e Scott: esses dois tipos de dados não dependem do servidor, pelo menos não na maneira como você está descrevendo e implicando. Os GUIDs dependem da "arquitetura" em termos de sua representação binária subjacente (consulte aqui para obter detalhes), mas se esse fosse um problema aqui, nenhum GUID seria transferido corretamente. Para DATETIMEOFFSET, que conhece apenas um deslocamento, não um fuso horário / TimeZoneID real. Embora ambos usem as informações do sistema para gerar novos valores, nenhum novo valor está sendo gerado aqui e, se fossem, não seriam gerados no provedor.
Solomon Rutzky 27/08/16
0

Solução alternativa: a resposta aceita parece indicar que a conversão precisa ocorrer localmente porque o driver OLEDB não oferece suporte.

Portanto, acho que uma solução simples (pelo menos no caso da minha consulta que está selecionando um nulo uniqueidentifierno caso base de um CTE recursivo) é declarar uma variável nula:

declare @nullGuid as uniqueidentifier = null;

--Instead of...
CAST(NULL AS UniqueIdentifier) AS [GUID]

--use
@nullGuid AS [GUID]
xr280xr
fonte