Sql server 为什么我的光标停在中间? 这里发布的代码是“示例”代码,不是生产代码。我这样做是为了使我解释的问题可读/简洁。
使用与下面类似的代码,我们遇到了一个奇怪的bug。每次插入后,WHILE循环停止 表包含100行,当插入在50行之后完成时,光标停止,只触及前50行。当插入在55之后完成时,它在55之后停止,依此类推Sql server 为什么我的光标停在中间? 这里发布的代码是“示例”代码,不是生产代码。我这样做是为了使我解释的问题可读/简洁。,sql-server,sql-server-2005,tsql,Sql Server,Sql Server 2005,Tsql,使用与下面类似的代码,我们遇到了一个奇怪的bug。每次插入后,WHILE循环停止 表包含100行,当插入在50行之后完成时,光标停止,只触及前50行。当插入在55之后完成时,它在55之后停止,依此类推 -- This code is an hypothetical example written to express -- an problem seen in production DECLARE @v1 int DECLARE @v2 int DECLARE MyCursor CURSOR
-- This code is an hypothetical example written to express
-- an problem seen in production
DECLARE @v1 int
DECLARE @v2 int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @v1, @v2
WHILE(@@FETCH_STATUS=0)
BEGIN
IF(@v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (@v2)
END
FETCH NEXT FROM MyCursor INTO @v1, @v2
END
CLOSE MyCursor
DEALLOCATE MyCursor
table2上有一个AFTER INSERT触发器,用于将table2上的突变记录到第三个表中,并恰当地命名为突变。它包含一个游标,用于插入以处理插入(每列以非常特定的方式记录突变,这需要游标)
一点背景知识:这存在于一组小型支持表中。出于审计目的,项目要求记录对源数据所做的每个更改。带有日志记录的表格包含诸如银行账号之类的内容,大量资金将存入其中。最多有几千条记录,它们应该很少被修改。审计功能是用来阻止欺诈的:我们用“谁做的”来记录“发生了什么变化”
实现这一点的明显、快速和合乎逻辑的方法是在每次进行更新时存储整行。这样我们就不需要光标了,它的性能会更好。然而,局势的政治性意味着我束手无策
呸。现在回到问题上来
触发器的简化版本(真实版本对每列进行插入,并且还插入旧值):
为什么代码在循环的中间退出?< p>这个代码不会从游标中获取任何其他值,也不会增加任何值。事实上,没有理由在这里实现游标 您的整个代码可以重写为:
DECLARE @v1 int
DECLARE @v2 int
SELECT @v1 = Col1, @v2 = Col2
FROM table
IF(@v1>10)
INSERT INTO table2(col1) VALUES (@v2)
编辑:已对Post进行编辑,以修复我所指的问题。此代码不会从光标获取任何进一步的值,也不会增加任何值。事实上,没有理由在这里实现游标 您的整个代码可以重写为:
DECLARE @v1 int
DECLARE @v2 int
SELECT @v1 = Col1, @v2 = Col2
FROM table
IF(@v1>10)
INSERT INTO table2(col1) VALUES (@v2)
编辑:已对帖子进行编辑,以修复我所指的问题。Ryan,您的问题是@@FETCH\u状态对于连接中的所有游标都是全局的 因此触发器中的光标以@FETCH\u状态-1结束。当控件返回到上面的代码时,最后一个@FETCH\状态为-1,因此光标结束 可以在MSDN上找到的文档中对此进行了解释 您可以使用一个局部变量来存储@@FETCH\u状态,并将该局部变量放入循环中。你会得到这样的结果:
DECLARE @v1 int
DECLARE @v2 int
DECLARE @FetchStatus int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @v1, @v2
SET @FetchStatus = @@FETCH_STATUS
WHILE(@FetchStatus=0)
BEGIN
IF(@v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (@v2)
END
FETCH NEXT FROM MyCursor INTO @v1, @v2
SET @FetchStatus = @@FETCH_STATUS
END
CLOSE MyCursor
DEALLOCATE MyCursor
值得注意的是,这种行为不适用于嵌套游标。我已经做了一个快速的例子,它在SQLServer2008上返回预期的结果(50)
Ryan,您的问题是@@FETCH\u状态对于连接中的所有游标都是全局的 因此触发器中的光标以@FETCH\u状态-1结束。当控件返回到上面的代码时,最后一个@FETCH\状态为-1,因此光标结束 可以在MSDN上找到的文档中对此进行了解释 您可以使用一个局部变量来存储@@FETCH\u状态,并将该局部变量放入循环中。你会得到这样的结果:
DECLARE @v1 int
DECLARE @v2 int
DECLARE @FetchStatus int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @v1, @v2
SET @FetchStatus = @@FETCH_STATUS
WHILE(@FetchStatus=0)
BEGIN
IF(@v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (@v2)
END
FETCH NEXT FROM MyCursor INTO @v1, @v2
SET @FetchStatus = @@FETCH_STATUS
END
CLOSE MyCursor
DEALLOCATE MyCursor
值得注意的是,这种行为不适用于嵌套游标。我已经做了一个快速的例子,它在SQLServer2008上返回预期的结果(50)
正如ck所提到的,您不会获取任何进一步的值。因此,@FETCH\u状态从AFTER INSERT触发器中包含的游标中获取其值 您应该将代码更改为
DECLARE @v1 int
DECLARE @v2 int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @v1, @v2
WHILE(@@FETCH_STATUS=0)
BEGIN
IF(@v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (@v2)
END
FETCH NEXT FROM MyCursor INTO @v1, @v2
END
正如ck所提到的,您不会获取任何进一步的值。因此,@FETCH\u状态从AFTER INSERT触发器中包含的游标中获取其值 您应该将代码更改为
DECLARE @v1 int
DECLARE @v2 int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO @v1, @v2
WHILE(@@FETCH_STATUS=0)
BEGIN
IF(@v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (@v2)
END
FETCH NEXT FROM MyCursor INTO @v1, @v2
END
你的问题是你根本不应该使用光标!这是上面给出的示例的代码
INSERT INTO table2(col1)
SELECT Col1 FROM table
where col1>10
您也不应该在触发器中使用光标,这会降低性能。如果有人在insert中添加100000行,这可能需要几分钟(甚至几小时),而不是几毫秒或几秒钟。我们在这里替换了一个(在我开始这份工作之前),并将该表的导入时间从40分钟减少到45秒
应该检查使用游标的任何生产代码,用正确的基于集合的代码替换它。根据我的经验,90%以上的游标都可以以基于集合的方式编写。您的问题是根本不应该使用游标!这是上面给出的示例的代码
INSERT INTO table2(col1)
SELECT Col1 FROM table
where col1>10
您也不应该在触发器中使用光标,这会降低性能。如果有人在insert中添加100000行,这可能需要几分钟(甚至几小时),而不是几毫秒或几秒钟。我们在这里替换了一个(在我开始这份工作之前),并将该表的导入时间从40分钟减少到45秒
应该检查使用游标的任何生产代码,用正确的基于集合的代码替换它。根据我的经验,90%以上的游标都可以用基于集合的方式编写。这只是对触发器的一个简单误解。。。你根本不需要光标
if UPDATE(Col1)
begin
insert into mutaties
(
tablename,
columnname,
newvalue
)
select
'table2',
coalesce(d.Col1,''),
coalesce(i.Col1,''),
getdate()
from inserted i
join deleted d on i.ID=d.ID
and coalesce(d.Col1,-666)<>coalesce(i.Col1,-666)
end
这是对触发器的简单误解。。。你根本不需要光标
if UPDATE(Col1)
begin
insert into mutaties
(
tablename,
columnname,
newvalue
)
select
'table2',
coalesce(d.Col1,''),
coalesce(i.Col1,''),
getdate()
from inserted i
join deleted d on i.ID=d.ID
and coalesce(d.Col1,-666)<>coalesce(i.Col1,-666)
end
您不必使用光标将每一列作为单独的行插入 以下是一个例子:
INSERT LOG.DataChanges
SELECT
SchemaName = 'Schemaname',
TableName = 'TableName',
ColumnName = CASE ColumnID WHEN 1 THEN 'Column1' WHEN 2 THEN 'Column2' WHEN 3 THEN 'Column3' WHEN 4 THEN 'Column4' END
ID = Key1,
ID2 = Key2,
ID3 = Key3,
DataBefore = CASE ColumnID WHEN 1 THEN I.Column1 WHEN 2 THEN I.Column2 WHEN 3 THEN I.Column3 WHEN 4 THEN I.Column4 END,
DataAfter = CASE ColumnID WHEN 1 THEN D.Column1 WHEN 2 THEN D.Column2 WHEN 3 THEN D.Column3 WHEN 4 THEN D.Column4 END,
DateChange = GETDATE(),
USER = WhateverFunctionYouAreUsingForThis
FROM
Inserted I
FULL JOIN Deleted D ON I.Key1 = D.Key1 AND I.Key2 = D.Key2
CROSS JOIN (
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) X (ColumnID)
在X表中,您可以使用第二列编写额外的行为代码,该列专门描述如何处理该列(假设您希望某些列一直发布,但其他列仅在值更改时发布)。重要的是,这是一个将行拆分为每列的交叉连接技术的示例,但是还有很多事情可以做