Как можно вызвать хранимую процедуру для каждой строки в таблице, где столбцы строки являются входными параметрами для sp без с помощью курсора?
SQL Call Сохраненная процедура для каждой строки без использования курсора
Ответ 1
В общем, я всегда ищу подход на основе набора (иногда за счет изменения схемы).
Однако этот фрагмент имеет свое место.
-- 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
Ответ 2
Вы можете сделать что-то вроде этого: закажите свою таблицу, например. CustomerID (используя таблицу примеров AdventureWorks Sales.Customer
) и перебирайте эти клиенты с помощью цикла 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
Это должно работать с любой таблицей, если вы можете определить какой-то элемент ORDER BY
для некоторого столбца.
Ответ 3
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)
Хорошо, поэтому я никогда не ставил бы такой код в производство, но он удовлетворяет вашим требованиям.
Ответ 4
Ответ Marc хороший (я бы прокомментировал его, если бы я мог разобраться, как!)
Просто подумал, что я бы указал, что лучше изменить цикл, так что SELECT
существует только один раз (в реальном случае, когда мне это нужно было, SELECT
был довольно сложным, а его запись дважды рискованное обслуживание).
-- 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
Ответ 5
Если вы можете включить хранимую процедуру в функцию, которая возвращает таблицу, то вы можете использовать кросс-заявку.
Например, скажем, у вас есть таблица клиентов, и вы хотите вычислить сумму своих заказов, вы должны создать функцию, которая приняла идентификатор CustomerID и вернула сумму.
И вы могли бы сделать это:
SELECT CustomerID, CustomerSum.Total
FROM Customers
CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum
Если функция будет выглядеть так:
CREATE FUNCTION ComputeCustomerTotal
(
@CustomerID INT
)
RETURNS TABLE
AS
RETURN
(
SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = @CustomerID
)
Очевидно, что пример выше может быть выполнен без определенной пользователем функции в одном запросе.
Недостатком является то, что функции очень ограничены - многие функции хранимой процедуры недоступны в пользовательской функции, и преобразование хранимой процедуры в функцию не всегда работает.
Ответ 6
Для SQL Server 2005 вы можете сделать это с помощью CROSS APPLY и табличной функции.
Просто для ясности я имею в виду те случаи, когда хранимая процедура может быть преобразована в функцию с табличной оценкой.
Ответ 7
Это вариант решения n3rds выше. Не требуется сортировка с использованием ORDER BY, поскольку используется MIN().
Помните, что CustomerID (или любой другой числовой столбец, который вы используете для достижения прогресса) должен иметь уникальное ограничение. Кроме того, чтобы сделать это как можно быстрее, идентификатор CustomerID должен быть проиндексирован.
-- 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
Я использую этот подход для некоторых varchars, которые мне нужно просмотреть, сначала помещая их во временную таблицу, чтобы дать им идентификатор.
Ответ 8
Я бы использовал принятый ответ, но другая возможность - использовать табличную переменную для хранения нумерованного набора значений (в данном случае только поля идентификатора таблицы) и прокрутки через Row Number с помощью JOIN для таблицу, чтобы получить все необходимое для действия в цикле.
DECLARE @RowCnt int; SET @RowCnt = 0 -- Loop Counter
-- Use a table variable to hold numbered rows containg MyTable 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 [email protected]
PRINT Convert(NVarChar(10),@RowCnt) +' '+ @Code +' '+ @Name
END
Ответ 9
Если вы не используете курсор, я думаю, вам придется сделать это извне (получить таблицу, а затем запустить для каждого оператора и каждый раз вызвать sp) это то же самое, что использовать курсор, но только за пределами SQL. Почему вы не используете курсор?
Ответ 10
Я обычно делаю это так, когда это довольно много строк:
- Выберите все параметры sproc в наборе данных с помощью SQL Management Studio
- Щелкните правой кнопкой мыши → Копировать
- Вставить в excel
- Создайте однострочные sql-операторы с формулой, например '=' EXEC schema.mysproc @param = "и A2 'в новом столбце excel. (Где A2 - ваш столбец excel, содержащий параметр).
- Скопировать список инструкций excel в новый запрос в SQL Management Studio и выполнить.
- Готово.
(В более крупных наборах данных я бы использовал одно из упомянутых выше решений).
Ответ 11
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;
Ответ 12
Это вариант ответа, который уже предоставлен, но должен быть лучше, потому что он не требует ORDER BY, COUNT или MIN/MAX. Единственным недостатком этого подхода является то, что вам нужно создать временную таблицу для хранения всех идентификаторов (предполагается, что у вас есть пробелы в вашем списке CustomerID).
Тем не менее, я согласен с @Mark Powell, хотя, вообще говоря, подход на основе набора должен все же быть лучше.
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
Ответ 13
В случае важности заказа
--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
[email protected] = 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
Ответ 14
Лучшее решение для этого -
- Копировать/предыдущий код хранимой процедуры
- Соедините этот код с таблицей, для которой вы хотите запустить ее снова (для каждой строки)
Это вы получили чистый вывод в формате таблицы. Хотя если вы запускаете SP для каждой строки, вы получаете отдельный результат запроса для каждой итерации, которая является уродливой.
Ответ 15
У меня был какой-то производственный код, который мог обрабатывать только 20 сотрудников за раз, ниже - рамки для кода. Я только что скопировал производственный код и удалил материал ниже.
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
Ответ 16
Мне нравится делать что-то похожее на это (хотя оно по-прежнему очень похоже на использование курсора)
[код]
-- 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
[/код]
Обратите внимание, что вам не нужен идентификатор или столбец isIterated в таблице temp/variable, я просто предпочитаю делать это таким образом, поэтому мне не нужно удалять верхнюю запись из коллекции, поскольку я повторяю ее цикл.