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


206

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

Как бы я написал запрос для этого?


5
Вам нужно указать, что такое СУБД - ответ будет отличаться для SQL Server, Oracle, MySql и т. Д.
— Gary.Ray

5
Скорее всего, вам вообще не нужна хранимая процедура. Можете ли вы описать, что именно делает хранимая процедура? Может быть, весь процесс может быть выражен в виде одного оператора обновления. Обычно следует избегать паттерна «делать один раз для каждой записи», если это возможно.
— Томалак

Какую базу данных вы используете?
— SO User

1
Вы должны прочитать эту статью ... В пункте 2 говорится, что НЕ используйте курсоры codeproject.com/KB/database/sqldodont.aspx...mind Я также против преждевременной оптимизации.
— Майкл Превецки

7
@MichaelPrewecki: Если вы будете читать дальше в этой плохо написанной статье, вы увидите, что пункт 10 - «НЕ используйте серверные курсоры, если вы не знаете, что делаете». Я думаю, что это случай «я знаю, что я делаю».
— Гейб

Ответы:


246

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

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; более подробно в этом вопросе

ДОБАВЛЕНИЕ 2: если вы будете обрабатывать не только несколько записей, сначала перетащите их в временную таблицу и наведите курсор на временную таблицу; это предотвратит эскалацию SQL в блокировки таблиц и ускорит работу

ДОБАВЛЕНИЕ 3: и, конечно, если вы можете встроить то, что ваша хранимая процедура делает с каждым идентификатором пользователя, и выполнить все это как отдельный оператор обновления SQL, это было бы оптимальным


21
Вы пропустили 'open cur' после объявления - это давало мне ошибки 'курсор не открыт'. У меня нет представителя, чтобы сделать редактирование.
— Фиона - myaccessible.website

5
Вы можете поблагодарить людей, проголосовав за их комментарии. Кто знает, может быть, таким образом у них будет представитель, который сделает редактирование, в следующий раз! :-)
— Робино

Убедитесь, что вы проверили свои индексы в предложениях JOINS и WHERE в полях, используемых в вашей хранимой процедуре. Я резко ускорил вызов своего SP в цикле после добавления соответствующих индексов.
— Матфея

1
Спасибо за напоминание об использовании временной таблицы, чтобы избежать потенциальных проблем блокировки, вызванных длительным выполнением.
— Тони

Иногда хранимая процедура слишком велика или сложна, чтобы ее можно было встроить без риска появления ошибок. Там, где производительность не является конечным приоритетом, выполнение SP в цикле курсора часто является наиболее практичным выбором.
— Suncat2000

55

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

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

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

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

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


3
+1 за очень хороший обходной путь, при условии, что вы контролируете ребенка
— Стивен А. Лоу

немного подумав, это решение намного превосходит!
— encc

7
Операции на основе множеств всегда предпочтительнее. Однако имейте в виду, что модификация SP не всегда возможна - подумайте, что поставщик предоставил решения. Некоторые пользователи могут даже не иметь видимости, оставляя только параметры курсора или цикла. В моем магазине наши разработчики могут видеть все, но есть много препятствий, которые нужно прояснить, если решение построено вне приложения поставщика из-за триггеров, вложенных процедур, количества записей, которыми манипулируют и т. Д. Во многих случаях это лучший вариант из-за Сложность приложения заключается в простом наведении курсора на записи.
— Стив

11

Что-то вроде этой замены понадобится для ваших таблиц и имен полей.

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

2
в то время как петли медленнее, чем курсоры
— Стивен А. Лоу

SQL-конструкция объявления оператора или оператор не поддерживается (??)
— MetaGuru

9

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

declare @cadena varchar(max) = ''
select @cadena = @cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(@cadena);

6

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

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

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

Смотрите http://www.sqlteam.com/article/user-defined-functions, чтобы узнать больше

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

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


1
OP: «хранимая процедура, которая изменяет пользовательские данные определенным образом» MSDN : Пользовательские функции не могут использоваться для выполнения действий, которые изменяют состояние базы данных. Однако SQLSVR 2014, похоже, не имеет проблем с этим
— Джонни 5

6

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

Как уже упоминалось ранее, курсор является последним средством. Главным образом потому, что он использует много ресурсов, блокирует проблемы и может быть признаком того, что вы просто не понимаете, как правильно использовать 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;

3

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

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

Я надеюсь, что это поможет кому-то...

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.