Курсор T-Sql не продолжается при получении - PullRequest
2 голосов
/ 28 октября 2008

Я знаю, что курсоры не одобряются, и я стараюсь максимально избегать их использования, но могут быть некоторые законные причины для их использования. У меня есть один, и я пытаюсь использовать пару курсоров: один для основной таблицы и один для дополнительной таблицы. Курсор первичной таблицы перебирает первичную таблицу во внешнем цикле. курсор вторичной таблицы перебирает вторичную таблицу во внутреннем цикле. Проблема заключается в том, что курсор первичной таблицы, по-видимому, продолжает и сохраняет значение столбца первичного ключа [Fname] в локальной переменной @Fname, но не получает строку для соответствующего столбца внешнего ключа во вторичной таблице. Для вторичной таблицы она всегда возвращает строки, значение столбца внешнего ключа которых совпадает со значением столбца первичного ключа первая строка первичной таблицы.

Ниже приведен очень упрощенный пример того, что я хочу сделать в реальной хранимой процедуре. Имена первичной таблицы

SET NOCOUNT ON
DECLARE 
    @Fname varchar(50) -- to hold the fname column value from outer cursor loop
    ,@FK_Fname varchar(50) -- to hold the fname column value from inner cursor loop
    ,@score int
;

--prepare primary table to be iterated in the  outer loop
DECLARE @Names AS Table (Fname varchar(50))
INSERT @Names
    SELECT 'Jim' UNION
    SELECT 'Bob' UNION
    SELECT 'Sam' UNION
    SELECT 'Jo' 


--prepare secondary/detail table to be iterated in the inner loop
DECLARE @Scores AS Table (Fname varchar(50), Score int)
INSERT @Scores
    SELECT 'Jo',1 UNION
    SELECT 'Jo',5 UNION
    SELECT 'Jim',4 UNION
    SELECT 'Bob',10 UNION
    SELECT 'Bob',15 

--cursor to iterate on the primary table in the outer loop
DECLARE curNames CURSOR
FOR SELECT Fname FROM @Names


OPEN curNames
FETCH NEXT FROM curNames INTO @Fname

--cursor to iterate on the secondary table in the inner loop
DECLARE curScores CURSOR
FOR 
    SELECT FName,Score 
    FROM @Scores 
    WHERE Fname = @Fname 
 --*** NOTE: Using the primary table's column value @Fname from the outer loop

WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT 'Outer loop @Fname = ' + @Fname

    OPEN curScores
    FETCH NEXT FROM curScores INTO @FK_Fname, @Score

    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT ' FK_Fname=' + @FK_Fname + '. Score=' + STR(@Score)
        FETCH NEXT FROM curScores INTO @FK_Fname, @Score
    END
    CLOSE curScores
    FETCH NEXT FROM curNames INTO @Fname
END

DEALLOCATE curScores

CLOSE curNames
DEALLOCATE curNames

Вот что я получаю за результат. Обратите внимание, что для внешнего цикла он показывает актуальное имя Fname, но когда это имя Fname используется как @Fname для извлечения соответствующей строки из вторичной таблицы для последующих итераций, он все равно получает строки, соответствующие первому строка (Bob) первичной таблицы.

Outer loop @Fname = Bob
    FK_Fname=Bob. Score=10
    FK_Fname=Bob. Score=15
Outer loop @Fname = Jim
    FK_Fname=Bob. Score=10
    FK_Fname=Bob. Score=15
Outer loop @Fname = Jo
    FK_Fname=Bob. Score=10
    FK_Fname=Bob. Score=15
Outer loop @Fname = Sam
    FK_Fname=Bob. Score=10
    FK_Fname=Bob. Score=15

Пожалуйста, дайте мне знать, что я делаю не так. Заранее спасибо!

Ответы [ 4 ]

2 голосов
/ 28 октября 2008

Значение @fName оценивается в: DECLARE curScores CURSOR, а не в первичном цикле. Вы должны объявить, а затем освободить второй курсор в первичном цикле.

1 голос
/ 28 октября 2008

Я думаю, вы могли бы сделать это намного проще с временными таблицами, которые имеют номера строк:

create table #temp1
(
 row int identity(1,1)
 , ... 
)

Похоже, вы просите SQL вести себя как язык, который любит циклы. Это не так. Всякий раз, когда я нахожу себя пишущим цикл в SQL, я спрашиваю себя, нужно ли это делать таким образом? 7/10 раз ответ нет, вместо этого я могу сделать это с сетами.

1 голос
/ 28 октября 2008

Благодаря нескольким подсказкам я смог найти решение.

Мне пришлось ОБЪЯВЛЯТЬ и ДЕЛАКЛИРОВАТЬ вторичный курсор в первом цикле. Сначала я ненавидел это делать, так как считал, что выделение и освобождение ресурсов в цикле не было хорошей идеей, но я думаю, что нет другого способа избежать этого в этой конкретной ситуации. Теперь рабочий код выглядит примерно так:

SET NOCOUNT ON
DECLARE 
    @Fname varchar(50) -- to hold the fname column value from outer cursor loop
    ,@FK_Fname varchar(50) -- to hold the fname column value from inner cursor loop
    ,@score int
;

--prepare primary table to be iterated in the  outer loop
DECLARE @Names AS Table (Fname varchar(50))
INSERT @Names
    SELECT 'Jim' UNION
    SELECT 'Bob' UNION
    SELECT 'Sam' UNION
    SELECT 'Jo' 


--prepare secondary/detail table to be iterated in the inner loop
DECLARE @Scores AS Table (Fname varchar(50), Score int)
INSERT @Scores
    SELECT 'Jo',1 UNION
    SELECT 'Jo',5 UNION
    SELECT 'Jim',4 UNION
    SELECT 'Bob',10 UNION
    SELECT 'Bob',15 

--cursor to iterate on the primary table in the outer loop
DECLARE curNames CURSOR
FOR SELECT Fname FROM @Names


OPEN curNames
FETCH NEXT FROM curNames INTO @Fname

--cursor to iterate on the secondary table in the inner loop
DECLARE curScores CURSOR
FOR 
    SELECT FName,Score 
    FROM @Scores 
    WHERE Fname = @Fname 
 --*** NOTE: Using the primary table's column value @Fname from the outer loop

WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT 'Outer loop @Fname = ' + @Fname

    OPEN curScores
    FETCH NEXT FROM curScores INTO @FK_Fname, @Score

    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT ' FK_Fname=' + @FK_Fname + '. Score=' + STR(@Score)
        FETCH NEXT FROM curScores INTO @FK_Fname, @Score
    END
    CLOSE curScores
    FETCH NEXT FROM curNames INTO @Fname
END

DEALLOCATE curScores

CLOSE curNames
DEALLOCATE curNames

И я получаю правильные результаты:

Outer loop @Fname = Bob
    FK_Fname=Bob. Score=        10
    FK_Fname=Bob. Score=        15
Outer loop @Fname = Jim
    FK_Fname=Jim. Score=         4
Outer loop @Fname = Jo
    FK_Fname=Jo. Score=         1
    FK_Fname=Jo. Score=         5
Outer loop @Fname = Sam
0 голосов
/ 28 октября 2008

Я бы попробовал разместить

DECLARE curScores CURSOR
FOR 
    SELECT FName,Score 
    FROM @Scores 
    WHERE Fname = @Fname 

внутри первого while, поскольку вы объявляете курсор только для имени первого значения

...