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


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.