ALTER PROCEDURE dbo.DynamicBulkUpInsert
@JsonData NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
-- 修正1:创建临时表存储解析数据
CREATE TABLE #TableData (
TableID INT IDENTITY(1,1) PRIMARY KEY,
TableName SYSNAME NOT NULL,
Records NVARCHAR(MAX) NOT NULL
);
-- 修正2:使用更健壮的JSON解析方法
DECLARE
@Start INT = 1,
@TableStart INT,
@TableEnd INT,
@JsonStart INT,
@JsonEnd INT,
@TableName NVARCHAR(128),
@Records NVARCHAR(MAX);
WHILE @Start < LEN(@JsonData)
BEGIN
-- 查找表名起始位置
SET @TableStart = CHARINDEX('"TableName":"', @JsonData, @Start);
IF @TableStart = 0 BREAK;
-- 提取表名
SET @TableStart = @TableStart + LEN('"TableName":"');
SET @TableEnd = CHARINDEX('"', @JsonData, @TableStart);
SET @TableName = SUBSTRING(@JsonData, @TableStart, @TableEnd - @TableStart);
-- 查找记录起始位置
SET @JsonStart = CHARINDEX('"INSJson":', @JsonData, @TableEnd);
IF @JsonStart = 0
BEGIN
SET @Start = @TableEnd + 1;
CONTINUE;
END
-- 确定JSON对象边界
SET @JsonStart = @JsonStart + LEN('"INSJson":');
DECLARE @BracketCount INT = 0;
DECLARE @Pos INT = @JsonStart;
WHILE @Pos <= LEN(@JsonData)
BEGIN
DECLARE @Char NCHAR(1) = SUBSTRING(@JsonData, @Pos, 1);
IF @Char = N'[' OR @Char = N'{'
SET @BracketCount = @BracketCount + 1;
ELSE IF @Char = N']' OR @Char = N'}'
SET @BracketCount = @BracketCount - 1;
IF @BracketCount = 0
BEGIN
SET @JsonEnd = @Pos;
BREAK;
END
SET @Pos = @Pos + 1;
END
IF @BracketCount <> 0 SET @JsonEnd = LEN(@JsonData);
-- 提取记录 (保留JSON数组格式)
SET @Records = SUBSTRING(@JsonData, @JsonStart, @JsonEnd - @JsonStart + 1);
SET @Start = @JsonEnd + 1;
-- 修正3:统一表名小写处理
SET @TableName = LOWER(@TableName);
INSERT INTO #TableData (TableName, Records)
VALUES (@TableName, @Records);
END;
-- 修正4:使用游标处理每个表
DECLARE @FullSql NVARCHAR(MAX) = N'';
DECLARE @TableID INT, @MaxTableID INT;
SELECT @MaxTableID = MAX(TableID) FROM #TableData;
SET @TableID = 1;
WHILE @TableID <= @MaxTableID
BEGIN
SELECT @TableName = TableName, @Records = Records
FROM #TableData WHERE TableID = @TableID;
-- 检查表是否存在
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = @TableName)
BEGIN
SET @TableID += 1;
CONTINUE;
END
-- 获取主键 (兼容多列主键)
DECLARE @PrimaryKeys NVARCHAR(MAX) = '';
SELECT @PrimaryKeys = @PrimaryKeys + QUOTENAME(c.name) + ','
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
WHERE i.is_primary_key = 1 AND OBJECT_NAME(ic.object_id) = @TableName
ORDER BY ic.key_ordinal;
IF LEN(@PrimaryKeys) > 0
SET @PrimaryKeys = LEFT(@PrimaryKeys, LEN(@PrimaryKeys) - 1);
IF @PrimaryKeys = ''
BEGIN
RAISERROR('Table %s has no primary key, cannot perform Upsert', 16, 1, @TableName);
SET @TableID += 1;
CONTINUE;
END
-- 创建临时表
DECLARE @TempTable NVARCHAR(128) = N'#Temp_' + CAST(@TableID AS NVARCHAR(10));
DECLARE @CreateTempSql NVARCHAR(MAX) = N'
CREATE TABLE ' + @TempTable + N' (
' + (
SELECT STUFF((
SELECT ', ' + QUOTENAME(name) + ' NVARCHAR(MAX)'
FROM sys.columns
WHERE object_id = OBJECT_ID(@TableName)
ORDER BY column_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) + N'
);';
-- 修正5:使用XML解析JSON数据
DECLARE @InsertTempSql NVARCHAR(MAX) = N'
INSERT INTO ' + @TempTable + N'
SELECT ' + (
SELECT STUFF((
SELECT ', MAX(CASE WHEN x.v.value(''local-name(.)'', ''NVARCHAR(128)'') = ''' + name + '''
THEN x.v.value(''.'', ''NVARCHAR(MAX)'') END) AS ' + QUOTENAME(name)
FROM sys.columns
WHERE object_id = OBJECT_ID(@TableName)
ORDER BY column_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) + N'
FROM (
SELECT CAST(''<root><row '' + REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(SUBSTRING(t.Records, 2, LEN(t.Records)-2),
''"'', ''''), '':'', ''="''), '','', ''" /><row ''), ''{'', ''''), ''}'', '''') + ''" /></root>'' AS XML) AS x
FROM (SELECT ''' + REPLACE(@Records, '''', '''''') + ''' AS Records) t
) a
CROSS APPLY x.nodes(''//row'') r(x)
CROSS APPLY (SELECT x.query(''./@*'')) AS a(x)
CROSS APPLY x.nodes(''./@*'') AS x(v);';
-- 生成MERGE语句
DECLARE @MergeSql NVARCHAR(MAX) = N'
MERGE ' + QUOTENAME(@TableName) + N' AS Target
USING ' + @TempTable + N' AS Source
ON (' + REPLACE(@PrimaryKeys, ',', ' = Source.') + N')
WHEN MATCHED THEN
UPDATE SET ' + (
SELECT STUFF((
SELECT ', ' + QUOTENAME(name) + N' = Source.' + QUOTENAME(name)
FROM sys.columns
WHERE object_id = OBJECT_ID(@TableName) AND is_identity = 0
AND name NOT IN (SELECT value FROM STRING_SPLIT(@PrimaryKeys, ','))
ORDER BY column_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) + N'
WHEN NOT MATCHED THEN
INSERT (' + (
SELECT STRING_AGG(QUOTENAME(name), ', ')
FROM sys.columns
WHERE object_id = OBJECT_ID(@TableName) AND is_identity = 0
) + N')
VALUES (' + (
SELECT STRING_AGG('Source.' + QUOTENAME(name), ', ')
FROM sys.columns
WHERE object_id = OBJECT_ID(@TableName) AND is_identity = 0
) + N');';
-- 组合SQL
SET @FullSql = @FullSql + @CreateTempSql + @InsertTempSql + @MergeSql + N'
DROP TABLE ' + @TempTable + N';';
SET @TableID += 1;
END;
-- 执行动态SQL
IF @FullSql <> N''
BEGIN
EXEC sp_executesql @FullSql;
END
DROP TABLE #TableData;
END;