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

SQL Server存储过程中JSON加载数据时CROSS APPLY重复问题解决

问题描述

我需要通过SQL Server存储过程填充两张主键自动生成的表,传入JSON数组作为数据输入。生成PARENT表后,需将其新增的ID作为PRODUCT表每条记录的外键。当前实现的存储过程使用CROSS APPLY时,PRODUCT表会产生与数组大小一致的重复记录,请问如何让PRODUCT表仅添加单条记录,或有无无需使用CROSS APPLY的实现方案?

原存储过程代码

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SP_CREATE_PRODUCT]
    @parentJSONData NVARCHAR(MAX)
AS
BEGIN
BEGIN TRANSACTION;
    SAVE TRANSACTION StartPoint;
    DECLARE @PARENT TABLE ([ID] INT, [PARENT_NAME] NVARCHAR(50), [EFFECTIVE_FROM_WEEK] INT, [EFFECTIVE_TO_WEEK] INT, [ADD_FLAG] INT, [DEL_FLAG] INT)
    DECLARE @PRODUCT TABLE ([PRODUCT_CODE_ID] INT, [PRODUCT_STATUS_ID] INT, [PRODUCT_NAME] NVARCHAR(50), [PARENT_ID] INT NOT NULL)
    DECLARE @Output TABLE ([ID] INT,[PARENT_NAME] NVARCHAR(50), [EFFECTIVE_FROM_WEEK] INT)

    DECLARE @ErrorMessage  nvarchar(MAX)
    DECLARE @ErrorSeverity nvarchar(MAX)
    DECLARE @ErrorState    nvarchar(MAX)
BEGIN TRY
INSERT INTO PARENT 
        ([PARENT_NAME], [EFFECTIVE_FROM_WEEK], [EFFECTIVE_TO_WEEK], [ADD_FLAG], [DEL_FLAG]) OUTPUT inserted.ID, inserted.PARENT_NAME, inserted.EFFECTIVE_FROM_WEEK INTO @Output
SELECT [PARENT_NAME], [EFFECTIVE_FROM_WEEK], [EFFECTIVE_TO_WEEK], [ADD_FLAG], [DEL_FLAG]
FROM OPENJSON(@parentJSONData)  WITH (
    PARENT_NAME      NVARCHAR(50) '$.parent.parentName'
        , EFFECTIVE_FROM_WEEK INT '$.parent.effectiveFromWeek'
        , EFFECTIVE_TO_WEEK   INT '$.parent.effectiveToWeek'
        , ADD_FLAG        INT '$.parent.addFlag.id'
        , DEL_FLAG        INT '$.parent.delFlag.id'
    ) AS jsonValues;

INSERT INTO PRODUCT
([PRODUCT_CODE_ID], [PRODUCT_STATUS_ID], [PRODUCT_NAME], [PARENT_ID])
SELECT PRODUCT_CODE_ID, PRODUCT_STATUS_ID, PRODUCT_NAME, ID
FROM OPENJSON(@parentJSONData) WITH (
    PRODUCT_CODE_ID      NVARCHAR(50) '$.productCodeID'
        , PRODUCT_STATUS_ID INT '$.productStatusID'
        , PRODUCT_NAME   NVARCHAR(50) '$.productName'
    ) AS jsonValues CROSS APPLY @Output;
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION
        SET @ErrorMessage  = ERROR_MESSAGE()
        SET @ErrorSeverity = ERROR_SEVERITY()
        SET @ErrorState    = ERROR_STATE()
        RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH
END;
GO

输入JSON

[{"parent":{"parentName":"Paper Pack","effectiveFromWeek":202423,"effectiveToWeek":202534,"addFlag":{"id":1,"state":"4x4"},"delFlag":{"id":1,"state":"4x4"}},"productCodeID":23386,"productStatusID":71013,"productName":"Paper Package"},{"parent":{"parentName":"Pen Pack","effectiveFromWeek":202423,"effectiveToWeek":202534,"addFlag":{"id":1,"state":"100"},"delFlag":{"id":1,"state":"100"}},"productCodeID":23387,"productStatusID":71023,"productName":"Pen Box"}]

解决方案

问题根源是CROSS APPLY @Output会让每条JSON中的产品数据与所有新增的父表ID做笛卡尔积,导致重复插入。核心要实现的是每条JSON里的父表数据和对应产品数据一一绑定,避免交叉关联。

方案一:用临时表保存JSON完整数据及唯一标识

先把所有JSON数据解析到临时表,保留每条记录的索引(来自OPENJSON的[key]字段),插入父表时同步输出该索引和新增的父ID,最后通过索引关联插入产品表,彻底避免笛卡尔积:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SP_CREATE_PRODUCT]
    @parentJSONData NVARCHAR(MAX)
AS
BEGIN
BEGIN TRANSACTION;
    -- 保存解析后的完整JSON数据,含每条记录的索引
    DECLARE @JsonData TABLE (
        JsonIndex INT,
        PARENT_NAME NVARCHAR(50),
        EFFECTIVE_FROM_WEEK INT,
        EFFECTIVE_TO_WEEK INT,
        ADD_FLAG INT,
        DEL_FLAG INT,
        PRODUCT_CODE_ID INT,
        PRODUCT_STATUS_ID INT,
        PRODUCT_NAME NVARCHAR(50)
    )

    -- 保存插入父表后的ID与对应JSON索引的关联
    DECLARE @InsertedParents TABLE (
        JsonIndex INT,
        PARENT_ID INT
    )

    DECLARE @ErrorMessage  nvarchar(MAX)
    DECLARE @ErrorSeverity int
    DECLARE @ErrorState    int

BEGIN TRY
    -- 解析JSON到临时表,保留每条记录的索引
    INSERT INTO @JsonData
    SELECT
        CAST([key] AS INT) AS JsonIndex,
        JSON_VALUE(value, '$.parent.parentName') AS PARENT_NAME,
        JSON_VALUE(value, '$.parent.effectiveFromWeek') AS EFFECTIVE_FROM_WEEK,
        JSON_VALUE(value, '$.parent.effectiveToWeek') AS EFFECTIVE_TO_WEEK,
        JSON_VALUE(value, '$.parent.addFlag.id') AS ADD_FLAG,
        JSON_VALUE(value, '$.parent.delFlag.id') AS DEL_FLAG,
        JSON_VALUE(value, '$.productCodeID') AS PRODUCT_CODE_ID,
        JSON_VALUE(value, '$.productStatusID') AS PRODUCT_STATUS_ID,
        JSON_VALUE(value, '$.productName') AS PRODUCT_NAME
    FROM OPENJSON(@parentJSONData)

    -- 插入父表,同时输出新增ID和对应JSON索引
    INSERT INTO PARENT 
        ([PARENT_NAME], [EFFECTIVE_FROM_WEEK], [EFFECTIVE_TO_WEEK], [ADD_FLAG], [DEL_FLAG])
    OUTPUT jd.JsonIndex, inserted.ID INTO @InsertedParents
    SELECT 
        PARENT_NAME, 
        EFFECTIVE_FROM_WEEK, 
        EFFECTIVE_TO_WEEK, 
        ADD_FLAG, 
        DEL_FLAG
    FROM @JsonData jd

    -- 通过JSON索引关联,插入产品表
    INSERT INTO PRODUCT
        ([PRODUCT_CODE_ID], [PRODUCT_STATUS_ID], [PRODUCT_NAME], [PARENT_ID])
    SELECT 
        jd.PRODUCT_CODE_ID, 
        jd.PRODUCT_STATUS_ID, 
        jd.PRODUCT_NAME, 
        ip.PARENT_ID
    FROM @JsonData jd
    JOIN @InsertedParents ip ON jd.JsonIndex = ip.JsonIndex

    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION
    SET @ErrorMessage  = ERROR_MESSAGE()
    SET @ErrorSeverity = ERROR_SEVERITY()
    SET @ErrorState    = ERROR_STATE()
    RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH
END;
GO

方案二:通过父表唯一字段关联(简化方案)

如果单次输入的PARENT_NAME保证唯一,可以直接通过父表名称关联JSON中的父数据和产品数据,无需额外保存索引:

注意:此方案依赖PARENT_NAME的唯一性,若存在重复名称会导致关联错误,优先选择方案一。

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SP_CREATE_PRODUCT]
    @parentJSONData NVARCHAR(MAX)
AS
BEGIN
BEGIN TRANSACTION;
    DECLARE @InsertedParents TABLE (
        PARENT_ID INT,
        PARENT_NAME NVARCHAR(50)
    )

    DECLARE @ErrorMessage  nvarchar(MAX)
    DECLARE @ErrorSeverity int
    DECLARE @ErrorState    int

BEGIN TRY
    -- 插入父表,输出ID和名称
    INSERT INTO PARENT 
        ([PARENT_NAME], [EFFECTIVE_FROM_WEEK], [EFFECTIVE_TO_WEEK], [ADD_FLAG], [DEL_FLAG])
    OUTPUT inserted.ID, inserted.PARENT_NAME INTO @InsertedParents
    SELECT 
        [PARENT_NAME], 
        [EFFECTIVE_FROM_WEEK], 
        [EFFECTIVE_TO_WEEK], 
        [ADD_FLAG], 
        [DEL_FLAG]
    FROM OPENJSON(@parentJSONData)  
    WITH (
        PARENT_NAME      NVARCHAR(50) '$.parent.parentName'
        , EFFECTIVE_FROM_WEEK INT '$.parent.effectiveFromWeek'
        , EFFECTIVE_TO_WEEK   INT '$.parent.effectiveToWeek'
        , ADD_FLAG        INT '$.parent.addFlag.id'
        , DEL_FLAG        INT '$.parent.delFlag.id'
    ) AS jsonValues;

    -- 通过PARENT_NAME关联,插入产品表
    INSERT INTO PRODUCT
        ([PRODUCT_CODE_ID], [PRODUCT_STATUS_ID], [PRODUCT_NAME], [PARENT_ID])
    SELECT 
        jv.PRODUCT_CODE_ID, 
        jv.PRODUCT_STATUS_ID, 
        jv.PRODUCT_NAME, 
        ip.PARENT_ID
    FROM OPENJSON(@parentJSONData) 
    WITH (
        PARENT_NAME      NVARCHAR(50) '$.parent.parentName'
        , PRODUCT_CODE_ID      INT '$.productCodeID'
        , PRODUCT_STATUS_ID INT '$.productStatusID'
        , PRODUCT_NAME   NVARCHAR(50) '$.productName'
    ) AS jv
    JOIN @InsertedParents ip ON jv.PARENT_NAME = ip.PARENT_NAME

    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION
    SET @ErrorMessage  = ERROR_MESSAGE()
    SET @ErrorSeverity = ERROR_SEVERITY()
    SET @ErrorState    = ERROR_STATE()
    RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH
END;
GO

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 05:04:58