如何在 INSERT 语句中使用存储过程中的 OUTPUT?

How can I use the OUTPUT from a stored procedure within an INSERT statement?

我正在尝试将数据从一个 table 复制到另一个,并向目标 table 添加一列,该列不存在于源 table 中,使用 INSERT INTO 和一个存储过程来为附加列生成值。

目标 table 有 counter 字段(整数)需要为插入的每一行递增(它不是 IDENTITY 列,递增此计数器由其他处理代码)。

计数器table有多个table的计数器,需要将计数器名称作为参数传递给存储过程,并使用output作为目标的附加列table.

我有一个存储过程,可以向它传递一个参数(计数器名称,在下面的示例中,计数器名称是“Counter_123”)并且它具有作为新计数器值的 output 值。

我可以 运行 这样的存储过程,它工作正常:

declare @value int
exec GetCounter 'Counter_123', @value output
PRINT @value

在这种情况下,如果计数器为 3,则 output 为 4。

这是我在 INSERT INTO 中尝试做的事情:

INSERT INTO Target_Table (col1, col2, col3, uspGetCounter 'Counter_123')
SELECT (val1, val2, val3) 
FROM Source_Table
WHERE ...

返回的错误是 'Counter_123'

附近的语法错误

我也试过将存储过程放在值列表中,但这也不起作用。

调用存储过程、向其传递参数并使用 INSERT INTO 查询中的 output 作为列值的语法是什么?

这里是存储过程代码供参考:

CREATE PROCEDURE [dbo].[uspGetCounter] 
    
    @CounterName varchar(30) = '', 
    @LastValue int OUTPUT
AS
BEGIN
    SET NOCOUNT ON
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

    BEGIN TRANSACTION

    if not exists (select * from Counter where COUNTER_NAME=@CounterName) 
       insert Counter (COUNTER_NAME, LAST_VALUE)
       values (@CounterName,0)

    select @LastValue=LAST_VALUE+1
       from Counter (holdlock)
       where COUNTER_NAME= @CounterName

    update Counter
       set LAST_VALUE=@LastValue,
           LAST_UPDATED=getdate(),
           from Counter
       where COUNTER_NAME=@CounterName

    COMMIT TRANSACTION
    
END

首先,程序放错地方了。 INSERT 语句的那部分需要列 names,而不是列 values。如果您可以 运行 那样(您不能),您对该过程所做的任何操作都将进入语句的 SELECT 子句。

顺便提一下,现有的 Counter table 和存储过程已过时。它们的存在是为了解决一个问题,该问题已作为核心功能融入 SQL 服务器。因此,这里的 BEST 解决方案是将计数器(所有计数器)转换为使用 SEQUENCE and NEXT VALUE FOR 语法。

然后,假设您已将序列设置为正确的值,INSERT 将如下所示:

INSERT INTO Target_Table (col1, col2, col3, [Counter_123])
SELECT (val1, val2, val3, NEXT VALUE FOR Counter_123) 
FROM Source_Table
WHERE ...

如果由于某种原因这不是一个选项,我会在单个事务中将计数器逻辑滚动到对 运行 的查询中,而不是很多事务,如下所示:

BEGIN TRANSACTION

DECLARE @LastValue int;
SELECT @LastValue = LAST_VALUE
       FROM Counter (holdlock)
       WHERE COUNTER_NAME = 'Counter_123'

INSERT INTO Target_Table (col1, col2, col3, [Counter_123])
SELECT val1, val2, val3, @LastValue + ROW_NUMBER() OVER (ORDER BY val1, val2, val3) 
FROM Source_Table
WHERE ...

UPDATE Counter
SET LAST_VALUE = (SELECT MAX([Counter_123]) FROM [Target_Table])
     ,LAST_UPDATED=current_timestamp
WHERE COUNTER_NAME = 'Counter_123'

COMMIT

假设您确实需要推出自己的序列生成器,因为使用内置序列会容易得多,那么根据您当前的逻辑,您一次只能插入一行,因为您的存储过程提供没有一次性分配一个值块的机制。因此,您需要某种循环,也许是游标,例如

DECLARE @Counter int, @Val1 int, @Val2 int, @Val3 int;

DECLARE MyCursor CURSOR LOCAL FOR
    SELECT val1, val2, val3 
    FROM Source_Table
    WHERE ...;

WHILE 1 =1 BEGIN
    FETCH NEXT FROM MyCursor INTO @Val1, @Val2, @Val3;
    IF @@FETCH_STATUS != 0 BREAK;

    EXEC uspGetCounter 'Counter_123', @Counter OUTPUT;

    INSERT INTO Target_Table (Val1, Val2, Val3, CounterCol)
        VALUES (@Val1, @Val2, @Val3, @Counter);
END;

您 运行 您需要插入的每一行的 SP 并获取新的序列值。

如果使用这种方法,我还会按如下方式修改您的 SP 以提高性能:

-- Attempt an update and assignment in a single statement
UPDATE dbo.[Counter] SET
  @LastValue = LAST_VALUE = LAST_VALUE + 1
  , LAST_UPDATED = GETDATE()
WHERE COUNTER_NAME = @CounterName;

-- If doesn't exist (less likely case as only the first time the counter is accessed)
IF @@ROWCOUNT = 0 BEGIN
    SET @LastValue = 1;
    -- Create a new record if none exists
    INSERT INTO dbo.[Counter] (COUNTER_NAME, LAST_VALUE, LAST_UPDATED)
        SELECT @CounterName, @LastValue, GETDATE();
END;

减少访问并加速 SP。


但是,再次假设您需要滚动自己的序列,如果您经常使用它来复制数据,您最好修改存储过程以一次性分配一个值块,即当您调用您的SP 你传递了一个参数 @NumValuesRequired 然后你的 return 值是那么长的块的第一个。然后你就可以一次完成你的复制,例如

DECLARE @RecordCount int, @CounterStart int;

SELECT @RecordCount = COUNT(*)
FROM Source_Table
WHERE ...;

EXEC uspGetCounter2 'Counter_123', @RecordCount, @CounterStart OUTPUT;

INSERT INTO Target_Table (Val1, Val2, Val3, CounterCol)
    SELECT val1, val2, val3, @CounterStart + ROW_NUMBER() OVER () - 1
    FROM Source_Table
    WHERE ...;