有没有办法在SQL Server光标中使用参数?

问题描述:

我在数据库中有一个父子关系。我需要做的是循环父母的查询,并使用父母的主键,得到它的孩子。我遇到的问题是我需要使用参数化游标(传入密钥)来执行此操作。有没有办法在SQL Server光标中使用参数?

在SQL Server中是否存在这样的事情或者一个模仿它的诡计?我试过这样做,但它没有工作:

DECLARE @value VARCHAR(20); 
DECLARE @someKey NUMERIC(19,0); 

DECLARE main_curs 
CURSOR FOR SELECT value FROM someTable where key = @someKey; 

SET @someKey = 12345; 

OPEN main_curs 
FETCH NEXT FROM main_curs INTO @value; 
CLOSE main_curs 
DEALLOCATE main_curs 

但它似乎没有拿起我设置@someKey。

任何帮助,将不胜感激。谢谢!

UPDATE

我应该包括更多的信息,因为我所做的例子似乎太简单了。我有多个我需要使用的@someKey值。如前所述,我有一个亲子关系,我最多可以有6个孩子。所以我得到了一份家长名单,它是各自的专栏,并通过它迭代。在WHILE-LOOP中,我想从父项中获取主键并调用另一个光标来获取子项信息(返回不同的列)。所以我会用不同的@someKey值设置多个对子游标的调用。希望这是有道理的。

+0

你可以只是查询数据,而不使用选择这通常更有效吗? – Mark 2012-10-12 19:33:48

你可以尝试的一件事是使用nested cursors。一个例子是标题为:的页面底部使用嵌套游标生成报告输出

需要之前设置@someKey = 12345;游标声明,如:

SET @someKey = 12345; 
DECLARE main_curs 
CURSOR FOR SELECT value FROM someTable where key = @someKey; 
+0

我试过这个,发现如果关闭游标,改变变量的值,然后重新打开游标,游标的内容不会改变。看起来,游标定义是在DECLARE语句中“编译”的。我来到这里是为了寻找解决光标问题的方法。 – 2016-07-25 20:37:40

你似乎有东西错了顺序,你没有真正做光标里面什么?

DECLARE @value VARCHAR(20); 
DECLARE @someKey NUMERIC(19,0); 

SET @someKey = 12345; --this has to be set before its used in cursor declaration 

DECLARE main_curs 
CURSOR FOR SELECT value FROM someTable where key = @someKey; 
OPEN main_curs 
FETCH NEXT FROM main_curs INTO @value;  -- first row is fetched 

WHILE @@FETCH_STATUS = 0  -- start the loop 
BEGIN 

-- do something here with @value 

FETCH NEXT FROM main_curs INTO @value; --fetch the next row 
END 

CLOSE main_curs 
DEALLOCATE main_curs 

如果您想遍历递归层次使用CTE,请参阅Recursive Queries Using Common Table Expressions。您可以在递归CTE上声明您的光标,例如:

create table test (
    id int not null identity(1,1) primary key, 
    parent_id int null, 
    data varchar (max)); 
go 

insert into test (parent_id, data) values 
    (null, 'root'), 
    (1, 'child 1'), 
    (1, 'child 2') , 
    (2, 'child of child 1'), 
    (4, 'child of child of child 1'); 
go 

declare @root int = 2; 

declare crs cursor for 
    with cte as (
     select id, parent_id, data 
     from test 
     where id = @root 
     union all 
     select t.id, t.parent_id, t.data 
     from test t 
      join cte c on t.parent_id = c.id) 
    select id, data from cte;  
open crs; 

declare @id int, @data varchar(max); 
fetch next from crs into @id, @data; 
while @@fetch_status = 0 
begin 
    print @data; 
    fetch next from crs into @id, @data; 
end 

close crs; 
deallocate crs; 

但是大多数情况下,递归CTE可以完全消除对游标的需要。

你需要的是2个游标 - 一个用于父级,另一个用于子级。确保子游标在LOOP内部不被DECLARED声明,如果你声明在外部,它将不起作用。

如:

DECLARE @value VARCHAR(20); 
DECLARE @someKey NUMERIC(19,0); 

DECLARE main_curs 
CURSOR FOR SELECT value FROM someTable where key = @someKey; 

SET @someKey = 12345; 

OPEN main_curs 
FETCH NEXT FROM main_curs INTO @value; 
while @@FETCH_STATUS = 0 
BEGIN 

DECLARE CHILD_CURS CURSOR FOR SELECT VALUE2 FROM CHILDTABLE WHERE [email protected]; 
open child_curs 
fetch next from child_curs into @x,@y 

close child_curs 
deallocate child_curs 

FETCH NEXT FROM main_curs INTO @value; 
END 

CLOSE main_curs 
DEALLOCATE main_curs 
+0

这适用于@someKey的单个值,但正如Karl Hoaglund在关于此解决方案的另一个答案的评论中所述,“我尝试了这一步,发现如果关闭游标,游标的内容不会更改,请更改值在变量中,然后重新打开游标,看起来游标定义是在DECLARE语句中“编译”的,我来到这里是为了找到一个解除游标的解决方法。这使得我们三个人在寻找答案,包括在OP中的更新中的Ascalonian。 – 2016-12-12 14:48:58

+0

@Rob - 根据John Kane发布的有关嵌套游标的Microsoft帮助文档链接中的信息,您的回复有些不准确。尽管关闭子游标确实不会改变内容,但释放子游标将强制SQL在循环的下一次迭代中重新创建游标(从而更改内容)。所以卡尔是误导性的,这篇文章也有误导性。您可以通过在循环内释放内部子游标来执行预期的操作,而不仅仅是关闭它。所以这个例子是准确的。 – Mitselplik 2017-02-13 22:22:59

在另外一个地方,一个人在使用存储过程(编译SQL,而不是一个特设的脚本)建议,但不工作的。这里的另一个MWE,显示问题相当明确:

/* Should print: 
dbNamein=master dbNameout=master 
dbNamein=model dbNameout=model 
dbNamein=msdb dbNameout=msdb 
*/ 
create procedure [TestParamsWithOpenCursorStmt] 
as 
begin 

    declare @dbNameIn [nvarchar](255) = N'tempdb', 
     @dbNameOut [nvarchar](255), 
     @fs [int]; 
    declare dbNames cursor for 
     select db.[name] from [master].[sys].[databases] db 
     where db.[name] = @dbNameIn; 
    while (@dbNameIn != N'msdb') begin 
     if @dbNameIn = N'tempdb' 
     set @dbNameIn = N'master' 
     else if @dbNameIn = N'master' 
     set @dbNameIn = N'model' 
     else if @dbNameIn = N'model' 
     set @dbNameIn = N'msdb'; 
     open dbNames; 
     fetch next from dbNames into @dbNameOut; 
     set @fs = @@fetch_status; 
     if @fs != 0 continue; 
     raiserror (N'dbNamein=%s dbNameout=%s', 0, 0, @dbNameIn, @dbNameOut) with nowait; 
     close dbNames; 
    end; 
    deallocate dbNames; 

end; 
go 

execute [TestParamsWithOpenCursorStmt]; 

看来,变量(和它的时间价值)被绑定到“申报...光标”,而不是打开的游标。

下面介绍如何使用'EXEC()'函数声明带有动态SQL的游标。令人惊讶的是,这确实奏效。例如:

DECLARE @QuotedDatabase NVARCHAR(128) = QUOTENAME('ReportServer') 
DECLARE @ObjectID INT = 389576426 
DECLARE @ColumnName NVARCHAR(128) 
DECLARE @ColumnType NVARCHAR(128) 
DECLARE @DeclareColumnCursor NVARCHAR(4000) 
SET @DeclareColumnCursor = ' 
    DECLARE ColumnCursor CURSOR READ_ONLY FORWARD_ONLY FOR 
    SELECT c.Name, t.Name 
    FROM ' + @QuotedDatabase + '.sys.columns c 
    INNER JOIN ' + @QuotedDatabase + '.sys.types t 
    ON c.user_type_id = t.user_type_id 
    WHERE c.object_id = ' + CAST(@ObjectID AS NVARCHAR) + ' 
    ORDER BY column_id' 
EXEC(@DeclareColumnCursor) 
OPEN ColumnCursor 
FETCH NEXT FROM ColumnCursor INTO @ColumnName, @ColumnType 
PRINT @ColumnName + ',' + @ColumnType 
CLOSE ColumnCursor 
DEALLOCATE ColumnCursor