如何在MSSQL中执行从xml到动态表和列的合并操作

问题描述

我有一个xml,可为我提供表名以及相应的ID列,并根据以下ID来更新或插入该列:

<tables>
    <table>
      <name>Table1</name>
       <attr>
         <id>1</id>
         <columns>
           <col_name>col1</col_name>
           <col_val>123</col_val>
         <columns>
          <columns>
           <col_name>col2</col_name>
           <col_val>345</col_val>
         <columns>
       </attr>
        <attr>
         <id>2</id>
         <columns>
           <col_name>col3</col_name>
           <col_val>123</col_val>
         <columns>
       </attr>
        <attr>
         <id>4</id>
         <columns>
           <col_name>col2</col_name>
           <col_val>123</col_val>
         <columns>
       </attr>
    </table>
        <table>
      <name>Table2</name>
       <attr>
         <id>1</id>
         <columns>
           <col_name>coltb1</col_name>
           <col_val>123</col_val>
         <columns>
          <columns>
           <col_name>coltb3</col_name>
           <col_val>345</col_val>
         <columns>
       </attr>
        <attr>
         <id>3</id>
         <columns>
           <col_name>coltb4</col_name>
           <col_val>123</col_val>
         <columns>
       </attr>
    </table>
    
</tables>

在这种情况下,我有一个表名,该表名可以与在数据库中创建的表匹配,并根据ID列进行匹配,我必须检查ID是否存在(如果存在),则必须更新这些列。出现在具有该值的列节点中,如果不存在,则需要插入到表中。

下面是我到目前为止完成的代码

;WITH CTE (ID,TableName) As (SELECT ROW_NUMBER() OVER (ORDER BY CAST(y.item.query('data(name)') AS NVARCHAR(300))) AS ROWNUM,CAST(y.item.query('data(name)') AS NVARCHAR(300)) AS TableName
                             FROM @input.nodes('/tables/table') y(item))
                             
SELECT  ID,TableName into #tmp FROM CTE

DECLARE @Counter INT,@tableName nvarchar(300)
SET @Counter=1
WHILE (@Counter<=(SELECT Count(*) FROM #tmp))
BEGIN

    SET @tableName=(SELECT TableName FROM #tmp WHERE ID=@Counter)
    ;WITH CTE2(id) AS (SELECT  CAST(x.item.query('data(id)') AS NVARCHAR(30)) AS id
                             FROM @input.nodes('/tables/table') y(item)
                             CROSS APPLY y.item.nodes('./attr') x(item)
                             WHERE CAST(y.item.query('data(name)') AS NVARCHAR(300))=@tableName)
    SELECT * FROM CTE2
    SET @Counter=@Counter+1
END

在这里,我能够获取表名,并且在循环它时,我也获得了ID,但是,我无法找到列名,也无法获取如何合并这些列名,因为它都是动态的

数据库中的表是

表1:

ID|col1|col2|col3|col4|col5
---------------------------
1 |123 |345 |456 |null|89
2 |222 |444 |667 |890 |99

表2

ID|coltb1|coltb2|coltb3|coltb4|coltb5
------------------------------------
1 |786   |678   |880   |99    |788
2 |345   |678   |667   |9990  |008
3 |344   |667   |623   |945   |678

解决方法

您可以将以下内容用作基础:

DECLARE @XML XML = N'<tables><table><name>Table1</name><attr><id>1</id><columns><col_name>col1</col_name><col_val>123</col_val></columns><columns><col_name>col2</col_name><col_val>345</col_val></columns></attr><attr><id>2</id><columns><col_name>col3</col_name><col_val>123</col_val></columns></attr><attr><id>4</id><columns><col_name>col2</col_name><col_val>123</col_val></columns></attr></table><table><name>Table2</name><attr><id>1</id><columns><col_name>coltb1</col_name><col_val>123</col_val></columns><columns><col_name>coltb3</col_name><col_val>345</col_val></columns></attr><attr><id>3</id><columns><col_name>coltb4</col_name><col_val>123</col_val></columns></attr></table></tables>';

DROP TABLE IF EXISTS #DataSource;

CREATE TABLE #DataSource
(
    [table_name] SYSNAME,[id] INT,[column_name] SYSNAME,[column_value] INT
);

WITH DataSource ([table_name],[columns_xml]) AS
(
    SELECT T.c.value('./name[1]','NVARCHAR(128)'),T.c.query('./attr') 
    FROM @XML.nodes('/tables/table') T(c)
)
INSERT INTO #DataSource ([table_name],[id],[column_name],[column_value])
SELECT [table_name],T.c.value('(./id)[1]','INT'),T.c.value('(./columns/col_name)[1]','VARCHAR(12)'),T.c.value('(./columns/col_val)[1]','INT')
FROM DataSource
CROSS APPLY [columns_xml].nodes('./attr') T(c);

SELECT *
FROM #DataSource

DECLARE @current_table_name SYSNAME,@current_columns VARCHAR(MAX),@current_columns_updated VARCHAR(MAX),@DynamicTSQLStatement NVARCHAR(MAX);

WHILE EXISTS(SELECT 1 FROM #DataSource)
BEGIN;

    SELECT TOP 1 @current_table_name = [table_name]
    FROM #DataSource;

    SELECT @current_columns = STRING_AGG(QUOTENAME([column_name]),',')   WITHIN GROUP (ORDER BY [column_name] ASC),@current_columns_updated  = STRING_AGG(QUOTENAME([column_name]) + ' = ISNULL(S.' + QUOTENAME([column_name]) + ',T.' + QUOTENAME([column_name]) + ')',') WITHIN GROUP (ORDER BY [column_name] ASC) 
    FROM #DataSource
    WHERE [table_name] = @current_table_name;

    SET @DynamicTSQLStatement = N'
    WITH DataSource AS
    (
        SELECT *
        FROM
        (
            SELECT [id],[column_value]
            FROM #DataSource
            WHERE [table_name] = ''' + @current_table_name + '''
        ) DS
        PIVOT
        (
            MAX([column_value]) FOR [column_name] IN (' + @current_columns + ')
        ) PVT
    )
    MERGE ' + @current_table_name + ' AS T
    USING DataSource AS S
        ON T.[id] = S.[id]
    WHEN MATCHED THEN  
        UPDATE SET  ' + @current_columns_updated + '
    WHEN NOT MATCHED THEN 
        INSERT ([id],' + @current_columns + ')
        VALUES (S.[id],' + @current_columns + ');';
    
    EXEC sp_executesql @DynamicTSQLStatement;

    DELETE FROM #DataSource
    WHERE [table_name] = @current_table_name;

END;