Как сделать ForEach для пользовательского типа таблицы в хранимой процедуре SQL Server?

XX PROCEDURE [dbo].[XXX]
    @X dbo.IntType readonly
AS
BEGIN
    SET NOCOUNT ON;
    // how can I foreach(@X) here and do process individually?
END

IntType — это определяемый пользователем тип таблицы.

CREATE TYPE [dbo].[IntType] AS TABLE(
    [T] [int] NOT NULL,
    PRIMARY KEY CLUSTERED 
(
    [T] ASC
)

Мне нужно использовать это в SQL Azure, пожалуйста, посоветуйте.


comment
Не могли бы вы описать, что вы хотите сделать с каждой строкой? Причина в том, что вы должны стараться избегать подобных циклов в SQL в пользу операций на основе SET, чтобы мы могли лучше помочь   -  person AdaTheDev    schedule 19.03.2012
comment
Конечно, я задаю здесь новый вопрос: stackoverflow.com/questions/9779915/   -  person Eric Yin    schedule 20.03.2012


Ответы (4)


Курсоры — это SQL-эквивалент ForEach,

Но курсоры часто являются признаком плохого SQL: они нарушают обычное мышление, основанное на наборах, на котором построен и оптимизирован SQL.

Найдите в SQL cursor или SQL Cursor Azure множество примеров, руководства и примечания по оптимизации.

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

person Jamie F    schedule 19.03.2012
comment
Я поставил этому минус. Я не думаю, что ему следует избегать использования курсора в случае, который он описывает (перебор табличной переменной). Смотрите мой ответ на основе курсора в другом месте на этой странице. - person peterh; 27.06.2013
comment
Я тщательно сформулировал свой ответ и сказал часто. Тот факт, что OP даже не знает словесный курсор, указывает на то, что они, вероятно, не знают, как быстро использование курсоров может снизить производительность SQL. Даже в вашем примере, хотя курсор не вызовет проблем, это означает, что не будут использоваться многие потенциальные оптимизации, которые могли бы использоваться, если бы таблица была соответствующим образом присоединена к запросу. ОП не предоставил достаточно подробностей, чтобы узнать лучший ответ для своего случая, и я думаю, что правильно уводить новичков от курсоров. - person Jamie F; 27.06.2013
comment
Ну опять же не соглашусь. Я говорю о случае, когда было установлено, что построчная обработка - единственная возможность. Это основа моего аргумента. В этом случае (и мы все еще говорим о табличных переменных, а не о традиционных таблицах) отвлечение его от курсоров приведет к тому, что он выберет неоптимальные решения, подобные тем, которые основаны на WHILE + SELECT, которые вы видите в другом месте на этой странице. Но, правда, если он может вообще избежать построчной обработки, тогда есть гораздо лучшие решения (т.е. решения с набором баз). - person peterh; 27.06.2013
comment
Я реагировал на это: Но этого недостаточно: избегайте курсоров. Это просто кажется общим утверждением (даже вводящим в заблуждение), когда в ОП конкретно говорится об итерации по табличной переменной, а не по традиционной таблице. - person peterh; 27.06.2013
comment
Вы видели, что на его конкретный вопрос есть основанный на множестве ответ? Перейдите по ссылке в комментарии OP, и вы увидите, что оператор слияния сработал для него хорошо. Мы можем не соглашаться. Я думаю, что новички в SQL часто чувствуют, что здесь действительно нужно использовать цикл. так как это модель программирования, с которой им удобно. Но петли нужны редко. Тот факт, что это табличная переменная, не меняет того факта, что курсор убивает многие потенциальные оптимизации, которые может применить механизм SQL. - person Jamie F; 27.06.2013
comment
Да, я понимаю, что в конечном итоге он обнаружил, что может вообще не использовать построчную обработку, и мы оба согласны с тем, что это первое решение. Но ЕСЛИ вам действительно нужна построчная обработка, а целью итерации является табличная переменная, тогда я призываю вас придумать более оптимальное решение, чем курсор. Следовательно, у меня проблемы с вашим предложением. Но этого недостаточно: избегайте курсоров. Я перефразирую свой собственный ответ, чтобы сделать это более ясным. (Я на самом деле не думаю, что мы так сильно расходимся во мнениях, за исключением, возможно, формулировки). - person peterh; 27.06.2013

Почему бы не использовать курсор???

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

Ваша первая точка принятия решения всегда должна заключаться в том, чтобы увидеть, можете ли вы выполнить операцию на основе набора, а не итерацию (построчную обработку). Базы данных оптимизированы для первого. Ответ, который я даю здесь, предназначен для тех, кто решил, что они никак не могут использовать подход, основанный на наборах, и целью итерации является табличная переменная.

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

Давайте рассмотрим пример, основанный на вашем сценарии. Сначала мы определяем тип таблицы:

CREATE TYPE [IntListType] AS TABLE
   (   [T] INT  );
GO

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

CREATE PROCEDURE [myTest]
 (
       @IntListInput IntListType READONLY
 )
 AS
 BEGIN
    SET NOCOUNT ON;

    DECLARE @myInt INT;
    DECLARE intListCursor CURSOR LOCAL FAST_FORWARD
    FOR
    SELECT [T]
    FROM @IntListInput;

    OPEN intListCursor;

    -- Initial fetch attempt
    FETCH NEXT FROM intListCursor INTO @myInt;

    WHILE @@FETCH_STATUS = 0
    BEGIN
       -- Here we do some kind of action that requires us to 
       -- process the table variable row-by-row. This example simply
       -- uses a PRINT statement as that action (not a very good
       -- example).
       PRINT 'Int var is : ' + CONVERT(VARCHAR(max),@myInt);

       -- Attempt to fetch next row from cursor
       FETCH NEXT FROM intListCursor INTO @myInt;
    END;

    CLOSE intListCursor;
    DEALLOCATE intListCursor;
 END;
 GO

Итак, да, я использую курсор для итерации.

Обратите внимание, что я использую ключевые слова LOCAL и FAST_FORWARD просто для того, чтобы оптимизатору было очень ясно (явно), что я не собираюсь обновлять свой курсор, и я буду прокручивать только вперед, и я буду получать к нему доступ только внутри процедуры.

Я тестировал это так:

DECLARE @IntList IntListType;

-- Put some random data into our list
INSERT INTO @IntList VALUES (33);
INSERT INTO @IntList VALUES (777);
INSERT INTO @IntList VALUES (845);
INSERT INTO @IntList VALUES (71);


EXEC myTest @IntList;
GO
person peterh    schedule 27.06.2013
comment
ИМХО в языке SQL есть дыры, которые заставляют использовать курсоры. Возьмем, к примеру, я хотел бы сделать вызов на основе набора, где я могу вызывать индивидуально по строкам хранимую процедуру для каждой строки из пользовательской таблицы. (Действительно ли динамический sql также является ответом?) Почему это не было добавлено в язык, мне не понятно. - person ΩmegaMan; 15.05.2018
comment
Спасибо, это помогло. - person Abdul Waheed; 22.01.2019

Вы можете сделать что-то похожее на это:

CREATE PROCEDURE [dbo].[testSet]
AS

BEGIN
    SET NOCOUNT ON;

    DECLARE @NumberofIntType            int,
            @RowCount                   int

    -- get the number of items
    SET @NumberofIntType = (SELECT  count(*)
                            FROM dbo.IntType)

    SET @RowCount = 0           -- set the first row to 0

    -- loop through the records 
    -- loop until the rowcount = number of records in your table
    WHILE @RowCount <= @NumberofIntType
        BEGIN
            -- do your process here

            SET @RowCount = @RowCount + 1
        END
END
person Taryn    schedule 19.03.2012
comment
Цикл WHILE основан не больше, чем курсор. - person Martin Smith; 19.03.2012
comment
@MartinSmith, вы правы, удалили мой комментарий и оставили пример кода. - person Taryn; 19.03.2012

небольшое исправление в приведенном выше ответе, когда я запустил proc... опечатку для @NumberofIntType

CREATE PROCEDURE [dbo].[testSet]
AS

BEGIN
    SET NOCOUNT ON;

    DECLARE @NumberofIntType            int,
            @RowCount                   int

    -- get the number of items
    SET @NumberofIntType = (SELECT  count(*)
                            FROM dbo.IntType)

    SET @RowCount = 0           -- set the first row to 0

    -- loop through the records 
    -- loop until the rowcount = number of records in your table
    WHILE @RowCount <= @NumberofIntType
        BEGIN
            -- do your process here

            SET @RowCount = @RowCount + 1
        END
END
person sri    schedule 06.10.2015