【问题标题】:How to do ForEach on user defined table type in SQL Server stored procedure?如何在 SQL Server 存储过程中对用户定义的表类型执行 ForEach?
【发布时间】:2012-04-04 04:38:17
【问题描述】:
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 中使用这个,请指教。

【问题讨论】:

  • 你能概括一下你想对每一行做什么吗?原因是,您应该尽量避免在 SQL 中这样循环,以支持基于 SET 的操作,以便我们能够提供更好的帮助
  • 当然,我在这里提出一个新问题:stackoverflow.com/questions/9779915/…

标签: sql-server stored-procedures azure-sql-database user-defined-functions user-defined-types


【解决方案1】:

Cursors 是 ForEach 的 SQL 等价物,

但游标通常是不良 SQL 的标志:它们违反了 SQL 构建和优化的通常基于集合的思想。

SQL cursorSQL Cursor Azure 上搜索许多examplestutorialsoptimization 注释。

但这还不够:避免使用游标:它们通常是 SQL 中其他语言程序员的拐杖,而且它们通常速度慢且难以维护。

【讨论】:

  • 我投了反对票。我不相信他应该避免使用游标在他描述的情况下(迭代表变量)。在此页面的其他地方查看我基于光标的答案。
  • 我的回答措辞谨慎,并说“经常”。 OP 甚至不知道“游标”这个词的事实表明他们可能不知道游标的使用会以多快的速度破坏 SQL 的性能。即使在您的示例中,虽然游标不会引起问题,但这意味着如果表被适当地连接到查询中,许多潜在的优化可能会被使用。 OP 没有提供足够的细节来了解他的案例的最佳答案,我认为引导初学者远离光标是正确的。
  • 好吧,我再次不同意。我说的是已经确定逐行处理是唯一可能性的情况。这是我论证的基础。在这种情况下(我们仍然在讨论表变量,而不是传统表),然后将他从游标上移开将导致他选择次优解决方案,例如您在本页其他地方看到的基于 WHILE + SELECT 的解决方案。但是,确实,如果他能避免逐行处理,那么会有更好的解决方案(即设置基解决方案)。
  • 我对此的反应是:“但这还不够:避免使用光标”。当 OP 专门讨论迭代 表变量,而不是传统表时,这似乎是一般性陈述(甚至是误导)。
  • 你看到他的特定问题有一个基于集合的答案吗?按照 OP 评论中的链接,您会发现合并语句对他很有效。我们可以同意不同意。我认为 SQL 新手经常会觉得“但我真的需要在这里使用循环”。因为这是他们熟悉的编程模型。但很少需要循环。它是一个表变量这一事实并没有改变这样一个事实,即游标会扼杀 SQL 引擎可以应用的许多潜在优化。
【解决方案2】:

你可以做类似这样的事情:

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

【讨论】:

  • A WHILE 循环不是基于光标设置的。
  • @MartinSmith 你是对的,删除我的评论并留下示例代码。
【解决方案3】:

为什么不用光标???

我不同意您在 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

所以,是的,我使用光标进行迭代。

请注意,我使用关键字 LOCALFAST_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

【讨论】:

  • 恕我直言,SQL 语言中存在导致使用游标的漏洞。举个例子,我想做一个基于集合的调用,我可以按行单独调用用户定义表中每一行的存储过程。 (动态 sql 真的也是一个答案吗?)为什么这还没有被添加到语言中,这超出了我的理解。
  • 谢谢,这有帮助。
【解决方案4】:

当我为@NumberofIntType 运行 proc...typo 时,对上述答案的一个小修正

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    • 2017-04-02
    • 1970-01-01
    • 2017-11-01
    • 2019-09-24
    相关资源
    最近更新 更多