在 SQL Server 数据库之间传递用户定义的表类型

Posted

技术标签:

【中文标题】在 SQL Server 数据库之间传递用户定义的表类型【英文标题】:Passing a user-defined table type between SQL Server database 【发布时间】:2013-01-24 15:21:16 【问题描述】:

我在 SQL Server 的一个数据库中有一个用户定义的表类型(我们称之为DB1)。

我的类型的定义非常简单,只包含 2 列。创建我的类型的脚本如下:

CREATE TYPE [dbo].[CustomList] AS TABLE
(
    [ID] [int] ,
    [Display] [NVARCHAR] (100)  
)

我还在另一个数据库上运行了相同的脚本,所以我的类型是在 2 个数据库上(我们称第二个数据库为DB2)。

我现在从我的 C# 应用程序中调用 DB1 中的存储过程,并传入我的 CustomList 用户定义类型的参数。

DB1 中的过程现在需要调用 DB2 上的过程,传入此 CustomList

所以,DB1 中的过程如下所示:

ALTER PROCEDURE [dbo].[selectData]
    @psCustomList CustomList ReadOnly
AS
BEGIN
    EXEC DB2.dbo.selectMoreData @psCustomList   
END

DB2 中的过程是这样的(我只显示了参数列表,因为这就是所有需要的):

ALTER PROCEDURE [dbo].[selectMoreData]
    @psCustomList CustomList ReadOnly
AS
BEGIN
......

当我运行它时,我收到以下错误:

操作数类型冲突:CustomList 与 CustomList 不兼容

有人知道我做错了什么吗?

我使用的是 SQL Server 2008。

提前致谢

【问题讨论】:

Passing Table Valued parameter to stored procedure across different databases的可能重复 “所以我的类型在 2 个数据库上” - 不,您的两个数据库恰好有定义为相同名称和结构的表类型。没有它们是同一类型的概念。而且您无法定义不同数据库类型的变量,因此您甚至无法创建正确类型的变量并复制数据。 【参考方案1】:

这是Can you create a CLR UDT to allow for a shared Table type across databases?的副本

本质上,用户定义的表类型不能跨数据库共享。基于 CLR 的 UDT 可以跨数据库共享,但前提是满足某些条件,例如将相同的程序集加载到两个数据库中,以及其他一些事情(详细信息在重复的问题中注明以上)。

对于这种特殊情况,有一种方法可以将信息从DB1 传递到DB2,尽管这不是一个优雅的解决方案。为了使用表类型,您当前的数据库上下文需要是表类型所在的数据库。这是通过USE 语句完成的,但如果需要在存储过程中完成,则只能在动态 SQL 中完成。

USE [DB1];
GO

CREATE PROCEDURE [dbo].[selectData]
    @psCustomList CustomList READONLY
AS
BEGIN
    -- create a temp table as it can be referenced in dynamic SQL
    CREATE TABLE #TempCustomList
    (
        [ID] [INT],
        [Display] [NVARCHAR] (100)
    );

    INSERT INTO #TempCustomList (ID, Display)
        SELECT ID, Display FROM @psCustomList;

    EXEC('
        USE [DB2];

        DECLARE @VarCustomList CustomList;

        INSERT INTO @VarCustomList (ID, Display)
            SELECT ID, Display FROM #TempCustomList;

        EXEC dbo.selectMoreData @VarCustomList;
     ');
END

更新

使用sp_executesql 试图通过简单地将UDTT 作为TVP 传递来避免本地临时表,或者仅仅作为进行参数化查询的一种方式,实际上都不起作用(尽管它看起来确实像应该)。含义,如下:

USE [DB1];
GO
CREATE PROCEDURE dbo.CrossDatabaseTableTypeA
(
    @TheUDTT dbo.TestTable1 READONLY
)
AS
SET NOCOUNT ON;

EXEC sp_executesql N'
  USE [DB2];
  SELECT DB_NAME() AS [CurrentDB];

  DECLARE @TableTypeDB2 dbo.TestTable2;
  INSERT INTO @TableTypeDB2 ([Col1])
    SELECT tmp.[Col1]
    FROM   @TableTypeDB1 tmp;

  --EXEC dbo.CrossDatabaseTableTypeB @TableTypeDB2;
  ',
  N'@TableTypeDB1 dbo.TestTable1 READONLY',
  @TableTypeDB1 = @TheUDTT;
GO


DECLARE @tmp dbo.TestTable1;
INSERT INTO @tmp ([Col1]) VALUES (1), (3);
SELECT * FROM @tmp;

EXEC dbo.CrossDatabaseTableTypeA @TheUDTT = @tmp;

将在“@TableTypeDB2 的数据类型无效”上失败,即使它正确显示 DB2 是“当前”数据库。它与sp_executesql 如何确定变量数据类型有关,因为错误将@TableTypeDB2 称为“变量#2”,即使它是在本地创建的而不是作为输入参数。

事实上,sp_executesql 会在声明单个变量时出错(通过sp_executesql 的参数列表输入参数),即使它从未被引用,更不用说使用了。这意味着,以下代码将遇到与上面的查询发生的无法找到 UDTT 定义的相同错误:

USE [DB1];
GO
CREATE PROCEDURE dbo.CrossDatabaseTableTypeC
AS
SET NOCOUNT ON;

EXEC sp_executesql N'
  USE [DB2];
  SELECT DB_NAME() AS [CurrentDB];

  DECLARE @TableTypeDB2 dbo.TestTable2;
  ',
  N'@SomeVar INT',
  @SomeVar = 1;
GO

(感谢@Mark Sowul 提到sp_executesql 在传递变量时不起作用)

但是,这个问题可以通过更改sp_executesql 的执行数据库来解决(好吧,只要您不尝试传入 TVP 以避免临时表 - 上面的 2 个查询)这样该过程将在另一个 TVP 所在的数据库中是本地的。关于sp_executesql 的一个好处是,与EXEC 不同,它是一个存储过程,并且是一个系统存储过程,因此它可以是完全限定的。利用这一事实允许sp_executesql 工作,这也意味着动态SQL 中不需要USE [DB2]; 语句。以下代码确实有效:

USE [DB1];
GO
CREATE PROCEDURE dbo.CrossDatabaseTableTypeD
(
    @TheUDTT dbo.TestTable1 READONLY
)
AS
SET NOCOUNT ON;

-- create a temp table as it can be referenced in dynamic SQL
CREATE TABLE #TempList
(
    [ID] [INT]
);

INSERT INTO #TempList ([ID])
   SELECT [Col1] FROM @TheUDTT;

EXEC [DB2].[dbo].sp_executesql N'
  SELECT DB_NAME() AS [CurrentDB];

  DECLARE @TableTypeDB2 dbo.TestTable2;
  INSERT INTO @TableTypeDB2 ([Col1])
    SELECT tmp.[ID]
    FROM   #TempList tmp;

  EXEC dbo.CrossDatabaseTableTypeB @TableTypeDB2;
  ',
  N'@SomeVariable INT',
  @SomeVariable = 1111;
GO

【讨论】:

请注意,如果您使用sp_executesql,如果您尝试使用参数块显然将无法正常工作 @MarkSowul 我测试了,看看你的意思。我更新以添加该信息。谢谢! 不完全——在sp_executesql 中使用 any 参数就可以了。我还在使用临时表;我只是传递了一些无害的东西。然后你会得到奇怪的错误inside动态SQL,抱怨表类型的使用(即使你用USE切换了dbs) @MarkSowul 是的,我现在明白了。事实上,简单地声明一个变量将导致在“当前”数据库中搜索 UDTT 定义。但是,我在更新的答案中显示了一个修复:-)

以上是关于在 SQL Server 数据库之间传递用户定义的表类型的主要内容,如果未能解决你的问题,请参考以下文章

使用 JDBC 将用户定义的表类型传递给 SQL Server 存储过程

将用户定义表中的日期值传递给 SQL Server 2008 存储过程

重新定义 MS Access 文件和 SQL Server 之间的数据链接

如何从 SQL Server 2014 中的 Select 查询中将数据分配给用户定义的表类型

SQL Server中用户定义的表类型的性能

还原SQL Server 2005数据库后,将所有用户链接到登录