Как выполнить хранимую процедуру один раз для каждой строки, возвращаемой запросом? - PullRequest
186 голосов
/ 20 мая 2009

У меня есть хранимая процедура, которая изменяет пользовательские данные определенным образом. Я передаю его user_id, и он делает свое дело. Я хочу выполнить запрос к таблице, а затем для каждого user_id, который я нахожу, запустить хранимую процедуру один раз для этого user_id

Как мне написать запрос для этого?

Ответы [ 7 ]

226 голосов
/ 20 мая 2009

использовать курсор

ADDENDUM: [пример курсора 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; подробнее в этом вопросе SO

ADDENDUM 2: если вы будете обрабатывать не только несколько записей, сначала перетащите их в временную таблицу и наведите курсор на временную таблицу; это предотвратит эскалацию SQL в блокировки таблиц и ускорит работу

ADDENDUM 3: и, конечно, если вы можете встроить то, что ваша хранимая процедура делает с каждым идентификатором пользователя, и запустить все это как один оператор обновления SQL, это будет оптимально

53 голосов
/ 20 мая 2009

попробуйте изменить свой метод, если вам нужно цикл!

в родительской хранимой процедуре создайте таблицу #temp, содержащую данные, которые необходимо обработать. Вызовите дочернюю хранимую процедуру, таблица #temp будет видна, и вы сможете ее обработать, надеясь работать со всем набором данных и без курсора или цикла.

это действительно зависит от того, что делает эта дочерняя хранимая процедура. Если вы ОБНОВЛЯЕТЕСЬ, вы можете «обновлять» из объединения в таблицу #temp и выполнять всю работу в одном операторе без цикла. То же самое можно сделать для INSERT и DELETE. Если вам нужно сделать несколько обновлений с IF, вы можете преобразовать их в несколько UPDATE FROM с помощью таблицы #temp и использовать операторы CASE или условия WHERE.

При работе с базой данных старайтесь не думать о циклах, это приводит к снижению производительности, вызывает блокировку / блокировку и замедляет обработку. Если вы будете выполнять циклы везде, ваша система будет плохо масштабироваться, и будет очень сложно ускорить ее, когда пользователи начнут жаловаться на медленное обновление.

Опубликуйте содержание этой процедуры, которую вы хотите вызвать, в цикле, и я поставлю 9 из 10 раз, вы можете написать ее для работы с набором строк.

9 голосов
/ 20 мая 2009

Для ваших таблиц и имен полей потребуется что-то подобное.

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
7 голосов
/ 08 февраля 2017

Вы можете сделать это с помощью динамического запроса.

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);
6 голосов
/ 24 марта 2010

Разве это нельзя сделать с помощью пользовательской функции для репликации того, что делает ваша хранимая процедура?

SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition

где udfMyFunction - это созданная вами функция, которая принимает идентификатор пользователя и выполняет с ним все, что вам нужно.

См. http://www.sqlteam.com/article/user-defined-functions для получения дополнительной информации

Я согласен, что курсоров действительно следует избегать, где это возможно. И это обычно возможно!

(конечно, мой ответ предполагает, что вы заинтересованы только в получении выходных данных от SP и что вы не изменяете фактические данные. Я считаю, что "пользовательские данные изменяются определенным образом", немного неоднозначно из Оригинальный вопрос, поэтому подумал, что я бы предложил это как возможное решение. Совершенно зависит от того, что вы делаете!)

5 голосов
/ 05 июня 2018

Используйте переменную таблицы или временную таблицу.

Как уже упоминалось ранее, курсор является последним средством. Главным образом потому, что он использует много ресурсов, блокирует проблемы и может быть признаком того, что вы просто не понимаете, как правильно использовать SQL.

Примечание: однажды я столкнулся с решением, использующим курсоры для обновления строки в таблице. После тщательного изучения выяснилось, что все это можно заменить одной командой UPDATE. Однако в этом случае где хранимая процедура должна быть выполнена, одна 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

Первый выбор извлекает данные из временной таблицы. Второй выбор обновляет @id. MIN возвращает ноль, если строки не были выбраны.

Альтернативный подход состоит в том, чтобы выполнить цикл, пока в таблице есть строки, SELECT TOP 1 и удалить выбранную строку из временной таблицы:

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;
2 голосов
/ 29 августа 2017

Мне нравится способ динамических запросов Дейва Ринкона, так как он не использует курсоры, он небольшой и простой. Спасибо, Дейв, за то, что поделился.

Но для моих нужд в 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;

Надеюсь, это кому-нибудь поможет ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...