Можно ли выполнить хранимую процедуру над набором без использования курсора? - PullRequest
16 голосов
/ 25 января 2009

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

SELECT EXEC dbo.Sproc @Param1 = Table1.id<br/> FROM Table1

Я использую T-SQL в SQL Server 2005. Я думаю, что это возможно при использовании функции, но я бы хотел использовать хранимую процедуру, если это возможно (стандарты компании)

Ответы [ 7 ]

17 голосов
/ 25 января 2009

Да. Если у вас есть столбец, который вы можете использовать в таблице, чтобы отметить обработанные, вы можете использовать WHILE EXISTS:

DECLARE @Id int
WHILE EXISTS(SELECT * FROM Table1 WHERE Processed='N')
BEGIN
 SELECT Top 1 @Id = id from Table1 WHERE Procesed='N'
 EXEC dbo.Sproc @Id
 UPDATE Table1 SET Processed = 'Y' WHERE Id = @Id
END

В качестве альтернативы, скопируйте идентификаторы во временную таблицу или переменную таблицы и удалите, когда закончите:

DECLARE @HoldTable table (Id int PRIMARY KEY)
DECLARE @Id int
INSERT INTO @HoldTable SELECT Id FROM Table1
WHILE EXISTS(SELECT * FROM @HoldTable)
BEGIN
 SELECT @Id = id from @HoldTable
 EXEC dbo.Sproc @Id
 DELETE FROM @HoldTable where Id = @Id
END
14 голосов
/ 26 января 2009

Если вы используете только SQL Server 2005 или новее, не заботитесь о обратной совместимости и можете преобразовать свой код в пользовательскую функцию (а не в сохраненный процесс), тогда вы можете использовать новый «CROSS». APPLY ", который использует синтаксис, очень похожий на тот, который вы хотите. Я нашел здесь краткое вступление (конечно, вы также можете прочитать BOL и MSDN)

Предположим, ваш SP возвращает одно значение с именем out_int , ваш пример может быть переписан как:

SELECT T.id, UDF.out_int
FROM 
    Table1 T
CROSS APPLY
    dbo.fn_My_UDF(T.id) AS UDF

Это будет извлекать каждый «идентификатор» из Таблицы1 и использовать его для вызова fn_My_UDF, результат которого появится в окончательном наборе результатов, кроме исходного параметра.

Вариант "CROSS APPLY" - "OUTER APPLY". Они эквивалентны «INNER JOIN» и «LEFT JOIN», но работают над объединением таблицы и UDF (и вызовом второго одновременно).

Если вы должны (по явному указанию заостренного босса) использовать SP внутри, ну - неудача! Вам придется придерживаться курсоров или попытаться немного обмануть: измените код на UDF и создайте SP-оболочки-оболочки: D.

3 голосов
/ 25 января 2009

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

1 голос
/ 11 мая 2019

Посмотрите на этот ответ с использованием динамического SQL. Я собираюсь переписать запрос в соответствии с вашими потребностями.

DECLARE @TSQL NVARCHAR(MAX) = ''
SELECT @TSQL = @TSQL + N'EXEC dbo.Sproc ' + id + '; 
' -- To insert a line break
FROM Table1
EXEC sp_executesql @TSQL

Это равносильно следующему, если вы PRINT @TSQL. Примечание как вставляется разрыв строки .

EXEC dbo.Sproc id1; 
EXEC dbo.Sproc id2; 
EXEC dbo.Sproc id3;
...
1 голос
/ 25 января 2009

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

Если это невозможно, возможно, вам не повезло.

0 голосов
/ 17 апреля 2012

Этот цикл работает, но он был медленным для моего SP. Я думаю, что правильный ответ от HLGEM, лучше всего написать лучший массовый запрос.

DECLARE @id INT
SET @id = 0

DECLARE @max INT
SELECT TOP 1 @max = TableID
FROM dbo.Table
ORDER BY TableID desc

-- loop until BREAK
-- this is how you can perform a DO-WHILE loop in TSQL
WHILE (1 = 1) 
BEGIN
    SELECT      
        TOP 1 @id = TableID
        FROM dbo.Table
        WHERE TableID > @id 

    IF @id IS NOT NULL
    BEGIN        
        -- call you SP here
        PRINT @id        
    END

    -- break out of the loop once the max id has been reached
    IF @id = @max BREAK 
END
0 голосов
/ 25 января 2009

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

Также, если вы не хотите удалять или обновлять таблицу, вы можете использовать что-то вроде этого:

DECLARE @id [type]
SELECT @id = MIN([id]) FROM [table]
WHILE @id IS NOT NULL
BEGIN
    EXEC [sproc] @id
    SELECT @id = MIN([id]) FROM [table] WHERE [id] > @id
END
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...