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


21

オリジンサーバーのビューを介してリンクサーバーからデータをクエリしています。ビューは、次のような標準化された列のカップルを含める必要がありCreatedModifiedかつDeleted、この場合には、ソース・サーバー上の表は、任意の適切な情報を持っていません。したがって、列はそれぞれの型に明示的にキャストされます。ビューを更新し、列を

NULL AS Modified

CAST(NULL as DateTime) as Modified

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

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

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

サーバーバージョン(オリジン):

Microsoft SQL Server 2012-11.0.5058.0(X64)2014年5月14日18:34:29 Copyright(c)Microsoft Corporation Enterprise Edition(64-bit)on Windows NT 6.1(Build 7601:Service Pack 1)(ハイパーバイザー)

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

Microsoft SQL Server 2008 R2(SP1)-10.50.2500.0(X64)2011年6月17日00:54:03 Copyright(c)Microsoft Corporation Enterprise Edition(64-bit)on Windows NT 6.1(Build 7601:Service Pack 1)(ハイパーバイザー)

編集
問題の列をすべて掲載しないことでミスを犯したことに気付いたので、重要な詳細を省いたことを謝罪する必要があります。これに気づかなかったのは早く分かりません。しかし、疑問はまだ残っています。

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

これが犯人です。

CAST(NULL AS UniqueIdentifier) AS [GUID]

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


サーバーごとに異なるANSI NULL設定がありますか?異なる照合?
ランドルフウェスト

両方のサーバーにANSI NULL = 0があります。オリジンサーバーには照合順序がDanish_Norwegian_CI_ASあり、リンクサーバーには照合順序がありますSQL_Danish_Pref_CP1_CI_ASが、このCOLLATE句はDateTime列に適用できないため、これ以上は取得しませんでした!
クリスタ

select Null from ...WITHまたはネストされたクエリとCAST別のクエリがある場合、これは失敗しますか?
ストーレグ

明示的なキャストを行わないとINT、データ型が変更されたものとして扱われます。しかし、なぜそのエラーメッセージが表示されるのかわかりません。
マーティンスミス

以前に、選択した値をCTEの実際の値でラップしてから、それらを選択し、CTEに続くステートメントでキャストされたNULLを運なくタックしようとしました。私はあなたの提案を試みました。CTEにNULLを保持し、CTEを照会するステートメントにNULLをキャストしましたが、同じエラーが発生しました。
クリスタ

回答:


13

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

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

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

ローカル参照がない場合、クエリ全体がリモートシステムに送られて実行され、何らかの理由NULLでに変換できないUNIQUEIDENTIFIERか、おそらくNULLOLE DBドライバーによって誤って変換されていると推測しています。


私が行ったテストに基づいて、これはバグのように見えますが、バグがSQL Serverにあるのか、SQL Server Native Client / OLEDBドライバーにあるのかはわかりません。しかし、変換誤差は、OLEDBドライバ内で発生などからの変換の問題は必ずしもないINTUNIQUEIDENTIFIER運転者が変換を行うために、SQL Serverを使用していないので(SQL Serverで許可されていない変換)(SQL Serverがありません変換を可能にINTするDATE試験のいずれかに示されるように)、まだ正常OLEDBドライバハンドル。

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

イベント:

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

列フィルター:

  • アプリケーション名
    • ありません%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予想どおりであることも示しています。からINTへの変換はDATE、SQL Serverでエラーが発生するもので、OLEDBドライバー内で正常に機能します。リモート値はですNULLので、直接返されるため、<ColumnReference Column="Expr1002" />です。

テスト#1を見るとSELECT 1、「リモート」システムで実行されています。SQLプロファイラトレースは、このリモートフィールドのデータ型が実際にあることを示していINTます。トレースは、クライアント側のフィールド(つまり、クエリを実行している場所)がGUID予想どおりであることも示しています。以下からの変換INTGUID(これはドライバ内で行われ、覚えている、とOLEDBは、「GUID」と呼んでいます)、SQL Serverでエラーが発生します何かが、OLEDBドライバ内だけで罰金に動作します。リモート値はそう NULLではないので、リテラル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データ型変換がI4からGUIDまたはDBDATEへの変換がサポートされていないことを示しているため、DATEクエリは機能するため、あまり役に立ちません。

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

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

エルゴ?

それについてどうすればいいですか?おそらく、SQL Native Client SQLNCLI11がSQL Server 2012で廃止されていることを考えると、一番上のセクションで述べた回避策にすぎません。SQLServer Native Client のトピックに関するMSDNページのほとんどは、上:

警告

SQL Server Native Client(SNAC)は、SQL Server 2012以降ではサポートされません。SNACを新しい開発作業で使用することは避け、現在SNACを使用しているアプリケーションを変更することを計画してください。SQL Server用のMicrosoft ODBCドライバは、 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"は、 "要求された変換はサポートされていません。"というメッセージを返しました。
Msg 7341、レベル16、状態2、行53
リンクサーバー "LocalODBC"のOLE DBプロバイダー "MSDASQL"から列 "(ユーザー生成式).Expr1002"の現在の行値を取得できません。


PS

リモートサーバーとローカルサーバー間のGUIDの転送に関連するため、非NULL値は特別な構文を介して処理されます。実行したときに、SQLプロファイラトレースで次のOLE DBイベント情報に気付きました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

また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実行計画ではNULL、テスト#1と同じ定数を使用してそれを示しています。


2012 sp3-cu1でこの問題を再現しました。
ボブクライムズ

@BobKlimesおもしろい。それは、2008 R2インスタンスへのリンクサーバーを使用していますか、それともループバックですか?
ソロモンラッツキー

リモートリンクサーバーは2008 R2 sp2 + MS15-058(10.50.4339.0)です。
ボブクライムズ

1
バージョン関連ではないようです。2008r2、2012、2014、2016の複数のコンボをテストしましたが、2008r2-2008r2でさえ、すべてこれまでにエラーが発生しました。
ボブクライムズ

2
なんと非常にあいまいな問題です。@srutzkyの洞察と研究に深く感謝します。今後の作業と前述の回避策へのフォールバックのために、SNACの廃止を念頭に置いておきます。すごい仕事!
クリスタ

4

唯一の醜い回避策はあります-のようないくつかの日付の定数を使用する'1900-01-01'代わりにnull

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

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

これは、ここにあるようなSQL 2012の機能/バグの一種です

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


この回避策はおそらく言及する価値がありましたが、問題の原因がuniqueidentifierコラムであることをOPが明確にしたので、もはや関係がないかもしれません。または、おそらくそれを適応させることができます-のようなものCAST('00000000-0000-0000-0000-000000000000' AS UniqueIdentifier) AS [GUID]でしょうか?
アンドリーM

みんなありがとう。空のGUIDまたはDateTimeへのキャストは機能しますが、なぜこれが起こっているのかを理解する必要があります。また、私は何もインポートしないので、ソースデータを変更する可能性はありません。
クリスタ

1
1900-00-00無効な日付であり、受け入れられません。
a_horse_with_no_name

@a_horse_with_no_name:愚かな間違い、修正。
アントンクルーグロフ

2

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

以下を考慮してください。

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操作を実行して、暗黙的intにデータ型に変更し、クリーンに変換できるようにするuniqueidentifer必要があります。つまり、最初にキャストして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;

この問題は、少なくともSQL Server内ではなく、データ型の変換によるものではありません。詳細は私の答えにありますが、変換はSQL Server内ではなくOLEDBドライバー内で実行されており、変換ルールは同じではありません。
ソロモンラッツキー16

1

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

私には「絶対的な」証拠はありませんが、問題は、UniqueIdentiferがサーバーに依存しており、おそらくプロバイダーがこの一意の識別子を取得するサーバー(ローカルまたはリモート)を見つけるのが困難であるという事実に起因していると思われますヌル。このため、おそらくこのシナリオでは他のデータ型を正常にキャストできますが、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

サーバーに依存しているUniqueidentifierDateTimeOffsetはどういう意味ですか?これに関する情報源はありますか?
クリスタ

@krystah-このリンクから(technet.microsoft.com/en-us/library/ms190215(v=sql.105).aspx)「uniqueidentifierデータ型には、グローバル一意識別子(GUID)として動作する16バイトのバイナリ値が格納されます。 GUIDは一意の2進数であり、世界の他のコンピューターはそのGUID値の複製を生成しません。GUIDの主な用途は、多くのサイトに多くのコンピューターがあるネットワークで一意でなければならない識別子を割り当てることです。 」
スコットホジン

DateTimeOffSet(msdn.microsoft.com/en-us/library/bb630289.aspx)の場合、タイムゾーンを認識し、24時間制に基づく時刻と組み合わせた日付を定義します
Scott Hodgin

これらの2つのデータ型はシナリオでエラーを発生させる唯一のデータ型であり、これらのデータ型は「サーバー依存」であるため、問題の原因であると「推測」しています。あなたのビューからあなたの結果を取得し、追加のヌルキャストを追加するためにOPENQUERYを使用している場合、プロバイダは、その情報を取得するために「どこ」を知っているので、それが動作するはずです
スコットHodgin

@krystahとScott:これらの2つのデータ型はサーバーに依存していません。少なくともあなたがどのように記述し、暗示しているかは違います。GUIDは、基礎となるバイナリ表現の点で「アーキテクチャ」に依存します(詳細についてはこちらをご覧ください)が、それが問題である場合、GUIDは適切に転送されません。DATETIMEOFFSETの場合、実際のタイムゾーン/ TimeZoneIDではなく、オフセットのみが認識されます。どちらもシステム情報を使用して新しい値を生成しますが、ここでは新しい値は生成されません。生成された場合、プロバイダーでは生成されません。
ソロモンラッツキー

0

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

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

declare @nullGuid as uniqueidentifier = null;

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

--use
@nullGuid AS [GUID]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.