Использование курсора с динамическим SQL в хранимой процедуре
-
20-08-2019 - |
Вопрос
У меня есть динамический оператор SQL, который я создал в хранимой процедуре.Мне нужно выполнить итерацию по результатам с помощью курсора.Мне трудно разобраться в правильном синтаксисе.Вот что я делаю.
SELECT @SQLStatement = 'SELECT userId FROM users'
DECLARE @UserId
DECLARE users_cursor CURSOR FOR
EXECUTE @SQLStatment --Fails here. Doesn't like this
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC asp_DoSomethingStoredProc @UserId
END
CLOSE users_cursor
DEALLOCATE users_cursor
Каков правильный способ сделать это?
Решение
Курсор будет принимать только инструкцию select, поэтому, если SQL действительно должен быть динамическим, сделайте declare cursor частью инструкции, которую вы выполняете.Для того чтобы нижеприведенное сработало, ваш сервер должен будет использовать глобальные курсоры.
Declare @UserID varchar(100)
declare @sqlstatement nvarchar(4000)
--move declare cursor into sql to be executed
set @sqlstatement = 'Declare users_cursor CURSOR FOR SELECT userId FROM users'
exec sp_executesql @sqlstatement
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId
WHILE @@FETCH_STATUS = 0
BEGIN
Print @UserID
EXEC asp_DoSomethingStoredProc @UserId
FETCH NEXT FROM users_cursor --have to fetch again within loop
INTO @UserId
END
CLOSE users_cursor
DEALLOCATE users_cursor
Если вам нужно избежать использования глобальных курсоров, вы также можете вставить результаты вашего динамического SQL во временную таблицу, а затем использовать эту таблицу для заполнения вашего курсора.
Declare @UserID varchar(100)
create table #users (UserID varchar(100))
declare @sqlstatement nvarchar(4000)
set @sqlstatement = 'Insert into #users (userID) SELECT userId FROM users'
exec(@sqlstatement)
declare users_cursor cursor for Select UserId from #Users
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC asp_DoSomethingStoredProc @UserId
FETCH NEXT FROM users_cursor
INTO @UserId
END
CLOSE users_cursor
DEALLOCATE users_cursor
drop table #users
Другие советы
Этот код является очень хорошим примером для динамического столбца с курсором, поскольку вы не можете использовать '+' в @STATEMENT:
ALTER PROCEDURE dbo.spTEST
AS
SET NOCOUNT ON
DECLARE @query NVARCHAR(4000) = N'' --DATA FILTER
DECLARE @inputList NVARCHAR(4000) = ''
DECLARE @field sysname = '' --COLUMN NAME
DECLARE @my_cur CURSOR
EXECUTE SP_EXECUTESQL
N'SET @my_cur = CURSOR FAST_FORWARD FOR
SELECT
CASE @field
WHEN ''fn'' then fn
WHEN ''n_family_name'' then n_family_name
END
FROM
dbo.vCard
WHERE
CASE @field
WHEN ''fn'' then fn
WHEN ''n_family_name'' then n_family_name
END
LIKE ''%''+@query+''%'';
OPEN @my_cur;',
N'@field sysname, @query NVARCHAR(4000), @my_cur CURSOR OUTPUT',
@field = @field,
@query = @query,
@my_cur = @my_cur OUTPUT
FETCH NEXT FROM @my_cur INTO @inputList
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT @inputList
FETCH NEXT FROM @my_cur INTO @inputList
END
RETURN
Работа с нереляционной базой данных (IDMS у кого-нибудь есть?) через ODBC-соединение квалифицируется как один из тех случаев, когда курсоры и динамический SQL кажутся единственным путем.
select * from a where a=1 and b in (1,2)
ответ занимает 45 минут, в то время как переписанный для использования наборов ключей без предложения in будет выполнен менее чем за 1 секунду:
select * from a where (a=1 and b=1)
union all
select * from a where (a=1 and b=2)
Если инструкция in для столбца B содержит 1145 строк, использование курсора для создания независимых инструкций и выполнения их как динамического SQL намного быстрее, чем использование предложения in.Глупый, эй?
И да, в реляционной базе данных нет времени, когда следует использовать курсор.Я просто не могу поверить, что наткнулся на случай, когда цикл наведения курсора выполняется на несколько величин быстрее.
Во-первых, избегайте использования курсора, если это вообще возможно.Вот несколько ресурсов для искоренения этого, когда кажется, что вы не можете обойтись без:
Должно быть 15 Способов Потерять Ваши курсоры...часть 1, Введение
Построчная обработка без использования курсора
Тем не менее, возможно, вы все-таки застряли с одним из них - я недостаточно знаю из вашего вопроса, чтобы быть уверенным, что любой из них применим.Если это так, то у вас другая проблема - оператор select для вашего курсора должен быть фактический Оператор SELECT, а не оператор EXECUTE.Ты застрял.
Но посмотрите ответ от cmsjr (который пришел, пока я писал) об использовании временной таблицы.Я бы избегал глобальный курсоров даже больше, чем "простых"....
После недавнего перехода с Oracle на SQL Server (предпочтения работодателя) я заметил, что поддержка курсора в SQL Server отстает.Курсоры не всегда вредны, иногда они необходимы, иногда намного быстрее, а иногда и чище, чем пытаться настроить сложный запрос путем изменения порядка или добавления подсказок по оптимизации.Мнение "курсоры - это зло" гораздо более распространено в сообществе SQL Server.
Так что я предполагаю, что этот ответ заключается в том, чтобы переключиться на Oracle или дать MS подсказку.
- Oracle НЕМЕДЛЕННО ВЫПОЛНИТЬ в курсор
- Цикл по неявному курсору (а
for
цикл неявно определяет / открывает /закрывает курсор!)
Есть еще один пример, которым я хотел бы поделиться с вами
:D
http://www.sommarskog.se/dynamic_sql.html#cursor0
Другой вариант в SQL Server - выполнять все ваши динамические запросы к табличной переменной в сохраненной процедуре, затем использовать курсор для запроса и обработки этого.Что касается страшных дебатов о курсоре :), я видел исследования, которые показывают, что в некоторых ситуациях курсор действительно может работать быстрее, если его правильно настроить.Я сам использую их, когда требуемый запрос слишком сложен или просто не по-человечески (для меня ;) ) возможен.
этот код может быть полезен для вас.
пример использования курсора в sql server
DECLARE sampleCursor CURSOR FOR
SELECT K.Id FROM TableA K WHERE ....;
OPEN sampleCursor
FETCH NEXT FROM sampleCursor INTO @Id
WHILE @@FETCH_STATUS <> -1
BEGIN
UPDATE TableB
SET
...