为什么此显式强制转换仅导致链接服务器出现问题?


21

我正在通过原始服务器上的视图从链接服务器查询数据。视图必须包括几个标准化列,如CreatedModifiedDeleted,但是在这种情况下,源服务器上的表中不具有任何合适的信息。因此,这些列被显式转换为它们各自的类型。我更新了视图,更改了

NULL AS Modified

CAST(NULL as DateTime) as Modified

但是,执行此更新后,视图将触发以下错误消息:

消息7341,级别16,状态2,第3行无法从链接服务器“”的OLE DB访问接口“ SQLNCLI11”获取列“(用户生成的表达式).Expr1002”的当前行值。

我们通常在原始服务器上进行了这种“显式强制转换”更改,而无需担心,我怀疑问题可能与所涉及的服务器版本有关。我们确实不需要应用此强制转换,但感觉更干净。现在,我只是好奇为什么会这样。

服务器版本(来源):

Microsoft SQL Server 2012-11.0.5058.0(X64)2014年5月14日18:34:29版权所有(c)Windows NT 6.1(Build 7601:Service Pack 1)(Hypervisor)上的Microsoft Corporation Enterprise Edition(64位)

服务器版本(链接):

Microsoft SQL Server 2008 R2(SP1)-10.50.2500.0(X64)2011年6月17日版权所有(c)Windows NT 6.1(Build 7601:Service Pack 1)上的Microsoft Corporation Enterprise Edition(64位)(Hypervisor )

编辑
我只是意识到我没有发布所有有问题的列而犯了一个错误,我必须为遗漏重要的细节表示歉意。我不知道我怎么没早注意到这一点。但是,问题仍然存在。

转换为DateTime时不会发生错误的转换,而是将列转换为UniqueIdentifier时发生。

是罪魁祸首:

CAST(NULL AS UniqueIdentifier) AS [GUID]

SQL Server 2008 R2支持UniqueIdentifiers,并且如注释中所述,视图执行的查询在链接服务器上运行良好。


每个服务器上是否有不同的ANSI NULL设置?不同的排序规则?
伦道夫·韦斯特

这两个服务器的ANSI NULL =0。原始服务器具有排序规则Danish_Norwegian_CI_AS,而链接服务器具有排序规则SQL_Danish_Pref_CP1_CI_AS,但是该COLLATE子句无法应用于DateTime列,因此我没有做更多的事情!
krystah

如果select Null from ...在WITH或嵌套查询中以及CAST在另一个查询中,这会失败吗?
Stoleg '16

如果没有显式强制转换,则将其视为,INT因此您已更改了数据类型。我不知道为什么这会给您该错误信息。
马丁·史密斯

之前,我曾尝试将选择的值与实际值包装在CTE中,然后选择它们并在CTE之后的语句中使用强制转换的NULL进行操作,但是没有运气。我尝试了您的建议,将NULL保留在CTE中并将其强制转换为查询CTE的语句,但是它也会产生相同的错误。
krystah

Answers:


13

因此,我意识到在CAST本地而不是在远程实例上完成操作后,便能够重现该错误。我以前曾建议升级到SP3,以期解决此问题(部分原因是由于无法重现SP3上的错误,部分原因是无论如何它都是一个好主意)。但是,既然我可以重现该错误,那么很明显,升级到SP3(虽然可能仍然是一个好主意)将无法解决此问题。我还重现了SQL Server 2008 R2 RTM和2014 SP1中的错误(在所有三种情况下均使用“回送”本地链接服务器)。

看来,这个问题与做在那里执行查询时,或至少其中一部分(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,或者NULLOLE DB驱动程序可能无法正确转换。


根据我所做的测试,这似乎是一个错误,但是我不确定该错误是否在SQL Server或SQL Server Native Client / OLEDB驱动程序内。但是,转换错误发生在OLEDB驱动程序内,因此不一定是从转换INTUNIQUEIDENTIFIER(SQL Server不允许的转换)的问题,因为驱动程序未使用SQL Server进行转换(SQL Server也不会允许转换INTDATE,但OLEDB驱动程序成功处理了该操作,如其中一项测试所示)。

我进行了三项测试。对于成功的两个,我查看了XML执行计划,这些计划显示了正在远程执行的查询。对于这三个,我都通过SQL Profiler捕获了任何Exception或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跟踪文件进行比较时,它的差异将减少一倍)

  • 测试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跟踪文件进行比较时,它的差异将减少一倍)

    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 Profiler跟踪显示此远程字段的数据类型实际上是INT。跟踪还显示,客户端上的字段(即我从中运行查询的位置)是DATE,如预期的那样。从INT到的转换(DATE这会在SQL Server中导致错误)在OLEDB驱动程序中运行良好。远程值是NULL,因此它直接返回,因此<ColumnReference Column="Expr1002" />

如果您查看测试#1,则它是SELECT 1在“远程”系统上执行的。SQL Profiler跟踪显示此远程字段的数据类型实际上是INT。跟踪还显示,客户端上的字段(即我从中运行查询的位置)是GUID,如预期的那样。从INT到的转换GUID(请记住,这是在驱动程序中完成的,OLEDB称其为“ GUID”),这会在SQL Server中引起错误,但在OLEDB驱动程序中工作得很好。远程值不是 NULL,因此将其替换为文字NULL,因此<Const ConstValue="NULL" />

测试#2失败,因此没有执行计划。但是,它确实可以成功查询“远程”系统,但无法将结果集传递回去。SQL Profiler捕获的查询为:

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

那是与测试1中完全相同的查询,但是这里失败了。还有其他一些细微差异,但是我无法完全解释OLEDB通信。但是,远程字段仍显示为INT(wType = 3 = adInteger /四字节带符号整数/ DBTYPE_I4),而“客户端”字段仍显示为GUID(wType = 72 = adGUID /全局唯一标识符/ DBTYPE_GUID)。OLE DB文档没有太大帮助,因为GUID数据类型转换DBDATE数据类型转换I4数据类型转换显示不支持从I4GUIDDBDATE的转换,但是DATE查询仍然有效。

这三个测试的Trace XML文件位于PasteBin上。如果要查看每个测试与其他测试不同之处的详细信息,可以将它们保存在本地,然后对它们进行“比较”。这些文件是:

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

ERGO?

怎么办呢?考虑到自SQL SQLNCLI11Server 2012起不推荐使用SQL本机客户端-可能只是我在上一节中提到的解决方法。有关SQL Server本机客户端主题的大多数MSDN页面在以下内容中均具有以下注意事项:最佳:

警告

SQL Server 2012以后不支持SQL Server Native Client(SNAC)。请避免在新的开发工作中使用SNAC,并计划修改当前使用它的应用程序。用于SQL ServerMicrosoft 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”返回消息“不支持请求的转换”。
消息7341,级别16,状态2,第53
行无法从链接服务器“ LocalODBC”的OLE DB提供程序“ MSDASQL”获取列“(用户生成的表达式).Expr1002”的当前行值。


聚苯乙烯

与在远程服务器和本地服务器之间传输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 Profiler跟踪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的名声,并回退到上述解决方法。做得好!
krystah'8

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


该解决方法可能值得一提,但由于OP已阐明问题的罪魁祸首是uniqueidentifier专栏,因此它可能不再适用。还是可以对其进行修改- CAST('00000000-0000-0000-0000-000000000000' AS UniqueIdentifier) AS [GUID]也许?
Andriy M

多谢你们。强制转换为空的GUID或DateTime可以,但是我需要了解为什么会这样。还值得一提的是,我没有导入任何内容,因此不可能更改源数据。
krystah

1
1900-00-00是无效的日期,将不被接受。
a_horse_with_no_name 2016年

@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中。详细信息在我的答案中,但是转换是在OLEDB驱动程序中完成的,而不是在SQL Server中完成的,并且转换规则也不相同。
所罗门·鲁兹基

1

OP最终可以决定这是否合适。

我没有“绝对”证据,但我“怀疑”这个问题源于一个事实,即UniqueIdentifer依赖于服务器,并且提供商可能难以确定从哪个服务器(本地或远程)获取此uniqueidentifier,即使它是空值。因此,在这种情况下,您可能可以成功地强制转换任何其他数据类型,但不能唯一转换。依赖于“服务器”的数据类型(例如UNIQUEIDENTIFIERS和DATETIMEOFFSET)将为您提供遇到的错误。

使用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

@krystah-从此链接(technet.microsoft.com/zh-cn/library/ms190215(v=sql.105).aspx)“唯一标识符数据类型存储16字节的二进制值,这些值用作全局唯一标识符(GUID) GUID是唯一的二进制数;世界上没有其他计算机会生成该GUID值的副本。GUID的主要用途是分配一个标识符,该标识符在具有许多站点的许多计算机的网络中必须是唯一的。 ”
Scott Hodgin

对于DateTimeOffSet(msdn.microsoft.com/zh-cn/library/bb630289.aspx)定义一个日期,该日期与具有时区意识的一天中的时间结合在一起,并且基于24小时制
Scott Hodgin

我是“推断”的,因为这两种数据类型“似乎”是导致您的方案中出现错误的唯一数据,并且这些数据类型是“与服务器有关的”,所以这就是问题的原因。如果您使用OPENQUERY从视图中检索结果并添加其他null强制转换,则该方法应该起作用,因为提供者知道“何处”获取该信息
Scott Hodgin

@krystah和Scott:这两个数据类型与服务器无关,至少与您的描述和暗示方式无关。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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.