Usar un cursor con SQL dinámico en un procedimiento almacenado
-
20-08-2019 - |
Pregunta
Tengo una instrucción SQL dinámica que he creado en un procedimiento almacenado. Necesito iterar sobre los resultados usando un cursor. Me está costando descubrir la sintaxis correcta. Esto es lo que estoy haciendo.
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
¿Cuál es la forma correcta de hacer esto?
Solución
Un cursor solo aceptará una instrucción de selección, por lo que si el SQL realmente necesita ser dinámico, haga que el cursor de declaración forme parte de la instrucción que está ejecutando. Para que lo siguiente funcione, su servidor tendrá que usar cursores globales.
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
Si necesita evitar el uso de cursores globales, también puede insertar los resultados de su SQL dinámico en una tabla temporal y luego usar esa tabla para llenar su cursor.
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
Otros consejos
Este código es un muy buen ejemplo para una columna dinámica con un cursor, ya que no puede usar '+' en @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
Trabajar con una base de datos no relacional (¿alguien con IDMS?) a través de una conexión ODBC califica como uno de esos momentos en los que los cursores y el SQL dinámico parecen ser la única ruta.
select * from a where a=1 and b in (1,2)
tarda 45 minutos en responder mientras se reescribe para usar conjuntos de teclas sin que la cláusula in se ejecute en menos de 1 segundo:
select * from a where (a=1 and b=1)
union all
select * from a where (a=1 and b=2)
Si la declaración in para la columna B contiene 1145 filas, usar un cursor para crear declaraciones indidivudales y ejecutarlas como SQL dinámico es mucho más rápido que usar la cláusula in. Tonta, ¿eh?
Y sí, no hay tiempo en una base de datos relacional para usar los cursores. Simplemente no puedo creer que me haya encontrado con una instancia en la que un bucle de cursor es varias magnitudes más rápido.
En primer lugar, evite usar un cursor si es posible. Aquí hay algunos recursos para eliminarlo cuando parece que no puede prescindir de él:
Debe haber 15 formas de perder sus cursores ... parte 1, Introducción
Procesamiento fila por fila sin cursor
Dicho esto, sin embargo, es posible que te quedes atrapado con uno después de todo; no sé lo suficiente de tu pregunta para asegurarme de que cualquiera de los dos se aplique. Si ese es el caso, tiene un problema diferente: la instrucción de selección para su cursor debe ser una instrucción SELECCIÓN real , no una instrucción EJECUTAR. Estás atrapado.
Pero vea la respuesta de cmsjr (que apareció mientras escribía) sobre el uso de una tabla temporal. Evitaría los cursores globales incluso más que & Quot; plain & Quot; unos ....
Después de cambiar recientemente de Oracle a SQL Server (preferencia del empleador), noto que la compatibilidad con el cursor en SQL Server está retrasada. Los cursores no siempre son malos, a veces se requieren, a veces mucho más rápido y a veces son más limpios que tratar de ajustar una consulta compleja reorganizando o agregando sugerencias de optimización. Los & Quot; los cursores son malvados & Quot; la opinión es mucho más prominente en la comunidad de SQL Server.
Entonces, supongo que esta respuesta es cambiar a Oracle o darle a MS una pista.
- Oracle EJECUTE INMEDIATAMENTE en un cursor
- Recorre un cursor implícito (un bucle
for
implícitamente define / abre / cierra el cursor!)
Hay otro ejemplo que me gustaría compartir con ustedes: D http://www.sommarskog.se/dynamic_sql.html#cursor0
Otra opción en SQL Server es hacer todas sus consultas dinámicas en la variable de tabla en un proceso almacenado, luego usar un cursor para consultar y procesar eso. En cuanto al temido debate sobre el cursor :), he visto estudios que muestran que, en algunas situaciones, un cursor puede ser más rápido si está configurado correctamente. Los uso yo mismo cuando la consulta requerida es demasiado compleja, o simplemente no humanamente (para mí;)) posible.
este código puede ser útil para usted.
ejemplo de uso del cursor en el servidor sql
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
...