Как сделать ForEach для определенного пользователем типа таблицы в хранимой процедуре SQL Server? - PullRequest
2 голосов
/ 19 марта 2012
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, пожалуйста, совет.

Ответы [ 4 ]

11 голосов
/ 27 июня 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
2 голосов
/ 19 марта 2012

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

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

Поиск по SQL cursor или SQL Cursor Azure для многих примеров , учебных пособий и оптимизации заметок.

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

1 голос
/ 19 марта 2012

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

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
0 голосов
/ 06 октября 2015

небольшая поправка в ответе выше, когда я запустил процесс ... опечатка для @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
...