【问题标题】:Use a variable in cursor SELECT statement在游标 SELECT 语句中使用变量
【发布时间】:2017-12-03 15:17:03
【问题描述】:

我编写了一个存储过程,它将采用父部件,检查该部件的物料清单记录并有条件地连接匹配的子记录(即包含在该部件的 BOM 中的部件),但是仅满足特定条件的子记录(使用我们的命名约定,以“.”、“E”或“ZG”开头的任何内容)。

我创建了 SP,它运行良好,但只有当我传入 [可选] 父部件号时。我需要为所有部分运行这个 SP,所以我的想法是在游标内执行 SP,并继续传递下一个部分,直到没有剩下要处理的部分。我无法弄清楚如何做到这一点,或者是否有可能,因为在游标的 SELECT 语句中(我正在使用存储过程代码)我需要传入一个变量才能使其工作。

这是存储过程的代码:

CREATE PROCEDURE dbo.usp_BuildBOMLit
@item_no CHAR(8) = NULL 
AS 
BEGIN
WITH CTE AS (
        SELECT DISTINCT
            LTRIM(RTRIM(lvl1.item_no)) as item_no, LTRIM(RTRIM(lvl1.comp_item_no)) as comp_item_no,
            CASE
                WHEN LTRIM(RTRIM(lvl1.comp_item_no)) LIKE '.%' 
                    THEN 
                        LTRIM(RTRIM(lvl1.comp_item_no))
                WHEN LTRIM(RTRIM(lvl1.comp_item_no)) LIKE 'E%' 
                    THEN
                        (SELECT TOP 1 LTRIM(RTRIM(comp_item_no)) FROM bmprdstr_sql WHERE LTRIM(RTRIM(item_no))=LTRIM(RTRIM(lvl1.comp_item_no))  AND LTRIM(RTRIM(comp_item_no)) LIKE '.%')
                WHEN LTRIM(RTRIM(lvl1.comp_item_no)) LIKE 'ZG%'
                    THEN 
                        (SELECT TOP 1 LTRIM(RTRIM(comp_item_no)) FROM bmprdstr_sql WHERE LTRIM(RTRIM(item_no))=LTRIM(RTRIM(lvl1.comp_item_no))  AND LTRIM(RTRIM(comp_item_no)) LIKE '.%')
                    ELSE
                        NULL
            END as lvl_2_comp_item_no
        FROM 
            bmprdstr_sql as lvl1 
            LEFT JOIN bmprdstr_sql lvl2 ON lvl1.comp_item_no=lvl2.item_no
        WHERE 
            (lvl1.item_no = @item_no)
            AND (lvl1.comp_item_no LIKE '.%' OR lvl1.comp_item_no LIKE 'ZG%' OR lvl1.comp_item_no LIKE 'E%')
    )
    SELECT DISTINCT
        CASE
            WHEN LEFT(item_no,1)='.'
                THEN STUFF(item_no,1,1,'')
            ELSE
                item_no
        END as item_no,
        part_no = 
            STUFF((SELECT DISTINCT ',' + 
                    CASE
                        WHEN LEFT(lvl_2_comp_item_no,1)='.'
                            THEN STUFF(lvl_2_comp_item_no,1,1,'')
                        ELSE lvl_2_comp_item_no
                        END
                    FROM CTE where item_no=@item_no FOR XML PATH('')),1,1,'')
    FROM
        CTE
    WHERE
        lvl_2_comp_item_no IS NOT NULL AND item_no IS NOT NULL
END

输出正是我需要的格式:

item_no | part_no

JM9027 | GS10702,LB2391,LB2704,LB2834,LB2896,LB6996

当我创建游标时,我在游标的SELECT 语句中使用相同的代码,但是您可以看到是否需要传入父级 (@itemno)。我已经尝试过了,没有有效:

 SET NOCOUNT ON;
 DECLARE @itemno CHAR(15);
 DECLARE @partno VARCHAR(254);
 DECLARE @outside_cursor AS CURSOR;

SET @outside_cursor = CURSOR FAST_FORWARD FOR 
WITH CTE AS
(SELECT DISTINCT 
        LTRIM(RTRIM(lvl1.item_no)) AS item_no,
        LTRIM(RTRIM(lvl1.comp_item_no)) AS comp_item_no,
        CASE
            WHEN LTRIM(RTRIM(lvl1.comp_item_no)) LIKE '.%' 
                THEN LTRIM(RTRIM(lvl1.comp_item_no))
            WHEN LTRIM(RTRIM(lvl1.comp_item_no)) LIKE 'E%' 
                THEN
                    (SELECT TOP 1 
                        LTRIM(RTRIM(comp_item_no))
                    FROM 
                        bmprdstr_sql
                    WHERE 

LTRIM(RTRIM(item_no))=LTRIM(RTRIM(lvl1.comp_item_no))
                        AND LTRIM(RTRIM(comp_item_no)) LIKE '.%'
                    )
            WHEN LTRIM(RTRIM(lvl1.comp_item_no)) LIKE 'ZG%' 
                THEN
                    (SELECT TOP 1 
                        LTRIM(RTRIM(comp_item_no))
                    FROM 
                        bmprdstr_sql
                    WHERE 

LTRIM(RTRIM(item_no))=LTRIM(RTRIM(lvl1.comp_item_no))
                        AND LTRIM(RTRIM(comp_item_no)) LIKE '.%'
                    )
            ELSE NULL
        END AS lvl_2_comp_item_no
 FROM 
    bmprdstr_sql AS lvl1
    LEFT JOIN bmprdstr_sql lvl2 ON lvl1.comp_item_no=lvl2.item_no
 WHERE 
    (lvl1.item_no = @itemno) -- <-- problem
     AND (lvl1.comp_item_no LIKE '.%' OR lvl1.comp_item_no LIKE 'ZG%' 
OR lvl1.comp_item_no LIKE 'E%')
)
SELECT DISTINCT 
    CASE
        WHEN LEFT(item_no,1)='.' 
            THEN STUFF(item_no,1,1,'')
        ELSE item_no
    END AS item_no,
    part_no = 
        STUFF(
            (SELECT DISTINCT ',' + 
                CASE 
                    WHEN LEFT(lvl_2_comp_item_no,1)='.' 
                        THEN STUFF(lvl_2_comp_item_no,1,1,'') 
                    ELSE lvl_2_comp_item_no 
                END
            FROM 
                CTE
            WHERE 
                item_no=@itemno FOR XML PATH('')),1,1,'') 
                     -- ^ problem
FROM 
    CTE
WHERE 
    lvl_2_comp_item_no IS NOT NULL
    AND item_no IS NOT NULL 

OPEN @outside_cursor;
FETCH NEXT FROM @outside_cursor INTO @itemno, @partno;

WHILE @@FETCH_STATUS = 0 
    BEGIN
        INSERT INTO items_parts (item_no, part_no)
        VALUES (@itemno, @partno) 
    FETCH NEXT FROM @outside_cursor INTO @itemno,  @partno 
    END 
CLOSE @outside_cursor 
DEALLOCATE @outside_cursor

关于如何实现这一点的任何建议?

【问题讨论】:

  • 不确定为什么要使用游标插入数据。我没有尝试解析这个大查询来真正了解它在做什么,但这可以重写为基于单个集合的插入。这是一个很好的起点。 spaghettidba.com/2015/04/24/…
  • @SeanLange 每个关于 SQL 游标的问题都会以一种或另一种形式出现此评论。在找到光标之前,我尝试使用基于集合的方法。
  • 这是因为几乎每个游标都可以使用基于集合的方法而不是 RBAR 来解决。

标签: sql-server sql-server-2008 tsql stored-procedures cursor


【解决方案1】:

不管您是否需要光标,并将您的问题视为学术问题,您使用光标的方式是错误的。

你有一个可以做你想做的事情的过程,但只适用于一个父母。合并游标的方法是使用游标遍历父项,并在每次循环时使用新的 item_no 填充 item_no 变量,然后在游标内运行现有代码。

您不需要在两个单独的查询中执行 SELECT 和 INSERT,您可以在光标中执行单个 INSERT..SELECT。光标只是遍历您的 item_no 值列表。

【讨论】:

  • 当您说我不需要在单独的查询中执行 SELECT 和 INSERT 时,您是说我可以在游标定义中执行 INSERT...SELECT 吗?像SET @outside_cursor = CURSOR FAST FORWARD FOR INSERT INTO items_parts VALUES @itemno, @partno WITH CTE AS (SELECT....),然后在 WHILE 循环中调用 FETCH NEXT?
  • 不,游标定义只是您想用作@itemno 变量的item_no 的SELECT。 INSERT..SELECT 是您放在游标循环中的操作,因此它对每个item_no 执行。
  • 感谢您的帮助@Tab Alleman。我能够在不使用光标的情况下完成此操作。
【解决方案2】:

我明白了;

我认为您拥有的是一个 CTE,它返回单行数据(或者可能是 (n) 行数据,但仅对 @itemno 唯一)

    CTE(item_no, comp_item_no, lvl_2_comp_item_no) 
      AS (
            --.... and the row will correspond to the @itemno provided

但是您想要的是 CTE,它返回 所有可能的 [ietm_no] 的值集(这应该会在后台生成一个临时表,其中包含 3 列 [item_no]、[comp_item_no] , [lvl_2_comp_item_no] 有 (n) 行。

然后您可以使用您的 CURSOR 逐个循环,将它们加载到 CURSOR VARIABLES 中,即:

FETCH NEXT FROM @outside_cursor INTO @itemno, @partno... 

并使用唯一的@itemno 处理每一行的插入。

顺便说一句,我注意到您将光标命名为@outside_cursor,这意味着某处也可能存在@inside_cursor?如果您要嵌套游标(这不是最佳选择),请注意 @@FETCH_STATUS 是一个全局变量,您需要为每个游标单独管理此变量中的值。

我看对了吗?

【讨论】:

  • CTE的结果是父item_no在item_no列重复,comp_item_no是父级下的第一个子级,如果comp_item_no值以'.'开头,'E',或“ZG”,它执行子查询并找到该项目的任何子项匹配相同的条件。然后,以下查询会删除“。”如果有,则从 item_no 的前面开始,并将所有 lvl_2_comp_item_no 连接到一个逗号分隔的列表中。此外,游标被命名为@outside_cursor,因为我一直试图通过创建一个嵌套游标来解决这个问题,但从未重命名它。
  • 好的,很好,光标没有嵌套,我上面的建议怎么样?看来您没有以正确的方式使用光标。我认为您需要为所有 itemno 加载数据集,然后一一处理...有意义吗?
  • 您所描述的最终是我想要的结果,但是如果不使用 CTE 中的变量,我无法弄清楚用于返回数据的正确语法。我将尝试您的建议并执行 SP open 以将所有数据插入临时表,然后再定义和运行游标以遍历行。我会报告的 - 谢谢你的建议。
  • 尝试简化 CTE,使其返回所有 item_no 的数据(基本上从语法中删除 @itemno),然后从 CTE 中选择 *,看看你有什么。您应该会看到很多可以加载到 CURSOR 中的行。让我知道这是否有效...
  • 感谢您的帮助@Milan。我能够在不使用光标的情况下完成此操作。
【解决方案3】:

感谢您在此问题上提供的帮助。我回到绘图板上,能够使用存储过程和自联接查询完成我需要的工作 - 不需要光标。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-02
    • 2013-05-07
    相关资源
    最近更新 更多