【问题标题】:How to use OPENROWSET to execute a stored procedure with parameters and insert result into a temp table如何使用 OPENROWSET 执行带参数的存储过程并将结果插入临时表
【发布时间】:2022-01-14 07:58:16
【问题描述】:

我想使用OPENROWSET 将存储过程的结果插入到临时表中。但是,我遇到的问题是我无法将参数传递给我的存储过程。

这是我的存储过程:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[N_spRetrieveStatement]
    @PeopleCodeId nvarchar(10),
    @StatementNumber int
AS
    SET NOCOUNT ON

    DECLARE @PersonId int
    SELECT @PersonId = [dbo].[fnGetPersonId](@PeopleCodeId)

    SELECT * 
    INTO #tempSpRetrieveStatement 
    FROM OPENROWSET('SQLNCLI', 'Server=PCPRODDB01;Trusted_Connection=yes;',
                    'EXEC Campus.dbo.spRetrieveStatement @StatementNumber, @PersonId');
     
    --2577, 15084

    SELECT * 
    FROM #tempSpRetrieveStatement;

【问题讨论】:

  • 你有链接服务器吗?为什么不EXEC PCPRODDB01.Campus.dbo.spRetrieveStatement @StatementNumber, @PersonId;?
  • 临时表中的插入真的有一点吗? SELECT ... INTO 和 SELECT .. FROM 之间是否会发生一些事情来保证您为简单起见而忽略的中间表?首先选择一个临时表,然后选择其中的内容,否则显然是不必要的迂回。
  • @JeroenMostert 是的,我确实需要将结果插入到临时表中。稍后将用于其他目的。

标签: sql sql-server stored-procedures


【解决方案1】:

OpenRowSet 不允许您使用输入参数执行过程。您必须使用 INSERT/EXEC。

INTO #tempSpRetrieveStatement(Col1, Col2,...)
EXEC PCPRODDB01.Campus.dbo.spRetrieveStatement @StatementNumber, @PersonId

在运行上述命令之前为 PCPRODDB01 创建和测试 LinkedServer。

【讨论】:

  • 是的,这是我的另一个选择。这样做的问题是,如果存储过程中的列发生更改,也会导致临时表列出现问题。
【解决方案2】:

鉴于您提供的代码示例,您的问题的根源在于您的语句中实际上没有参数,您正在传输到您正在连接的远程服务器。即使它是您连接的同一台机器,它们也会位于不同的进程中,并且另一个进程无权访问您的会话变量。

LinkedServer 作为一个选项被提及,我的理解是这是首选选项。然而在实践中,由于技术或组织限制的本地怪癖,这并不总是可用的。它发生了。

但是有一种方法可以做到这一点。

它隐藏在明显的视线中。

您需要将文字传递到将在另一台服务器上执行的字符串中,对吧?

因此,您首先要构建将执行此操作的字符串。

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[N_spRetrieveStatement]
    @PeopleCodeId nvarchar(10),
    @StatementNumber int
AS

SET NOCOUNT ON

DECLARE
    @PersonId INT,
    @TempSQL VARCHAR(4000) = '';

SELECT @PersonId = [dbo].[fnGetPersonId](@PeopleCodeId);


SET @TempSQL =
    'EXEC Campus.dbo.spRetrieveStatement(''''' + 
        FORMAT(@StatementNumber,'D') +''''', ''''' +
        FORMAT(@PersonId,'D') + ''''')';
        --2577, 15084

请注意看似过多的引号。这不是一个错误——这是一个预兆。因为,是的,OPENROWSET 讨厌将变量作为参数。它也只需要文字。那么,我们如何为 OPENROWSET 提供它所需要的东西呢?

我们创建一个字符串,它是整个语句,没有任何类型的变量。我们执行它。

SET @TempSQL = 
    'SELECT * INTO #tempSpRetrieveStatement ' +
    'FROM OPENROWSET(''SQLNCLI'', ''Server=PCPRODDB01;Trusted_Connection=yes;'', ' + @TempSQL + 
                    'EXEC Campus.dbo.spRetrieveStatement @StatementNumber, @PersonId';

EXEC (@TempSQL);

SELECT * 
FROM #tempSpRetrieveStatement;

就是这样!很简单,除了计算你的转义引号,对吧?

现在...这几乎超出了您提出的问题的范围,但这是我通过 OPENROWSET 在另一台机器上执行存储过程时遇到的一个“问题”。您显然习惯于使用临时表。如果您正在调用的存储过程正在创建临时表或执行其他一些操作(简而言之),这将失败,从而使您的 SQL 服务器产生歧义的恐惧。它不喜欢模棱两可。如果是这种情况,您将看到如下消息:

“消息 11514,级别 16,状态 1,过程 sp_describe_first_result_set,第 1 行

无法确定元数据,因为过程“...您的本地存储过程的名称...”中的语句“...您的远程 EXEC 语句...”包含动态 SQL .考虑使用 WITH RESULT SETS 子句显式描述结果集。”

那么,这是怎么回事?

您不仅可以使用 OPENROWSET 取回数据。本地服务器和远程服务器就本地服务器对远程服务器的期望进行了简短的对话(因此它可以优化接收和处理它——这对于大型行集来说非常重要)。从 SQL Server 2012 开始,sp_describe_first_result_set 是较新的过程,通常它会在您不注意的情况下快速执行。只是它的占卜之力不是无限的。也就是说,它不知道如何获取有关临时表的类型和名称信息(可能还有其他一些它不能做的事情——select 语句中的 PIVOT 可能是正确的)。

我特别想明确指出这一点,因为您回复了关于您对使用 LinkedServer 犹豫不决的问题。事实上,与您犹豫不决的相同原因可能会使该错误消息的建议完全无用 - 您甚至无法预测您将获得哪些列以及以何种顺序获得,直到您得到他们。

我认为,如果您只是根据条件语句向上游分支并且正在执行几个潜在的 SELECT 语句之一,那么您正在做的事情将会起作用。我认为,如果您只是不确定是否可以依赖正在修复的上游组件并试图确保即使 it 变化,this 过程也不会'不必,因为它非常通用。

但另一方面,您面临的情况是,您实际上无法保证 SQL Server 可以预测列,您可能不得不强制对您调用的存储过程进行一些更改 坚持它是稳定的。例如,您可以通过使用 CASE 表达式而不是任何 PIVOT 来确定如何确保所有可能的字段始终存在。您可能会创建一个会话表,该表专用于容纳您需要的 SELECT 足够长的时间来执行此操作,然后将内容从那里删除。您可能会更改传输数据的方式,使其基本上通过 UNPIVOT 的等效。在所有这些额外的工作之后,如果您使用 LinkedServer 或 OPENROWSET 来移植数据,也许这只是一个偏好问题。

这就是你所问的字面问题的答案,也是你可以用这个答案做些什么的限制之一。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-26
    • 2018-07-05
    • 2020-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    相关资源
    最近更新 更多