it-swarm.com.de

Warum verursacht diese explizite Besetzung nur Probleme mit einem Verbindungsserver?

Ich frage Daten von einem Verbindungsserver über eine Ansicht auf dem Origin-Server ab. Die Ansicht muss einige standardisierte Spalten enthalten, z. B. Created, Modified und Deleted. In diesem Fall ist die Tabelle auf dem Quellserver jedoch nicht geeignet die Info. Die Spalten werden daher explizit in ihre jeweiligen Typen umgewandelt. Ich habe die Ansicht aktualisiert und eine Spalte von geändert

NULL AS Modified

zu

CAST(NULL as DateTime) as Modified

Nach der Durchführung dieses Updates löst die Ansicht jedoch die folgende Fehlermeldung aus:

Nachricht 7341, Ebene 16, Status 2, Zeile 3 Der aktuelle Zeilenwert der Spalte "(vom Benutzer generierter Ausdruck) .Expr1002" kann nicht von OLE DB-Anbieter "SQLNCLI11" für Verbindungsserver "" abgerufen werden.

Wir haben diese "explizite Besetzung" im Allgemeinen ohne Bedenken auf dem Origin-Server durchgeführt, und ich vermute, dass das Problem mit der Version der beteiligten Server zusammenhängt. Wir brauchen diese Besetzung nicht wirklich brauchen, aber sie fühlt sich sauberer an. Im Moment bin ich nur neugierig, warum das passiert.

Serverversion (Herkunft):

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

Serverversion (verlinkt):

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

Bearbeiten
Ich habe gerade festgestellt, dass ich einen Fehler gemacht habe, indem ich nicht alle fraglichen Spalten veröffentlicht habe, und ich muss mich dafür entschuldigen, dass ich ein wichtiges Detail ausgelassen habe. Ich weiß nicht, wie ich das nicht früher bemerkt habe. Die Frage bleibt jedoch weiterhin.

Die fehlerhafte Umwandlung erfolgt nicht mit der Umwandlung in DateTime, sondern mit einer Spalte, die in UniqueIdentifier umgewandelt wird.

Dies ist der Schuldige:

CAST(NULL AS UniqueIdentifier) AS [GUID]

UniqueIdentifiers werden unter SQL Server 2008 R2 unterstützt. Wie in den Kommentaren erwähnt, wird die von der Ansicht ausgeführte Abfrage auf dem Verbindungsserver ordnungsgemäß ausgeführt.

21
krystah

So konnte ich den Fehler reproduzieren, nachdem ich festgestellt hatte, dass CAST lokal und nicht auf der Remote-Instanz ausgeführt wurde. Ich hatte zuvor empfohlen, auf SP3 umzusteigen, um dies zu beheben (teilweise, weil der Fehler auf SP3 nicht reproduziert werden konnte, und teilweise, weil es trotzdem eine gute Idee war). Jetzt, da ich den Fehler reproduzieren kann, ist klar, dass ein Wechsel zu SP3, obwohl dies wahrscheinlich immer noch eine gute Idee ist, dies nicht beheben wird. Außerdem habe ich den Fehler in SQL Server 2008 R2 RTM und 2014 SP1 (in allen drei Fällen mit einem lokalen "Loopback" -Verbindungsserver) reproduziert.

Es scheint, dass dieses Problem damit zu tun hat, dass wo die Abfrage ausgeführt wird oder zumindest, wo Teil (e) davon ausgeführt werden. Ich sage dies, weil ich die Operation CAST zum Laufen bringen konnte, aber nur durch Einfügen eines Verweises auf ein lokales DB-Objekt:

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

Das funktioniert tatsächlich. Aber das Folgende erhält den ursprünglichen Fehler:

SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (VALUES (1)) tmp(dummy);

Ich vermute, wenn keine lokalen Referenzen vorhanden sind, wird die gesamte Abfrage zur Ausführung an das Remote-System gesendet, und aus irgendeinem Grund können NULL nicht in UNIQUEIDENTIFIER konvertiert werden, oder vielleicht in die NULL wird vom OLE DB-Treiber falsch übersetzt.


Basierend auf den von mir durchgeführten Tests scheint dies ein Fehler zu sein, aber ich bin nicht sicher, ob der Fehler in SQL Server oder im SQL Server Native Client/OLEDB-Treiber liegt. Der Konvertierungsfehler tritt jedoch im OLEDB-Treiber auf und ist daher nicht unbedingt ein Problem beim Konvertieren von INT nach UNIQUEIDENTIFIER (eine Konvertierung, die in SQL Server nicht zulässig ist), da dies beim Treiber nicht der Fall ist Verwenden von SQL Server für Konvertierungen (SQL Server ermöglicht auch keine Konvertierung von INT in DATE, der OLEDB-Treiber verarbeitet dies jedoch erfolgreich, wie in einem der Tests gezeigt).

Ich habe drei Tests durchgeführt. Für die beiden, die erfolgreich waren, habe ich mir die XML-Ausführungspläne angesehen, die die Abfrage zeigen, die remote ausgeführt wird. Für alle drei habe ich alle Ausnahmen oder OLEDB-Ereignisse über SQL Profiler erfasst:

Veranstaltungen:

  • Fehler und Warnungen
    • Achtung
    • Ausnahme
    • Ausführungswarnungen
    • Benutzerfehlermeldung
  • OLEDB
    • alle
  • TSQL
    • all außer:
      • SQL: StmtRecompile
      • Statischer XQuery-Typ

Spaltenfilter:

  • Anwendungsname
    • NICHT WIE % Intellisense%
  • SPID
    • Größer oder gleich 50

DIE TESTS

  • Test 1

    • CAST(NULL AS UNIQUEIDENTIFIER) das funktioniert
    SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
                 , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    

    Relevanter Teil des XML-Ausführungsplans:

              <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) das schlägt fehl
    SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    

    (Hinweis: Ich habe die Unterabfrage dort beibehalten und auskommentiert, damit es beim Vergleich der XML-Tracedateien einen Unterschied weniger gibt.)

  • Test 3

    • CAST(NULL AS DATE) das funktioniert
    SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    

    (Hinweis: Ich habe die Unterabfrage dort beibehalten und auskommentiert, damit es beim Vergleich der XML-Tracedateien einen Unterschied weniger gibt.)

    Relevanter Teil des XML-Ausführungsplans:

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

Wenn Sie sich Test 3 ansehen, führt er eine SELECT TOP (2) NULL auf dem "entfernten" System aus. Der SQL Profiler-Trace zeigt, dass der Datentyp dieses Remote-Felds tatsächlich INT ist. Die Ablaufverfolgung zeigt auch, dass das Feld auf der Clientseite (d. H. Wo ich die Abfrage ausführe) erwartungsgemäß DATE ist. Die Konvertierung von INT nach DATE, bei der in SQL Server ein Fehler auftritt, funktioniert im OLEDB-Treiber einwandfrei. Der Remote-Wert ist NULL, daher wird er direkt zurückgegeben, daher <ColumnReference Column="Expr1002" />.

Wenn Sie sich Test 1 ansehen, wird auf dem "Remote" -System ein SELECT 1 Ausgeführt. Der SQL Profiler-Trace zeigt, dass der Datentyp dieses Remote-Felds tatsächlich INT ist. Die Ablaufverfolgung zeigt auch, dass das Feld auf der Clientseite (d. H. Wo ich die Abfrage ausführe) erwartungsgemäß GUID ist. Die Konvertierung von INT nach GUID (denken Sie daran, dies erfolgt innerhalb des Treibers und OLEDB nennt es "GUID"), was in SQL Server einen Fehler verursacht, funktioniert innerhalb des OLEDB-Treiber. Der entfernte Wert ist nichtNULL, daher wird er durch ein Literal NULL ersetzt, daher der <Const ConstValue="NULL" />.

Test Nr. 2 schlägt fehl, daher gibt es keinen Ausführungsplan. Das "entfernte" System wird jedoch erfolgreich abgefragt, die Ergebnismenge kann jedoch nicht zurückgegeben werden. Die von SQL Profiler erfasste Abfrage lautet:

SELECT TOP (2) NULL "Expr1002" FROM "TEMPTEST"."sys"."objects" "Tbl1001"

Das ist genau die gleiche Abfrage, die in Test 1 durchgeführt wird, aber hier schlägt sie fehl. Es gibt noch andere kleine Unterschiede, aber ich kann die OLEDB-Kommunikation nicht vollständig interpretieren. Das Remote-Feld wird jedoch weiterhin als INT (wType = 3 = adInteger/4-Byte-Ganzzahl mit Vorzeichen/DBTYPE_I4) angezeigt, während das Feld "client" weiterhin als GUID (wType = 72) angezeigt wird = adGUID/global eindeutiger Bezeichner/DBTYPE_GUID). Die OLE DB-Dokumentation hilft nicht viel wie GUID-Datentypkonvertierungen , DBDATE-Datentypkonvertierungen und I4-Daten Typkonvertierungen zeigen, dass die Konvertierung von I4 nach entweder [~ # ~] guid [~ # ~] oder [~ # ~] dbdate [~ # ~] wird nicht unterstützt, dennoch wird DATE Abfrage funktioniert.

Die Trace-XML-Dateien für die drei Tests befinden sich in Pastebin. Wenn Sie die Details sehen möchten, bei denen sich jeder Test von den anderen unterscheidet, können Sie sie lokal speichern und dann einen "Unterschied" für sie machen. Die Dateien sind:

  1. NullGuidSuccess.xml
  2. NullGuidError.xml
  3. NullDateSuccess.xml

ERGO?

Was tun? Wahrscheinlich nur die Problemumgehung, die ich im oberen Abschnitt notiert habe, da der SQL Native Client - SQLNCLI11 - ab SQL Server 2012 veraltet ist. Die meisten MSDN-Seiten zum Thema SQL Server Native Client haben den folgenden Hinweis oben:

Warnung

SQL Server Native Client (SNAC) wird über SQL Server 2012 hinaus nicht unterstützt. Vermeiden Sie die Verwendung von SNAC in neuen Entwicklungsarbeiten und planen Sie, Anwendungen zu ändern, die es derzeit verwenden. Der Microsoft ODBC Treiber für SQL Server bietet native Konnektivität von Windows zu Microsoft SQL Server und Microsoft Azure SQL-Datenbank.

Weitere Informationen finden Sie unter:


ODBC ??

Ich habe einen ODBC Linked Server) eingerichtet über:

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;

Und dann versucht:

SELECT CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
FROM [LocalODBC].[tempdb].[sys].[objects] rmt;

und erhielt den folgenden Fehler:

Der OLE DB-Anbieter "MSDASQL" für den Verbindungsserver "LocalODBC" hat die Meldung "Angeforderte Konvertierung wird nicht unterstützt." Zurückgegeben.
Nachricht 7341, Ebene 16, Zustand 2, Zeile 53
Der aktuelle Zeilenwert der Spalte "(vom Benutzer generierter Ausdruck) .Expr1002" kann nicht von OLE DB-Anbieter "MSDASQL" für den Verbindungsserver "LocalODBC" abgerufen werden.


P.S.

Beim Transport von GUIDs zwischen Remote- und lokalen Servern werden Nicht-NULL-Werte über eine spezielle Syntax behandelt. Ich habe Folgendes festgestellt: OLE DB-Ereignisinformationen im SQL Profiler-Trace, als ich CAST(0x00 AS UNIQUEIDENTIFIER) ausgeführt habe:

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

P.P.S.

Ich habe auch über OPENQUERY mit der folgenden Abfrage getestet:

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;

und es gelang auch ohne die lokale Objektreferenz. Die SQL Profiler-Trace-XML-Datei wurde an Pastebin gesendet unter:

NullGuidSuccessOPENQUERY.xml

Der XML-Ausführungsplan zeigt dies unter Verwendung einer NULL - Konstante an, wie in Test 1.

13
Solomon Rutzky

Es gibt nur eine hässliche Problemumgehung - verwenden Sie eine Datumskonstante wie '1900-01-01' Anstelle von null.

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

Nach dem Import können Sie Spalten mit 1900-01-01 Zurück auf Null aktualisieren.

Dies ist eine Art SQL 2012-Feature/Fehler gemäß hier .

Bearbeiten: ersetzt 1900-00-00 Durch ein gültiges Datum 1900-01-01 Gemäß dem Kommentar @a_horse_with_no_name unten.

4
Anton Krouglov

Das Problem hängt mit Datentypkonvertierungen zusammen (wie in den Kommentaren angegeben).

Folgendes berücksichtigen:

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

Beachten Sie, dass NullColumn vom Typ int ist. SQL Server konvertiert int -Werte nicht gerne in uniqueidentifier. Diese SELECT -Anweisung schlägt bei einer Datentypkonvertierung fehl:

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

Nachricht 529, Ebene 16, Status 2, Zeile 3

Eine explizite Konvertierung vom Datentyp int in einen eindeutigen Bezeichner ist nicht zulässig.

Während dieser bestimmte Wert (NULL) in eine GUID umgewandelt werden kann, löst SQL Server den Fehler basierend auf der Datentypkonvertierung aus, bevor die spezifischen Werte überhaupt betrachtet werden. Stattdessen müssen Sie eine mehrstufige CAST -Operation ausführen, um das implizite int in einen Datentyp zu ändern, der sauber in uniqueidentifer-- konvertiert werden kann zu varchar dann zu 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;
2
AMtwo

Das OP kann letztendlich entscheiden, ob dies eine angemessene Antwort ist.

Ich habe keinen "absoluten" Beweis, aber ich "vermute", dass das Problem auf der Tatsache beruht, dass ein UniqueIdentifer serverabhängig ist und der Anbieter möglicherweise Schwierigkeiten hat, herauszufinden, von welchem ​​Server (lokal oder remote) er diese eindeutige Kennung erhalten soll, obwohl dies der Fall ist Null. Aus diesem Grund können Sie in diesem Szenario wahrscheinlich jeden anderen Datentyp erfolgreich umwandeln, jedoch keine eindeutige Kennung. Datentypen, die vom Server abhängig sind, wie UNIQUEIDENTIFIERS und DATETIMEOFFSET, geben Ihnen den Fehler, auf den Sie stoßen.

Die Verwendung von OPENQUERY anstelle eines vierteiligen Namens funktioniert.

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
1
Scott Hodgin

Problemumgehung: Die akzeptierte Antwort scheint darauf hinzudeuten, dass die Konvertierung lokal erfolgen muss, da der OLEDB-Treiber dies nicht unterstützt.

Daher denke ich, dass eine einfache Problemumgehung (zumindest im Fall meiner Abfrage, bei der im Basisfall eines rekursiven CTE eine Null uniqueidentifier ausgewählt wird) darin besteht, eine Nullvariable zu deklarieren:

declare @nullGuid as uniqueidentifier = null;

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

--use
@nullGuid AS [GUID]
0
xr280xr