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

在Azure Data Factory中将嵌套XML键值数组转换为单行

实现XML发票行到SQL表的动态转换方案

根据你的需求,需要将XML中每行发票数据(包含line属性、product属性及动态的pos_record/data键值对)转换为SQL表的单行记录,以下是几种常用的实现方式:


1. SQL Server 内置XML直接处理

如果XML已存储在SQL Server表中,可通过XQuery+动态SQL实现动态列转换:

步骤与代码示例

假设XML存储在InvoiceData表的XmlContent列:

-- 1. 提取所有唯一的data key,生成动态列名(带product_pos_前缀)
DECLARE @Columns NVARCHAR(MAX)
SELECT @Columns = STRING_AGG(DISTINCT QUOTENAME('product_pos_' + c.value('@key', 'NVARCHAR(100)')), ', ')
FROM InvoiceData
CROSS APPLY XmlContent.nodes('/invoice/lines/line/product/pos_record/data') AS t(c)

-- 2. 动态生成行转列查询
DECLARE @Query NVARCHAR(MAX) = N'
SELECT 
    ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS id,
    line.value(''@quantity'', ''DECIMAL(10,3)'') AS qty,
    line.value(''@unit'', ''NVARCHAR(10)'') AS unit,
    product.value(''@name'', ''NVARCHAR(255)'') AS name,
    product.value(''@product_type'', ''NVARCHAR(50)'') AS product_type,
    ' + @Columns + '
FROM InvoiceData
CROSS APPLY XmlContent.nodes(''/invoice/lines/line'') AS t(line)
CROSS APPLY line.nodes(''product'') AS p(product)
CROSS APPLY product.nodes(''pos_record/data'') AS d(data)
PIVOT (
    MAX(data.value(''@value'', ''NVARCHAR(255)''))
    FOR data.value(''@key'', ''NVARCHAR(100)'') IN (' + REPLACE(@Columns, 'product_pos_', '') + ')
) AS PivotTable'

EXEC sp_executesql @Query

2. SSIS 数据流实现

若用SSIS做ETL,可通过以下步骤实现动态转换:

  • XML源组件:读取XML,设置XPath为/invoice/lines/line,提取line的quantity/unit、product的name/product_type,以及pos_record下data的key/value。
  • 脚本组件(动态列处理):由于SSIS透视转换默认需固定列,用C#脚本实现动态列生成与赋值:
using System;
using System.Data;
using System.Xml.Linq;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;

[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
    private string[] _dynamicColumns;

    public override void PreExecute()
    {
        base.PreExecute();
        // 从XML中提取所有唯一的data key,生成带前缀的列名
        var xml = XDocument.Parse(Variables.InvoiceXml);
        var keys = xml.Descendants("data").Select(d => (string)d.Attribute("key")).Distinct();
        _dynamicColumns = keys.Select(k => "product_pos_" + k).ToArray();
        
        // 动态添加输出列
        foreach (var col in _dynamicColumns)
        {
            Output0Buffer.AddColumn(col, DataType.DT_WSTR, 255);
        }
    }

    public override void Input0_ProcessInputRow(Input0Buffer Row)
    {
        var lineXml = XElement.Parse(Row.LineXml);
        // 填充固定列
        Row.id = Row.Id;
        Row.qty = (decimal)lineXml.Attribute("quantity");
        Row.unit = (string)lineXml.Attribute("unit");
        var product = lineXml.Element("product");
        Row.name = (string)product.Attribute("name");
        Row.product_type = (string)product.Attribute("product_type");
        
        // 填充动态列
        var posData = product.Element("pos_record").Elements("data")
            .ToDictionary(d => "product_pos_" + (string)d.Attribute("key"), d => (string)d.Attribute("value"));
        
        foreach (var col in _dynamicColumns)
        {
            Output0Buffer.SetString(col, posData.TryGetValue(col, out var val) ? val : string.Empty);
        }
    }
}

3. Python 脚本自定义处理

适合灵活的自定义数据流场景,用xml.etree解析+pandas透视:

import xml.etree.ElementTree as ET
import pandas as pd

# 解析XML文件
tree = ET.parse('invoice.xml')
root = tree.getroot()

# 提取每行发票数据
rows = []
for line in root.findall('.//line'):
    # 基础字段
    row = {
        'qty': float(line.get('quantity')),
        'unit': line.get('unit'),
        'name': line.find('product').get('name'),
        'product_type': line.find('product').get('product_type')
    }
    # 提取pos_record动态键值对
    pos_record = line.find('.//pos_record')
    if pos_record:
        for data in pos_record.findall('data'):
            key = f'product_pos_{data.get("key")}'
            row[key] = data.get('value')
    rows.append(row)

# 转换为DataFrame,自动适配动态列
df = pd.DataFrame(rows)
df['id'] = df.index + 1  # 添加自增id

# 输出到SQL表(需安装sqlalchemy)
# from sqlalchemy import create_engine
# engine = create_engine('mssql+pyodbc://server/database?driver=ODBC+Driver+17+for+SQL+Server')
# df.to_sql('InvoiceLines', engine, if_exists='replace', index=False)

print(df)

核心注意事项

  • 动态列兼容性:所有方案都会自动收集所有出现过的data key,即使部分行无对应key也会填充空值,适配未来key的变化。
  • 数据类型转换:根据SQL表需求,需将数值型字段(如quantity、tax)转换为对应类型,避免存储为字符串。
  • 多发票处理:确保提取逻辑会遍历所有发票的line节点,不会混淆不同发票的行数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 02:23:14