我有如下所示的存储过程,它从包含两列的表中读取数据:第一列row_id包含行的ID,第二列row_data包含包含数据的JSON,然后它将该JSON转换为列并将其插入到新表中.

该存储过程从包含JSON的一个表中读取数据,并将该JSON规范化为一个新表,同时删除重复的行:

CREATE OR ALTER PROCEDURE dbo.basic_json_normalization_incr
    @tableNamep NVARCHAR(MAX),
    @tableName NVARCHAR(MAX), 
    @rows_count INT OUTPUT
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @raw_tableName NVARCHAR(MAX);
    DECLARE @chargement NVARCHAR(MAX);
    DECLARE @truncateQuery NVARCHAR(MAX);
    DECLARE @columns_list NVARCHAR(MAX);
    DECLARE @json_query_part NVARCHAR(MAX);
    DECLARE @json_query NVARCHAR(MAX) = '';
    DECLARE @separator NVARCHAR(2) = '';
    DECLARE @sqldelete NVARCHAR(MAX);
    DECLARE @sqlinsert NVARCHAR(MAX);

    SET @raw_tableName = 'dbo.raw' + @tableNamep;

    SELECT @columns_list = COALESCE(@columns_list + ', ' + '[' + column_name + ']', '[' + column_name + ']')
    FROM dbo.schemaSource
    WHERE _enabled = 1 AND table_name = @tableNamep;

    DECLARE json_cursor CURSOR FOR
        SELECT CASE
                   WHEN single_mult = 'M'
                       THEN CONCAT('[', column_name, '] NVARCHAR(MAX) ', 'AS JSON')
                   ELSE CONCAT('[', column_name, '] NVARCHAR(MAX) ', '''' + '$.', column_name, '''')
               END
        FROM dbo.schemaSource WITH (NOLOCK)
        WHERE _enabled = 1
          AND table_name = @tableNamep;

    OPEN json_cursor;

    FETCH NEXT FROM json_cursor INTO @json_query_part;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @json_query = @json_query + @separator + @json_query_part;
        SET @separator = ',';
        
        FETCH NEXT FROM json_cursor INTO @json_query_part;
    END;

    CLOSE json_cursor;
    DEALLOCATE json_cursor;

    -- Determine the duplicate check column based on table name
    DECLARE @duplicateCheckColumn NVARCHAR(MAX);
    
    SET @duplicateCheckColumn = '_id';
    
    -- Build the dynamic SQL query for insertion with duplicate and CURR_NO check

    SET @sqldelete = N'DELETE FROM ' + @tableName + ' WHERE _id COLLATE French_CI_AS IN (SELECT raw_id COLLATE French_CI_AS FROM raw' + @tableNamep + ');';
        
    SET @sqlinsert =  N'
    INSERT INTO dbo.' + @tableName + '(_id, ORIGINAL_ID, ' + @columns_list + ')
    SELECT j.[_id], j.[ORIGINAL_ID], ' + @columns_list + '
    FROM ' + @raw_tableName + ' r
    CROSS APPLY OPENJSON(r.raw_data) WITH (
        _id nvarchar(155) '$._id' STRICT,
        ORIGINAL_ID nvarchar(150) '$.ORIGINAL_ID',
        ' + @json_query + '
    ) AS j
';
    BEGIN
        BEGIN TRANSACTION;

        EXECUTE sp_executesql @sqldelete;
        EXECUTE sp_executesql @sqlinsert;

        SELECT @rows_count = @@ROWCOUNT;
        COMMIT;
        END;
END;
GO

问题是我正在处理包含大量数据的表,所以这个存储过程非常慢. 以下是表ACCOUNT的示例数据:

rawACCOUNT:

|raw_id|raw_data
|001   |{"_id":"001","ORIGINAL_ID":"001","CATEGORY":"65","ACCOUNT_OFFICER":"1","OPENING_DATE":"20100322","CURR_NO":"3","DATE_TIME":"2112091237","DEPT_CODE":"13"}|

期望表ACCOUNT:

|ORIGINAL_ID|_id|INSERT_DATE   |ACCOUNT_OFFICER|CATEGORY|CURR_NO|CUSTOMER|DATE_LAST_UPDATE|DATE_TIME |DEPT_CODE|OPENING_DATE|INACTIV_MARKER|
|001        |001|20230906085135|1              |65      |3      |NULL    |NULL            |2112091237|13       |20100322    |NULL          |

我有没有什么优化可以让它运行得更快?

PS:我曾经使用STRING_AGG代替游标,但它向我展示了这一点 错误:

The result of STRING_AGG aggregation has exceeded the 8,000-byte limit. Use LOB types to avoid truncation of the result.

推荐答案

这里不需要光标.只要用STRING_AGG就行了.

您可能还需要_id列的索引.

此外:

  • 对象名称变量应为sysname.
  • 如果报价正确,请使用QUOTENAME.
  • 如果JSON属性名与列名相同,则不需要指定它.
  • 首先将列设置为正确的排序规则,否则它将需要执行完全扫描,因为如果您更改查询内的排序规则,它将无法使用索引.
  • 不要使用变量合并来聚合字符串,这是不可靠的.请改用STRING_AGG.
  • 不要使用NOLOCK,它有严重的数据完整性影响,而且不是一个更快的switch .
  • 您需要使用输出参数从动态SQL中获取行数.
CREATE OR ALTER PROCEDURE dbo.basic_json_normalization_incr
    @tableNamep sysname,
    @tableName sysname,
    @rows_count INT OUTPUT
AS

SET NOCOUNT, XACT_ABORT ON;

DECLARE @raw_tableName NVARCHAR(1000) = 'dbo.' + QUOTENAME('raw' + @tableNamep);
DECLARE @main_tableName NVARCHAR(1000) = 'dbo.' + QUOTENAME(@tableName);
DECLARE @columns_list NVARCHAR(MAX);
DECLARE @json_query NVARCHAR(MAX);
DECLARE @sql NVARCHAR(MAX);

SET @raw_tableName

SELECT @columns_list =
  STRING_AGG(CAST(QUOTENAME(column_name) AS nvarchar(max)), ', ')
FROM dbo.schemaSource
WHERE _enabled = 1
  AND table_name = @tableNamep;

SELECT @json_query =
  STRING_AGG(
    CAST(
      QUOTENAME(column_name)
        + ' nvarchar(max)'
        + CASE WHEN single_mult = 'M' THEN ' AS JSON' END
      AS nvarchar(max)),
    ','
  )
FROM dbo.schemaSource
WHERE _enabled = 1
  AND table_name = @tableNamep;

SET @sql = N'
BEGIN TRAN;

DELETE FROM dbo.' + @main_tableName + '
WHERE _id IN (
    SELECT raw_id
    FROM ' + raw_tableName + '
);

INSERT INTO dbo.' + @main_tableName + '
  (_id, ORIGINAL_ID, ' + @columns_list + ')
SELECT j.[_id], j.[ORIGINAL_ID], ' + @columns_list + '
FROM ' + @raw_tableName + ' r
CROSS APPLY OPENJSON(r.raw_data)
  WITH (
    _id nvarchar(155) '$._id' STRICT,
    ORIGINAL_ID nvarchar(150),
    ' + @json_query + '
  ) AS j;

SET @rows_count = @@ROWCOUNT;

COMMIT;
';

PRINT @sql;  -- Your friend

EXECUTE sp_executesql @sql,
  N'@rows_count int OUTPUT',
  @rows_count = @rows_count OUTPUT;

Sql相关问答推荐

如何在SQL查询中只比较日期时间的年份和月份(而忽略日期比较)?

表名数组

SQL使最终结果显示两个表后的所有数据(匹配和不匹配)?

SQL—如何根据2列填写缺失的值

提高写密集型表的查询性能

我希望以正确的升序获取SQL结果.怎样才能得到它们?

当一个视图在Postgres中失效时?

按分隔符和总和分析字符串

如何设计一个调用嵌套函数并仅在所有被调用的嵌套函数都提交时才提交的事务,例如,如果一个子函数失败则中止?

如何向 mariadb 添加外键?

返回给定日期后的第 4 个工作日(不包括公众假期)

SQL:无重复项的两个聚合函数

IN子句使用的表值用户定义函数参数

在给定的日期范围内填写缺失的日期

编写查询以根据级别 (p2) 返回父位置

Snowflake 中的对象是如何比较的?

如何根据某个值在where子句中添加某个条件

如何对 SQL 表中的连续时间戳进行分组?

PlSql 陷入死循环

在 PostgreSQL 中,如何将数组中的每个元素用作另一个表中的键?