Я знаю, что курсоры не одобряются, и я стараюсь по возможности избегать их использования, но могут быть некоторые законные причины для их использования. У меня есть один, и я пытаюсь использовать пару курсоров: один для основной таблицы и один для дополнительной таблицы. Курсор основной таблицы выполняет итерацию по основной таблице во внешнем цикле. курсор вторичной таблицы проходит по вторичной таблице во внутреннем цикле. Проблема в том, что курсор первичной таблицы, хотя, очевидно, продолжает и сохраняет значение столбца первичного ключа [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 для выборки соответствующей строки из вторичной таблицы для последующих итераций, он по-прежнему получает строки, соответствующие первой строка (Боб) основной таблицы.
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
Пожалуйста, дайте мне знать, что я делаю не так. Заранее спасибо!





Я бы попробовал разместить
DECLARE curScores CURSOR
FOR
SELECT FName,Score
FROM @Scores
WHERE Fname = @Fname
внутри первого while, потому что вы объявляете курсор только для первого значения имени
Значение @fName оценивается в: DECLARE curScores CURSOR, а не в основном цикле. Вы должны объявить, а затем освободить второй курсор в основном цикле.
Благодаря нескольким подсказкам я смог найти решение.
Мне пришлось ОБЪЯВИТЬ и ОТКЛЮЧИТЬ вторичный курсор в первом цикле. Сначала я ненавидел это делать, так как думал, что выделение и освобождение ресурсов в цикле было не очень хорошей идеей, но я думаю, что в данной конкретной ситуации другого способа избежать этого нет. Но рабочий код выглядит примерно так:
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
Я думаю, вы могли бы сделать это намного проще с временными таблицами с номерами строк:
create table #temp1
(
row int identity(1,1)
, ...
)
Похоже, вы действительно просите SQL вести себя как язык, который любит циклы. Это не так. Когда я пишу цикл в SQL, я спрашиваю себя, нужно ли это делать таким образом? 7/10 раз ответ отрицательный, я могу сделать это с наборами.