web-dev-qa-db-ja.com

この明示的なキャストがリンクサーバーでのみ問題を引き起こすのはなぜですか?

オリジンサーバーのビューを介してリンクサーバーからデータをクエリしています。ビューには、CreatedModifiedDeletedなどのいくつかの標準化された列を含める必要がありますが、この場合、ソースサーバーのテーブルには適切な列がありません。情報。したがって、列はそれぞれの型に明示的にキャストされます。列を変更してビューを更新しました

NULL AS Modified

CAST(NULL as DateTime) as Modified

ただし、この更新を実行した後、ビューは次のエラーメッセージをトリガーします。

メッセージ7341、レベル16、状態2、行3列=(ユーザーが生成した式)の現在の行の値を取得できませんOLEリンクサーバー ""のDBプロバイダー "SQLNCLI11"。

この「明示的なキャスト」の変更は、通常、Originサーバー全体で心配なく行われました。問題は、関連するサーバーのバージョンに関連しているのではないかと思います。実際にこのキャストを適用する必要はありませんが、よりクリーンに感じられます。今、私はなぜこれが起こっているのか知りたいだけです。

サーバーのバージョン(元):

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

サーバーのバージョン(リンク):

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

編集
問題の列をすべて投稿しないことで間違いを犯したことに気づきました。重要な詳細を省いたことをお詫びします。これに気づかなかった方法がすぐにわかりません。問題はまだ残っています。

誤ったキャストは、DateTimeへのキャストでは発生せず、UniqueIdentifierにキャストされる列で発生します。

これが原因です:

CAST(NULL AS UniqueIdentifier) AS [GUID]

UniqueIdentifiersはSQL Server 2008 R2でサポートされており、コメントに記載されているように、ビューによって実行されるクエリはリンクサーバーで正常に実行されます。

21
krystah

したがって、CASTがリモートインスタンスではなくローカルで実行されていることを認識した後、エラーを再現できました。これを修正することを期待して、以前はSP3に移行することを推奨していました(一部はSP3でエラーを再現できないため、一部はそれが適切なアイデアであるためです)。ただし、これでエラーを再現できるようになったので、SP3に移行しても、おそらく良いアイデアではありますが、これを修正できないことは明らかです。また、SQL Server 2008 R2でエラーを再現しましたRTMおよび2014 SP1(3つのケースすべてで「ループバック」ローカルリンクサーバーを使用))。

この問題は、クエリが実行されているwhere、または少なくともpart(s)がどこにあるかに関係しているようです実行。これは、CAST操作を機能させることができたためですが、ローカルDBオブジェクトへの参照を含めることによってのみ可能です。

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

それは実際に機能します。ただし、以下は元のエラーを取得します。

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

ローカル参照がない場合、クエリ全体がリモートシステムに送信されて実行され、何らかの理由でNULLsをUNIQUEIDENTIFIERに変換できないか、おそらくNULLがOLE DBドライバが正しくありません。


私が行ったテストに基づくと、これはバグのように見えますが、バグがSQL Server内にあるのか、SQL Server Native Client/OLEDBドライバー内にあるのかはわかりません。ただし、変換エラーはOLEDBドライバー内で発生するため、ドライバーはSQL Serverを使用して変換を行っていないため(SQL ServerではSQL Serverでも使用できない変換)、INTからUNIQUEIDENTIFIERへの変換の問題であるとは限りません(SQL ServerもINTDATEに変換することはできませんが、OLEDBドライバは、テストの1つに示されているように、それを正常に処理します。

私は3つのテストを実行しました。成功した2つについては、リモートで実行されているクエリを示すXML実行プランを調べました。 3つすべてについて、SQLプロファイラーを介して例外またはOLEDBイベントをキャプチャしました。

イベント:

  • エラーと警告
    • 注意
    • 例外
    • 実行警告
    • ユーザーエラーメッセージ
  • OLEDB
    • すべて
  • TSQL
    • すべてexcept
      • SQL:StmtRecompile
      • XQuery静的タイプ

列フィルター:

  • アプリケーション名
    • NOT LIKE%Intellisense%
  • SPID
    • 以上50

テスト

  • テスト1

    • 機能するCAST(NULL AS UNIQUEIDENTIFIER)
    _SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
                 , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    _

    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;"
     />
    _
  • テスト2

    • 失敗したCAST(NULL AS UNIQUEIDENTIFIER)
    _SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    _

    (注:私はサブクエリをそこに保持し、コメント化したので、XMLトレースファイルを比較したときの差が1つ少なくなります)

  • テスト3

    • 機能するCAST(NULL AS DATE)
    _SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    _

    (注:私はサブクエリをそこに保持し、コメント化したので、XMLトレースファイルを比較したときの差が1つ少なくなります)

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

テスト#3を見ると、「リモート」システムでSELECT TOP (2) NULLを実行しています。 SQLプロファイラートレースは、このリモートフィールドのデータ型が実際にINTであることを示しています。トレースは、クライアント側のフィールド(つまり、クエリを実行している場所)がDATEであることも示しています。 SQL Serverでエラーが発生するINTからDATEへの変換は、OLEDBドライバ内で正常に機能します。リモート値はNULLであるため、直接返されるため、_<ColumnReference Column="Expr1002" />_になります。

テスト#1を見ると、「リモート」システムで_SELECT 1_を実行しています。 SQLプロファイラートレースは、このリモートフィールドのデータ型が実際にINTであることを示しています。トレースは、クライアント側のフィールド(つまり、クエリを実行している場所)がGUIDであることも示しています。 INTからGUIDへの変換(これはドライバー内で行われ、OLEDBはこれを「GUID」と呼びます)は、SQL Serverでエラーが発生するもので、OLEDBドライバー内で正常に機能します。リモート値はnotNULLであるため、リテラルNULLに置き換えられるため、_<Const ConstValue="NULL" />_になります。

テスト#2が失敗したため、実行計画はありません。ただし、「リモート」システムのクエリは成功しますが、結果セットを返すことができません。 SQLプロファイラがキャプチャしたクエリは次のとおりです。

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

これは、テスト#1で実行されているのとまったく同じクエリですが、ここでは失敗しています。他にも小さな違いはありますが、OLEDBの通信を完全に解釈することはできません。ただし、リモートフィールドは引き続きINT(wType = 3 = adInteger/4バイトの符号付き整数/ DBTYPE_I4)として表示されますが、「クライアント」フィールドは引き続きGUID(wType = 72 = adGUID /グローバル一意識別子/ DBTYPE_GUID)として表示されます。 OLE DBドキュメントは GUIDデータ型変換DBDATEデータ型変換 、および I4 Dataタイプ変換 は、I4からいずれかの[〜#〜] guid [〜#〜]への変換を示しますまたは[〜#〜] dbdate [〜#〜]はサポートされていませんが、DATEクエリは機能します。

3つのテストのトレースXMLファイルはPastebinにあります。各テストが他のテストと異なる場所の詳細を確認する場合は、それらをローカルに保存してから、「差分」を実行できます。ファイルは次のとおりです。

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

ERGO?

それについて何をしますか? SQL Native Client-_SQLNCLI11_-がSQL Server 2012で非推奨になっていることを考えると、おそらく、上のセクションで説明した回避策だけです。SQLServer Native Clientのトピックに関するほとんどのMSDNページには、上部の次の通知:

警告

SQL Server Native Client(SNAC)はSQL Server 2012以降ではサポートされていません。新しい開発作業ではSNACの使用を避け、現在それを使用しているアプリケーションの変更を計画してください。 Microsoft ODBC Driver for SQL Server は、WindowsからMicrosoft SQL ServerおよびMicrosoft Azure SQLデータベースへのネイティブ接続を提供します。

詳細については、以下を参照してください。


ODBC ??

ODBCリンクサーバーを次のように設定しました:

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

そして、試してみました:

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

次のエラーを受け取りました:

リンクサーバー "LocalODBC"のOLE DBプロバイダー "MSDASQL"がメッセージ "要求された変換はサポートされていません。"を返しました。
メッセージ7341、レベル16、状態2、行53
リンクサーバー "LocalODBC"のOLE DBプロバイダー "MSDASQL"から、列 "(ユーザー生成式).Expr1002"の現在の行の値を取得できません。


P.S。

リモートサーバーとローカルサーバー間のGUIDの転送に関連するため、NULL以外の値は特別な構文を介して処理されます。 CAST(0x00 AS UNIQUEIDENTIFIER)を実行すると、SQLプロファイラトレースに次のOLE DBイベント情報が表示されます。

_<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。

また、次のクエリでOPENQUERYを使用してテストしました。

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

ローカルオブジェクト参照がなくても成功しました。 SQLプロファイラのトレースXMLファイルは、Pastebinの次の場所に投稿されています。

NullGuidSuccessOPENQUERY.xml

XML実行プランは、テスト#1と同じように、NULL定数を使用してそれを示しています。

13
Solomon Rutzky

醜い回避策しかありません-nullの代わりに'1900-01-01'のような日付定数を使用してください。

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

インポート後、1900-01-01で列を更新してNullに戻すことができます。

これは here によるSQL 2012の機能/バグの一種です。

編集:以下の@a_horse_with_no_nameコメントに従って、1900-00-00を有効な日付1900-01-01に置き換えます。

4
Anton Krouglov

この問題は、データ型変換に関連しています(コメントでヒット)。

以下を検討してください。

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

NullColumnのタイプはintであることに注意してください。 SQL Serverは、int値をuniqueidentifierに変換することを好みません。このSELECTステートメントは、データ型変換で失敗します。

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

メッセージ529、レベル16、状態2、行3

データ型intからuniqueidentifierへの明示的な変換は許可されていません。

この特定の値(NULL)はGUIDにキャストできますが、SQL Serverは特定の値を調べる前に、データ型変換に基づいてエラーをスローします。代わりに、マルチステップCAST操作を実行して、暗黙のintuniqueidentiferにきれいに変換できるデータ型に変更する必要があります。 varcharに、次に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

OPは、これが適切な答えであるかどうかを最終的に決定できます。

私には「絶対的な」証明はありませんが、UniqueIdentiferはサーバーに依存しており、プロバイダーがこのuniqueidentifierを取得するサーバー(ローカルまたはリモート)を特定するのが困難であるという事実に問題があることが「疑い」ですヌル。このため、このシナリオではおそらく他のデータ型を正常にキャストできますが、uniqueidentifierはキャストできません。 UNIQUEIDENTIFIERSやDATETIMEOFFSETのように「サーバー」に依存するデータ型を使用すると、発生しているエラーが発生します。

4部構成の名前の代わりにOPENQUERYを使用しても機能します。

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

回避策: 受け入れられた回答 は、OLEDBドライバーがサポートしていないため、ローカルで変換を行う必要があることを示しているようです。

したがって、簡単な回避策(少なくとも、再帰CTEの基本ケースでnull uniqueidentifierを選択しているクエリの場合)はnull変数を宣言することです:

declare @nullGuid as uniqueidentifier = null;

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

--use
@nullGuid AS [GUID]
0
xr280xr