如何在 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 ...;
我正在尝试将数据从一个 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 ...;