tipos recorrer procedimientos example ejemplos cursores anidados almacenados actualizar sql sql-server stored-procedures cursor

recorrer - select cursor sql server



Procedimiento de llamada almacenada de SQL para cada fila sin usar un cursor (16)

¿Cómo se puede llamar a un procedimiento almacenado para cada fila en una tabla, donde las columnas de una fila son parámetros de entrada para la sp sin usar un Cursor?


DELIMITER //

CREATE PROCEDURE setFakeUsers (OUT output VARCHAR(100)) BEGIN -- define the last customer ID handled DECLARE LastGameID INT; DECLARE CurrentGameID INT; DECLARE userID INT; SET @LastGameID = 0; -- define the customer ID to be handled now SET @userID = 0; -- select the next game to handle SELECT @CurrentGameID = id FROM online_games WHERE id > LastGameID ORDER BY id LIMIT 0,1; -- as long as we have customers...... WHILE (@CurrentGameID IS NOT NULL) DO -- call your sproc -- set the last customer handled to the one we just handled SET @LastGameID = @CurrentGameID; SET @CurrentGameID = NULL; -- select the random bot SELECT @userID = userID FROM users WHERE FIND_IN_SET(''bot'',baseInfo) ORDER BY RAND() LIMIT 0,1; -- update the game UPDATE online_games SET userID = @userID WHERE id = @CurrentGameID; -- select the next game to handle SELECT @CurrentGameID = id FROM online_games WHERE id > LastGameID ORDER BY id LIMIT 0,1; END WHILE; SET output = "done"; END;// CALL setFakeUsers(@status); SELECT @status;


En caso de que el orden sea importante

--declare counter DECLARE @CurrentRowNum BIGINT = 0; --Iterate over all rows in [DataTable] WHILE (1 = 1) BEGIN --Get next row by number of row SELECT TOP 1 @CurrentRowNum = extendedData.RowNum --here also you can store another values --for following usage --@MyVariable = extendedData.Value FROM ( SELECT data.* ,ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RowNum FROM [DataTable] data ) extendedData WHERE extendedData.RowNum > @CurrentRowNum ORDER BY extendedData.RowNum --Exit loop if no more rows IF @@ROWCOUNT = 0 BREAK; --call your sproc --EXEC dbo.YOURSPROC @MyVariable END


En términos generales, siempre busco un enfoque basado en conjuntos (a veces a expensas de cambiar el esquema).

Sin embargo, este fragmento sí tiene su lugar.

-- Declare & init (2008 syntax) DECLARE @CustomerID INT = 0 -- Iterate over all customers WHILE (1 = 1) BEGIN -- Get next customerId SELECT TOP 1 @CustomerID = CustomerID FROM Sales.Customer WHERE CustomerID > @CustomerId ORDER BY CustomerID -- Exit loop if no more customers IF @@ROWCOUNT = 0 BREAK; -- call your sproc EXEC dbo.YOURSPROC @CustomerId END


Esta es una variación de la solución n3rds anterior. No se necesita ordenar usando ORDER BY, ya que se usa MIN ().

Recuerde que CustomerID (o cualquier otra columna numérica que use para el progreso) debe tener una restricción única. Además, para que sea lo más rápido posible, CustomerID debe estar indexado.

-- Declare & init DECLARE @CustomerID INT = (SELECT MIN(CustomerID) FROM Sales.Customer); -- First ID DECLARE @Data1 VARCHAR(200); DECLARE @Data2 VARCHAR(200); -- Iterate over all customers WHILE @CustomerID IS NOT NULL BEGIN -- Get data based on ID SELECT @Data1 = Data1, @Data2 = Data2 FROM Sales.Customer WHERE [ID] = @CustomerID ; -- call your sproc EXEC dbo.YOURSPROC @Data1, @Data2 -- Get next customerId SELECT @CustomerID = MIN(CustomerID) FROM Sales.Customer WHERE CustomerID > @CustomerId END

Utilizo este enfoque en algunos varchars que necesito revisar, poniéndolos en una tabla temporal primero, para darles una identificación.


Esta es una variación de las respuestas ya proporcionadas, pero debería tener un mejor rendimiento porque no requiere ORDER BY, COUNT o MIN / MAX. La única desventaja de este enfoque es que debe crear una tabla temporal para contener todos los Id. (Se supone que tiene lagunas en la lista de IdClientes).

Dicho esto, estoy de acuerdo con @Mark Powell en que, en términos generales, un enfoque basado en un conjunto debería ser aún mejor.

DECLARE @tmp table (Id INT IDENTITY(1,1) PRIMARY KEY NOT NULL, CustomerID INT NOT NULL) DECLARE @CustomerId INT DECLARE @Id INT = 0 INSERT INTO @tmp SELECT CustomerId FROM Sales.Customer WHILE (1=1) BEGIN SELECT @CustomerId = CustomerId, @Id = Id FROM @tmp WHERE Id = @Id + 1 IF @@rowcount = 0 BREAK; -- call your sproc EXEC dbo.YOURSPROC @CustomerId; END


La respuesta de Marc es buena (¡comentaría si pudiera averiguar cómo hacerlo!)
Solo pensé en señalar que podría ser mejor cambiar el ciclo para que SELECT solo exista una vez (en un caso real en el que necesitaba hacer esto, el SELECT era bastante complejo, y escribirlo dos veces era un tema de mantenimiento arriesgado) .

-- define the last customer ID handled DECLARE @LastCustomerID INT SET @LastCustomerID = 0 -- define the customer ID to be handled now DECLARE @CustomerIDToHandle INT SET @CustomerIDToHandle = 1 -- as long as we have customers...... WHILE @LastCustomerID <> @CustomerIDToHandle BEGIN SET @LastCustomerId = @CustomerIDToHandle -- select the next customer to handle SELECT TOP 1 @CustomerIDToHandle = CustomerID FROM Sales.Customer WHERE CustomerID > @LastCustomerId ORDER BY CustomerID IF @CustomerIDToHandle <> @LastCustomerID BEGIN -- call your sproc END END


Me gusta hacer algo similar a esto (aunque sigue siendo muy similar al uso de un cursor)

[código]

-- Table variable to hold list of things that need looping DECLARE @holdStuff TABLE ( id INT IDENTITY(1,1) , isIterated BIT DEFAULT 0 , someInt INT , someBool BIT , otherStuff VARCHAR(200) ) -- Populate your @holdStuff with... stuff INSERT INTO @holdStuff ( someInt , someBool , otherStuff ) SELECT 1 , -- someInt - int 1 , -- someBool - bit ''I like turtles'' -- otherStuff - varchar(200) UNION ALL SELECT 42 , -- someInt - int 0 , -- someBool - bit ''something profound'' -- otherStuff - varchar(200) -- Loop tracking variables DECLARE @tableCount INT SET @tableCount = (SELECT COUNT(1) FROM [@holdStuff]) DECLARE @loopCount INT SET @loopCount = 1 -- While loop variables DECLARE @id INT DECLARE @someInt INT DECLARE @someBool BIT DECLARE @otherStuff VARCHAR(200) -- Loop through item in @holdStuff WHILE (@loopCount <= @tableCount) BEGIN -- Increment the loopCount variable SET @loopCount = @loopCount + 1 -- Grab the top unprocessed record SELECT TOP 1 @id = id , @someInt = someInt , @someBool = someBool , @otherStuff = otherStuff FROM @holdStuff WHERE isIterated = 0 -- Update the grabbed record to be iterated UPDATE @holdAccounts SET isIterated = 1 WHERE id = @id -- Execute your stored procedure EXEC someRandomSp @someInt, @someBool, @otherStuff END

[/código]

Tenga en cuenta que no necesita la identidad o la columna isIterated en su tabla de temperatura / variable, solo prefiero hacerlo de esta forma para no tener que eliminar el registro superior de la colección cuando repito el ciclo.


Normalmente lo hago de esta manera cuando hay bastantes filas:

  1. Seleccione todos los parámetros de sproc en un conjunto de datos con SQL Management Studio
  2. Haga clic con el botón derecho -> Copiar
  3. Pegar para sobresalir
  4. Cree sentencias sql de una sola fila con una fórmula como ''= "EXEC schema.mysproc @ param =" & A2'' en una nueva columna de Excel. (Donde A2 es su columna de Excel que contiene el parámetro)
  5. Copie la lista de declaraciones de Excel en una nueva consulta en SQL Management Studio y ejecútelo.
  6. Hecho.

(En conjuntos de datos más grandes, usaría una de las soluciones mencionadas anteriormente).


Para SQL Server 2005 en adelante, puede hacer esto con CROSS APPLY y una función con valores de tabla.

Solo por claridad, me refiero a aquellos casos en los que el procedimiento almacenado se puede convertir en una función con valores de tabla.


Podría hacer algo como esto: pida su tabla, por ejemplo, CustomerID (utilizando la tabla de muestra AdventureWorks Sales.Customer ), e itere sobre los clientes que utilizan un bucle WHILE:

-- define the last customer ID handled DECLARE @LastCustomerID INT SET @LastCustomerID = 0 -- define the customer ID to be handled now DECLARE @CustomerIDToHandle INT -- select the next customer to handle SELECT TOP 1 @CustomerIDToHandle = CustomerID FROM Sales.Customer WHERE CustomerID > @LastCustomerID ORDER BY CustomerID -- as long as we have customers...... WHILE @CustomerIDToHandle IS NOT NULL BEGIN -- call your sproc -- set the last customer handled to the one we just handled SET @LastCustomerID = @CustomerIDToHandle SET @CustomerIDToHandle = NULL -- select the next customer to handle SELECT TOP 1 @CustomerIDToHandle = CustomerID FROM Sales.Customer WHERE CustomerID > @LastCustomerID ORDER BY CustomerID END

Eso debería funcionar con cualquier tabla siempre que pueda definir algún tipo de ORDER BY en alguna columna.


Si no usa el cursor, creo que tendrá que hacerlo externamente (obtenga la tabla, y luego ejecútelo para cada instrucción y cada vez llame a la sp) Es lo mismo que usar un cursor, pero solo afuera SQL. ¿Por qué no usarás un cursor?


Si puede convertir el procedimiento almacenado en una función que devuelve una tabla, puede usar la aplicación cruzada.

Por ejemplo, supongamos que tiene una tabla de clientes y desea calcular la suma de sus pedidos, crearía una función que tomara un ID de cliente y devolviera la suma.

Y podrías hacer esto:

SELECT CustomerID, CustomerSum.Total FROM Customers CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum

Dónde se vería la función:

CREATE FUNCTION ComputeCustomerTotal ( @CustomerID INT ) RETURNS TABLE AS RETURN ( SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = @CustomerID )

Obviamente, el ejemplo anterior podría realizarse sin una función definida por el usuario en una sola consulta.

El inconveniente es que las funciones son muy limitadas: muchas de las funciones de un procedimiento almacenado no están disponibles en una función definida por el usuario, y la conversión de un procedimiento almacenado a una función no siempre funciona.


Tenía un código de producción que solo podía manejar 20 empleados a la vez, a continuación se muestra el marco para el código. Acabo de copiar el código de producción y eliminé cosas a continuación.

ALTER procedure GetEmployees @ClientId varchar(50) as begin declare @EEList table (employeeId varchar(50)); declare @EE20 table (employeeId varchar(50)); insert into @EEList select employeeId from Employee where (ClientId = @ClientId); -- Do 20 at a time while (select count(*) from @EEList) > 0 BEGIN insert into @EE20 select top 20 employeeId from @EEList; -- Call sp here delete @EEList where employeeId in (select employeeId from @EE20) delete @EE20; END; RETURN end


Una mejor solución para esto es

  1. Código de copia / pasado del procedimiento almacenado
  2. Une ese código con la tabla para la cual deseas ejecutarlo de nuevo (para cada fila)

De esta forma obtienes un resultado limpio con formato de tabla. Mientras que si ejecuta SP para cada fila, obtiene un resultado de consulta separado para cada iteración que es feo.


Usaría la respuesta aceptada, pero otra posibilidad es usar una variable de tabla para contener un conjunto de valores numerados (en este caso solo el campo ID de una tabla) y recorrerlos por Número de fila con un JOIN a la tabla para recupera lo que necesites para la acción dentro del ciclo.

DECLARE @RowCnt int; SET @RowCnt = 0 -- Loop Counter -- Use a table variable to hold numbered rows containg MyTable''s ID values DECLARE @tblLoop TABLE (RowNum int IDENTITY (1, 1) Primary key NOT NULL, ID INT ) INSERT INTO @tblLoop (ID) SELECT ID FROM MyTable -- Vars to use within the loop DECLARE @Code NVarChar(10); DECLARE @Name NVarChar(100); WHILE @RowCnt < (SELECT COUNT(RowNum) FROM @tblLoop) BEGIN SET @RowCnt = @RowCnt + 1 -- Do what you want here with the data stored in tblLoop for the given RowNum SELECT @Code=Code, @Name=LongName FROM MyTable INNER JOIN @tblLoop tL on MyTable.ID=tL.ID WHERE tl.RowNum=@RowCnt PRINT Convert(NVarChar(10),@RowCnt) +'' ''+ @Code +'' ''+ @Name END


DECLARE @SQL varchar(max)='''' -- MyTable has fields fld1 & fld2 Select @SQL = @SQL + ''exec myproc '' + convert(varchar(10),fld1) + '','' + convert(varchar(10),fld2) + '';'' From MyTable EXEC (@SQL)

Ok, entonces nunca pondré ese código en producción, pero cumple sus requisitos.