在表中创建新记录时避免使用游标并使用新生成的键更新另一个记录?

问题描述 投票:0回答:1
介绍

我最近必须修复旧 WCF/Angular 项目中的一个错误。它是一个待办事项应用程序,允许您创建任务(“列表”记录)并使用第三个表进行一些时间计算,并在“待办事项”中将其标记为已完成。让我们忽略它的所有错误(而且有很多),因为我确信即使我提出建议也不会改变任何事情。 这两个表看起来像这样:

  • 列表(ListId int、SiteUserId int、ListText varchar(max)、Status int、CreatedBy int、CreatedDate 日期时间)
  • Todo(ToDoId int,[日期]日期,优先级 int,ListId int,状态 int,实际时间 int,计算时间 int)

我被要求修复的错误是,当用户将待办任务(这是三个表中的数据的组合)复制到不同的日期并更改文本值时,该项目的每个实例都有其文本值改变了。 通过对 API 进行细微更改即可修复该错误(制作副本时,会创建一个指向相同“列表”项目的新“待办事项”项目)。但是,我还被要求修复现有记录,以便它们将来不会显示此行为。

我创建了一个查询,该查询将为每个待办事项(列表项的副本)创建一个新的列表项,然后为这些待办事项分配新的 ListId。我对 SQL 相当不熟悉(当然对编写高性能查询一无所知),并且想知道替代策略。最后,我花在阅读文档和编写查询上的三个小时毫无用处,因为我们认为早期的记录并不重要。我的代码花了大约 9 分钟来执行(如果我没记错的话)大约 6600 个待办事项。

我非常确定查询如此慢的主要原因是我使用了游标。无论如何,我是否可以做到这一点,而无需一次循环遍历每个记录?我对 SQL 没有丰富的知识基础或良好的直觉,并且会喜欢任何可以帮助我变得更好的资源。

我的代码:

DECLARE @TodoId INT
DECLARE @ListId INT

BEGIN TRY
    BEGIN TRANSACTION

    SELECT *
    INTO #tempLists
    FROM List
    WHERE ListId IN (
            SELECT ListId
            FROM todo
            GROUP BY ListId
            HAVING count(ListId) >= 2
            );

    WITH cte
    AS (
        SELECT ToDoId,
            ROW_NUMBER() OVER (
                PARTITION BY ListId ORDER BY [Date]
                ) AS rn
        FROM ToDo
        WHERE ListId IN (
                SELECT ListId
                FROM #tempLists
                )
        )
    SELECT *
    INTO #tempToDos
    FROM cte
    WHERE rn > 1

    DECLARE db_cursor CURSOR
    FOR
    SELECT TodoId,
        ListId
    FROM ToDo
    WHERE TodoId IN (
            SELECT TodoId
            FROM #tempToDos
            )

    OPEN db_cursor

    FETCH NEXT
    FROM db_cursor
    INTO @TodoId,
        @ListId

    WHILE @@FETCH_STATUS = 0
    BEGIN
        INSERT INTO List (
            SiteUserId,
            ListText,
            [Status],
            CreatedBy,
            CreatedDate
            )
        SELECT SiteUserId,
            ListText,
            [Status],
            CreatedBy,
            CreatedDate
        FROM #tempLists
        WHERE ListId = @ListId

        UPDATE ToDo
        SET ListId = (
                SELECT SCOPE_IDENTITY()
                )
        WHERE TodoId = @TodoId

        PRINT @TodoId
        PRINT @ListId

        FETCH NEXT
        FROM db_cursor
        INTO @TodoId,
            @ListId
    END

    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK
    DECLARE @ErrMsg NVARCHAR(4000),
        @ErrSeverity INT
    SELECT @ErrMsg = ERROR_MESSAGE(),
        @ErrSeverity = ERROR_SEVERITY()
    RAISERROR (
            @ErrMsg,
            @ErrSeverity,
            1
            )
END CATCH

CLOSE db_cursor
DEALLOCATE db_cursor

DROP TABLE #tempLists
DROP TABLE #tempToDos
sql sql-server query-optimization database-cursor
1个回答
0
投票

根据@Dale K 和@TN 的建议,我想出了以下方法来替换光标操作。它在 1 秒内执行。谢谢你:D

DECLARE @insertedLists TABLE (
    ListId INT,
    ToDoId INT
    );

WITH cte
AS (
    SELECT tl.*,
        td.TodoId
    FROM #tempLists tl
    CROSS JOIN #tempToDos td
    WHERE tl.ListId = td.ListId
    )
MERGE List AS TGT
USING cte AS SRC
    ON 1 = 0
WHEN NOT MATCHED
    THEN
        INSERT (
            ListId,
            SiteUserId,
            ListText,
            [Status],
            CreatedBy,
            CreatedDate
            )
        VALUES (
            SRC.ListId,
            SRC.SiteUserId,
            SRC.ListText,
            0,
            SRC.CreatedBy,
            SRC.CreatedDate
            )
OUTPUT INSERTED.ListId,
    SRC.ToDoId
INTO @insertedLists;

UPDATE ToDo
SET ToDo.ListId = tl.ListId
FROM @insertedLists AS tl
WHERE ToDo.ToDoId = tl.ToDoId
© www.soinside.com 2019 - 2024. All rights reserved.