You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server游标全表更新时EndDate值错误的问题问询

问题分析与解决方案

这个问题我之前也碰到过,核心原因是循环中的变量未重置导致旧值污染,结合你的代码详细拆解下:

为什么全表执行会出错?

看你的代码,@pId、@pInvItemId这些变量是在循环外声明的,每次循环时如果执行SELECT TOP 1 @pId = Id...没有找到匹配的记录(比如第一次处理某个新的InvItemId),这些变量会保留上一次循环的旧值。

举个实际场景的例子:

  1. 先处理InvItemId=A,找到了对应的历史记录,@pId被赋值为100,然后执行UPDATE把这条记录的EndDate改成A的@CreatedOn。
  2. 接着处理InvItemId=B,此时InvPrice_History里还没有B的记录,SELECT TOP 1不会给@pId赋值,所以@pId还是100(A的记录ID)。
  3. 这时候执行UPDATE InvPrice_History SET EndDate = CONVERT(VARCHAR, @CreatedOn) WHERE Id = @pId;,就会错误地把A的那条记录的EndDate改成B的@CreatedOn。

全表执行时,这种跨Item的变量污染会反复发生,最终大量记录的EndDate被最后处理的几条记录的@CreatedOn覆盖;而指定单个ItemId时,所有循环都处理同一个Item,不会出现其他Item的旧值残留,所以结果正确。

另外还有个小隐患:CONVERT(VARCHAR, @CreatedOn)没有指定VARCHAR的长度,SQL Server在不同上下文的默认长度可能不一致,可能导致日期字符串截断,建议显式指定长度(比如VARCHAR(23))来匹配DATETIME的完整格式。

修复方案

1. 每次循环重置变量

在每次循环开始时,把@pId、@pInvItemId等相关变量重置为NULL,避免旧值干扰:

FETCH NEXT FROM stockCursor INTO @Id, @InvItemId, @LocationId, @IsDeleted, @Quantity, @CreatedOn, @StandardCost;
WHILE @@FETCH_STATUS = 0
BEGIN
    -- 重置历史记录变量为NULL
    SET @pId = NULL;
    SET @pInvItemId = NULL;
    SET @pStartDate = NULL;
    SET @pEndDate = NULL;
    SET @pCost = NULL;

    SELECT TOP 1 @pId = Id, @pInvItemId = InvItemId, @pStartDate = StartDate, @pEndDate = EndDate, @pCost = Cost
    FROM InvPrice_History
    WHERE InvItemId = @InvItemId
    ORDER BY StartDate DESC

    -- 后续逻辑保持不变...
END

2. 显式指定CONVERT长度

把CONVERT(VARCHAR, @CreatedOn)改成CONVERT(VARCHAR(23), @CreatedOn),确保日期格式完整:

UPDATE InvPrice_History SET EndDate = CONVERT(VARCHAR(23), @CreatedOn) WHERE Id = @pId;

3. 增加@pId非空判断(可选但更严谨)

在执行UPDATE前先检查@pId是否不为NULL,避免无意义的执行:

IF @pId IS NOT NULL AND @pCost != @sStandardCost
BEGIN
    UPDATE InvPrice_History SET EndDate = CONVERT(VARCHAR(23), @CreatedOn) WHERE Id = @pId;
END

修改后的完整代码示例

DECLARE @up_InvItemId INT = 14330
TRUNCATE TABLE InvPrice_History
--DELETE FROM InvPrice_History where InvItemId=@up_InvItemId
DECLARE stockCursor CURSOR LOCAL FOR
SELECT Id, InvItemId, LocationId, IsDeleted, Quantity, CreatedOn, StandardCost
FROM InvStockMoves
--WHERE InvItemId = @up_InvItemId ;
DECLARE @Id INT, @InvItemId INT, @LocationId INT, @IsDeleted BIT, @Quantity FLOAT, @CreatedOn DATETIME, @StandardCost FLOAT;
DECLARE @pId INT, @pInvItemId INT, @pLocationId INT, @pStartDate DATETIME, @pEndDate DATETIME, @pCost FLOAT;
DECLARE @sId INT, @sInvItemId INT, @sLocationId INT, @sIsDeleted BIT, @sQuantity FLOAT, @sCreatedOn DATETIME, @sStandardCost FLOAT;
OPEN stockCursor;
FETCH NEXT FROM stockCursor INTO @Id, @InvItemId, @LocationId, @IsDeleted, @Quantity, @CreatedOn, @StandardCost;
WHILE @@FETCH_STATUS = 0
BEGIN
    -- 重置历史记录变量,避免旧值污染
    SET @pId = NULL;
    SET @pInvItemId = NULL;
    SET @pStartDate = NULL;
    SET @pEndDate = NULL;
    SET @pCost = NULL;

    SELECT TOP 1 @pId = Id, @pInvItemId = InvItemId, @pStartDate = StartDate, @pEndDate = EndDate, @pCost = Cost
    FROM InvPrice_History
    WHERE InvItemId = @InvItemId
    ORDER BY StartDate DESC

    SET @sStandardCost = dbo.getByTimeCostInvItem(@CreatedOn, @InvItemId);
    IF @sStandardCost IS NOT NULL AND @sStandardCost > 0
    BEGIN
        IF @pId IS NOT NULL AND @pCost != @sStandardCost
        BEGIN
            IF @InvItemId = @up_InvItemId
            BEGIN
                PRINT(@CreatedOn);
            END
            UPDATE InvPrice_History SET EndDate = CONVERT(VARCHAR(23), @CreatedOn) WHERE Id = @pId;
        END
        IF @pId IS NULL OR (@pId IS NOT NULL AND @pCost != @sStandardCost)
        BEGIN
            INSERT INTO InvPrice_History (InvItemId, StartDate, EndDate, Cost)
            VALUES (@InvItemId, @CreatedOn, NULL, @sStandardCost);
        END
    END
    FETCH NEXT FROM stockCursor INTO @Id, @InvItemId, @LocationId, @IsDeleted, @Quantity, @CreatedOn, @StandardCost;
END
CLOSE stockCursor
DEALLOCATE stockCursor

内容的提问来源于stack exchange,提问作者Maaz Anzar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:56:35