Perché questo cast esplicito causa problemi solo con un server collegato?


21

Sto interrogando i dati da un server collegato attraverso una vista sul server di origine. La vista deve includere un paio di colonne standardizzate, come Created, Modifiede Deleted, ma in questo caso la tabella sul server di origine non ha alcuna informazione adatta. Le colonne vengono quindi espressamente espresse nei rispettivi tipi. Ho aggiornato la vista, cambiando una colonna da

NULL AS Modified

a

CAST(NULL as DateTime) as Modified

Tuttavia, dopo aver eseguito questo aggiornamento, la vista attiva il seguente messaggio di errore:

Messaggio 7341, livello 16, stato 2, riga 3 Impossibile ottenere il valore di riga corrente della colonna "(espressione generata dall'utente). Espr1002" dal provider OLE DB "SQLNCLI11" per il server collegato "".

Abbiamo fatto questo scambio "esplicito cast" in generale attraverso il server di origine senza preoccupazioni, e sospetto che il problema potrebbe essere correlato alla versione dei server coinvolti. Non abbiamo davvero bisogno di applicare questo cast, ma sembra più pulito. In questo momento sono solo curioso di sapere perché questo sta accadendo.

Versione server (origine):

Microsoft SQL Server 2012 - 11.0.5058.0 (X64) 14 maggio 2014 18:34:29 Copyright (c) Microsoft Corporation Enterprise Edition (64 bit) su Windows NT 6.1 (Build 7601: Service Pack 1) (Hypervisor)

Versione server (collegata):

Microsoft SQL Server 2008 R2 (SP1) - 10.50.2500.0 (X64) 17 giu 2011 00:54:03 Copyright (c) Microsoft Corporation Enterprise Edition (64-bit) su Windows NT 6.1 (Build 7601: Service Pack 1) (Hypervisor )

Modifica
Mi sono appena reso conto di aver fatto un errore non pubblicando tutte le colonne in questione e devo scusarmi per aver lasciato fuori un dettaglio importante. Non so come non me ne sia accorto prima. La domanda rimane ancora, comunque.

Il cast errato non si verifica con il cast su DateTime, ma con una colonna che viene trasmessa su UniqueIdentifier.

Questo è il colpevole:

CAST(NULL AS UniqueIdentifier) AS [GUID]

UniqueIdentifiers è supportato su SQL Server 2008 R2 e, come indicato nei commenti, la query eseguita dalla vista funziona correttamente sul server collegato.


Hai un'impostazione ANSI NULL diversa su ciascun server? Regole di confronto diverse?
Randolph West,

Entrambi i server hanno ANSI NULL = 0. Il server di origine ha le regole di confronto Danish_Norwegian_CI_ASe il server collegato ha le regole di confronto SQL_Danish_Pref_CP1_CI_AS, ma la COLLATEclausola non può essere applicata alle DateTimecolonne, quindi non ho ottenuto molto di più!
Krystah,

Questo fallirebbe se avesse una select Null from ...query WITH o nidificata e CASTun'altra?
Stoleg,

Senza il cast esplicito, verrà trattato come se INTcosì fosse stato modificato il tipo di dati. Non so perché ciò ti darebbe quel messaggio di errore.
Martin Smith,

In precedenza avevo provato a racchiudere i valori selezionati con i valori effettivi in ​​un CTE, quindi selezionarli e virare sui NULL lanciati nell'istruzione seguendo il CTE senza fortuna. Ho provato il tuo suggerimento, mantenendo i NULL nel CTE e lanciandoli nell'istruzione che interroga il CTE, ma produce anche lo stesso errore.
Krystah,

Risposte:


13

Quindi, sono stato in grado di riprodurre l'errore dopo aver realizzato che CASTera 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 CASTfunzionare 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 NULLnon è possibile convertirla in s UNIQUEIDENTIFIER, o forse NULLviene 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 INTa 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 INTin 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
    • tutti
  • TSQL
    • tutti tranne :
      • SQL: StmtRecompile
      • Tipo statico XQuery

Filtri a colonna:

  • Nome dell'applicazione
    • NON COME % Intellisense%
  • SPID
    • Maggiore o uguale a 50

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

Se si guarda al Test n. 3, sta eseguendo una operazione SELECT TOP (2) NULLsul 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 INTa 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 1sul 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 INTa 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 DATEquery 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:

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

PPS

Ho anche testato OPENQUERYcon 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 NULLcostante, come nel Test n. 1.


Ho ricreato questo problema nel 2012 sp3-cu1.
Bob Klimes,

@BobKlimes Interessante. L'uso di un server collegato a un'istanza o loop-back di 2008 R2?
Solomon Rutzky,

il server remoto collegato è 2008 R2 sp2 + MS15-058 (10.50.4339.0).
Bob Klimes,

1
non sembra essere correlato alla versione. Ho testato più combinazioni di 2008r2.2012.2014,2016 e finora hanno prodotto tutti errori, anche 2008r2-2008r2.
Bob Klimes,

2
Che problema estremamente oscuro. Grazie mille per la comprensione e la ricerca, @srutzky, è molto apprezzato. Terrò a mente la decrepazione di SNAC per il lavoro futuro e il ritorno alla soluzione sopra menzionata. Ottimo lavoro!
Krystah,

4

C'è solo una brutta soluzione: usa una costante di data come '1900-01-01'invece di null.

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

Dopo l'importazione è possibile aggiornare le colonne con 1900-01-01ritorno a Null.

Questo è un tipo di funzionalità / bug di SQL 2012 come qui .

Modifica: sostituito 1900-00-00con data valida 1900-01-01secondo il commento @a_horse_with_no_name di seguito.


Probabilmente vale la pena menzionare questa soluzione alternativa, ma potrebbe non essere più rilevante ora che il PO ha chiarito che il colpevole del problema è una uniqueidentifiercolonna. O forse potrebbe essere adattato - qualcosa del genere CAST('00000000-0000-0000-0000-000000000000' AS UniqueIdentifier) AS [GUID], forse?
Andriy M,

Grazie ragazzi. Il cast su un GUID vuoto o DateTime funziona, ma devo capire perché questo sta accadendo. Vale anche la pena ricordare che non sto importando nulla, quindi non è possibile modificare i dati di origine.
Krystah,

1
1900-00-00è una data non valida e non sarà accettata.
a_horse_with_no_name

@a_horse_with_no_name: stupido errore, corretto.
Anton Krouglov,

2

Il problema è legato alle conversioni del tipo di dati (come colpito nei commenti).

Considera quanto segue:

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

Si noti che NullColumnè di tipo int. A SQL Server non piace convertire i intvalori in uniqueidentifier. Questa SELECTaffermazione fallirà in una conversione del tipo di dati:

--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;

Messaggio 529, livello 16, stato 2, riga 3

La conversione esplicita dal tipo di dati int a uniqueidentifier non è consentita.

Mentre questo valore specifico (NULL) è in grado di essere trasmesso a un GUID, SQL Server genera l'errore in base alla conversione del tipo di dati, prima ancora di esaminare i valori specifici. Invece, dovrai fare un'operazione in più passaggi CASTper cambiare l'implicito intin un tipo di dati che può essere convertito in modo pulito in uniqueidentifer- il che significa che prima devi lanciare per varcharpoi 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;

Questo problema non è in realtà dovuto alle conversioni del tipo di dati, almeno non in SQL Server. I dettagli sono nella mia risposta , ma la conversione viene eseguita nel driver OLEDB, non in SQL Server, e le regole di conversione non sono le stesse.
Solomon Rutzky,

1

Alla fine l'OP può decidere se questa è una risposta appropriata.

Non ho prove "assolute", ma "sospetto" che il problema derivi dal fatto che un UniqueIdentifer dipende dal server e forse il provider ha difficoltà a capire da quale server (locale o remoto) ottenere questo identificatore univoco, anche se è nullo. Ecco perché probabilmente puoi lanciare con successo qualsiasi altro tipo di dati in questo scenario, ma non univocoidentificatore. I tipi di dati che dipendono dal "server" come UNIQUEIDENTIFIERS e DATETIMEOFFSET ti daranno l'errore che stai riscontrando.

L'uso di OPENQUERY invece del nome in 4 parti funziona.

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

Che cosa esattamente cosa si intende per Uniqueidentifiere DateTimeOffsetessendo di server-dipendente? Hai qualche fonte per questo?
Krystah,

@krystah - Da questo link ( technet.microsoft.com/en-us/library/ms190215(v=sql.105).aspx ) "Il tipo di dati uniqueidentifier memorizza valori binari a 16 byte che funzionano come identificatori univoci globali (GUID) Un GUID è un numero binario univoco, nessun altro computer al mondo genererà un duplicato di quel valore GUID. L'uso principale di un GUID è per assegnare un identificatore che deve essere univoco in una rete che ha molti computer in molti siti. "
Scott Hodgin,

Per DateTimeOffSet ( msdn.microsoft.com/en-us/library/bb630289.aspx ) Definisce una data combinata con un'ora di un giorno che è a conoscenza del fuso orario e si basa su un orologio di 24 ore
Scott Hodgin

Sto "inferendo" che, poiché questi due tipi di dati "sembrano" essere i soli a dare l'errore nel tuo scenario e quei tipi di dati sono "dipendenti dal server", questa è la ragione del tuo problema. Se si utilizza OPENQUERY per recuperare i risultati dalla vista e aggiungere ulteriori cast null, dovrebbe funzionare perché il provider sa "dove" ottenere tali informazioni
Scott Hodgin,

@krystah e Scott: questi due tipi di dati non dipendono dal server, almeno non dal modo in cui stai descrivendo e implicando. I GUID dipendono dall'architettura in termini di rappresentazione binaria sottostante (vedere qui per i dettagli), ma se questo fosse un problema qui, nessun GUID verrebbe trasferito correttamente. Per DATETIMEOFFSET, che conosce solo un offset, non un fuso orario / fuso orario effettivo. Mentre entrambi utilizzano le informazioni di sistema per generare nuovi valori, qui non vengono generati nuovi valori e, se lo fossero, non verrebbero generati nel provider.
Solomon Rutzky,

0

Soluzione alternativa: la risposta accettata sembra indicare che la conversione deve avvenire localmente perché il driver OLEDB non la supporta.

Quindi penso che una semplice soluzione alternativa (almeno nel caso della mia query che sta selezionando un null uniqueidentifiernel caso base di un CTE ricorsivo) sia dichiarare una variabile null:

declare @nullGuid as uniqueidentifier = null;

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

--use
@nullGuid AS [GUID]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.