我最近必须修复旧 WCF/Angular 项目中的一个错误。它是一个待办事项应用程序,允许您创建任务(“列表”记录)并使用第三个表进行一些时间计算,并在“待办事项”中将其标记为已完成。让我们忽略它的所有错误(而且有很多),因为我确信即使我提出建议也不会改变任何事情。 这两个表看起来像这样:
我被要求修复的错误是,当用户将待办任务(这是三个表中的数据的组合)复制到不同的日期并更改文本值时,该项目的每个实例都有其文本值改变了。 通过对 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
根据@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