Как выполнить хранимую процедуру один раз для каждой строки, возвращаемой запросом?
У меня есть хранимая процедура, которая изменяет данные пользователя определенным образом. Я передаю его user_id, и он делает это. Я хочу запустить запрос в таблице, а затем для каждого user_id я нахожу запустить хранимую процедуру один раз на этом user_id
Как бы я написал запрос для этого?
7 ответов
используйте курсор
добавление: [пример курсора MS SQL]
declare @field1 int
declare @field2 int
declare cur CURSOR LOCAL for
select field1, field2 from sometable where someotherfield is null
open cur
fetch next from cur into @field1, @field2
while @@FETCH_STATUS = 0 BEGIN
--execute your sproc on each row
exec uspYourSproc @field1, @field2
fetch next from cur into @field1, @field2
END
close cur
deallocate cur
в MS SQL,вот пример статьи
обратите внимание, что курсоры медленнее, чем операции на основе набора, но быстрее, чем ручные циклы while; подробнее в этом так вопрос
добавление 2: Если вы будете обрабатывать больше, чем просто несколько записей, сначала вытащите их в временную таблицу и запустите курсор над временной таблицей; это предотвратит SQL от эскалация в таблицы-замки и ускорить работу
добавление 3: и, конечно, если вы можете встроить все, что ваша хранимая процедура делает с каждым идентификатором пользователя, и запустить все это как один оператор обновления SQL, это было бы оптимально
попробуйте изменить свой метод, если вам нужно цикл!
в родительской хранимой процедуре создайте таблицу #temp, содержащую данные, которые необходимо обработать. Вызовите дочернюю хранимую процедуру, таблица #temp будет видна, и вы можете обработать ее, надеюсь, работая со всем набором данных и без курсора или цикла.
это действительно зависит от того, что делает эта дочерняя хранимая процедура. Если вы обновляете, вы можете "обновить от" присоединения в таблице #temp и выполните всю работу в одном операторе без цикла. То же самое можно сделать для INSERT и Delets. Если вам нужно сделать несколько обновлений с IFs, вы можете преобразовать их в несколько UPDATE FROM
с таблицей # temp и инструкциями Use CASE или условиями WHERE.
при работе в базе данных попробуйте потерять мышление цикла, это реальная утечка производительности, вызовет блокировку / блокировку и замедлит обработку. Если вы петляете везде, ваша система не будет масштабироваться Очень ну, и будет очень сложно ускорить, когда пользователи начнут жаловаться на медленное обновление.
опубликовать содержимое этой процедуры, которую вы хотите вызвать в цикле, и я ставлю 9 из 10 раз, вы можете написать ее для работы над набором строк.
что-то вроде этой замены потребуется для ваших таблиц и имен полей.
Declare @TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare @i Int, @MaxI Int, @UserID nVarchar(50)
Insert into @TableUser
Select User_ID
From Users
Where (My Criteria)
Select @MaxI = @@RowCount, @i = 1
While @i <= @MaxI
Begin
Select @UserID = UserID from @TableUsers Where MyRowCount = @i
Exec prMyStoredProc @UserID
Select
@i = @i + 1, @UserID = null
End
нельзя ли это сделать с помощью пользовательской функции для репликации того, что делает хранимая процедура?
SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition
где udfMyFunction-это функция, которую вы делаете, которая принимает идентификатор пользователя и делает все, что вам нужно сделать с ним.
посмотреть http://www.sqlteam.com/article/user-defined-functions немного больше фона
Я согласен, что курсоры действительно следует избегать, где это возможно. И это обычно возможно!
(of конечно, мой ответ предполагает, что вы заинтересованы только в получении выходных данных от SP и что вы не изменяете фактические данные. Я нахожу, что "изменяет пользовательские данные определенным образом" немного неоднозначно от исходного вопроса, поэтому я подумал, что предложу это как возможное решение. Все зависит от того, что вы делаете!)
вы можете сделать это с помощью динамического запроса.
declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);
Мне нравится динамический способ запроса Дэйва Ринкона, поскольку он не использует курсоры и мал и прост. Спасибо, Дэйв, что поделился.
но для моих нужд в Azure SQL и с "отличным" в запросе мне пришлось изменить код следующим образом:
Declare @SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines
SELECT @SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' +
convert(varchar(8),tenantid) + ';'
from Dim_Machine
where iscurrent = 1
FOR XML PATH(''))
--for debugging print the sql
print @SQL;
--execute the generated sql script
exec sp_executesql @SQL;
надеюсь, это кому-то поможет...
использовать табличную переменную или временную таблицу.
Как уже упоминалось ранее, Курсор-это крайняя мера. В основном потому, что он использует много ресурсов, блокирует проблемы и может быть признаком того, что вы просто не понимаете, как правильно использовать SQL.
(Примечание: я однажды наткнулся на решение, которое использовало курсоры для обновления строк в таблице. После некоторого изучения оказалось, что все это можно заменить одной командой обновления.)
однако, в в этом случае, когда хранимая процедура должна быть выполнена, одна SQL-команда не будет работать.
создать табличную переменную, как это (если вы работаете с большим количеством данных или не хватает памяти, использовать временная таблица вместо):
DECLARE @menus AS TABLE (
id INT IDENTITY(1,1),
parent NVARCHAR(128),
child NVARCHAR(128));
на id
важно.
заменить parent
и child
С некоторыми хорошими данными, например, соответствующими идентификаторами или всем набором данных для работы.
вставить данные в таблицу, например:
INSERT INTO @menus (parent,child)
VALUES ('Some name', 'Child name');
...
INSERT INTO @menus (parent,child)
VALUES ('Some other name', 'Some other child name');
объявить несколько переменных:
DECLARE @id INT = 1;
DECLARE @parentName NVARCHAR(128);
DECLARE @childName NVARCHAR(128);
и, наконец, создайте цикл while над данными в таблице:
WHILE @id IS NOT NULL
BEGIN
SELECT @parentName = parent,
@childName = child
FROM @menus WHERE id = @id;
EXEC myProcedure @parent=@parentName, @child=@childName;
SELECT @id = MIN(id) FROM @menus WHERE id > @id;
END
первый select извлекает данные из временной таблицы. Второй выбор обновляет @id. MIN
возвращает null, если строки не были выбраны.
альтернативным подходом является цикл, в то время как таблица имеет строки, SELECT TOP 1
и удалите выбранную строку из temp таблица.
обновление: как оказалось, мне нужна была итерация, где я тоже удаляю, так что вот она:
WHILE EXISTS(SELECT 1 FROM @menuIDs)
BEGIN
SELECT TOP 1 @menuID = menuID FROM @menuIDs;
EXEC myProcedure @menuID=@menuID;
DELETE FROM @menuIDs WHERE menuID = @menuID;
END;