SQL Server 从不带标记名称的 XML 列中提取数据

SQL Server extract data from XML column without tag names

提问人:goryef 提问时间:2/4/2023 最后编辑:marc_sgoryef 更新时间:2/5/2023 访问量:218

问:

我有一个XML字符串:

<XML>
 <xml_line>
   <col1>1</col1>
   <col2>foo 1</col2>
 </xml_line>
 <xml_line>
   <col1>2</col1>
   <col2>foo 2</col2>
 </xml_line>    
</XML>

我通过将该字符串存储在 SQL Server 表中并解析它来从该字符串(存储在 )中提取数据:@data_xml

-- create temp table, insert XML string 
CREATE TABLE table1 (data_xml XML)

INSERT table1 
    SELECT @data_xml

-- parse XML string into temp table
SELECT
    N.C.value('col1[1]', 'int') col1_name,
    N.C.value('col2[1]', 'varchar(31)') col2_name,
FROM 
    table1
CROSS APPLY 
    data_xml.nodes('//xml_line') N(C)

我想知道是否有一种通用方法可以在不指定列名的情况下完成相同的操作(即 col1[1]、col2[1])

SQL Server XML XML 分析 节点

评论

1赞 John Cappelletti 2/4/2023
SQL Server 在设计上是声明性的。动态列需要动态 SQL
1赞 John Cappelletti 2/4/2023
只是为了添加到我的评论中,您可以对 XML 进行哈希处理并创建层次结构。这可能比你要找的要多,但可以删减。dbfiddle.uk/sE36tDoK
1赞 goryef 2/4/2023
@JohnCappelletti 谢谢。我看到这里接近 stackoverflow.com/questions/61755806/......。我可以与之合作。似乎和你的相似。但希望有一个更简单的解决方案。
1赞 John Cappelletti 2/4/2023
Shnugo 是 XML 之王(老实说,他是 XML 的忠实粉丝)。这种方法是我函数的缩小版本。您可能还注意到,它不支持属性或复杂结构。另外,我还需要指出,这两种解决方案,列都不是动态的。

答:

1赞 T N 2/4/2023 #1

您可以使用类似以下内容的内容:

SELECT
    N.C.value('let $i := . return count(//xml_line[. << $i]) + 1', 'int') as LineNumber,
    Item.Node.value('local-name(.)', 'varchar(max)') name,
    Item.Node.value('.', 'varchar(max)') value
FROM 
    table1
CROSS APPLY 
    data_xml.nodes('//xml_line') N(C)
CROSS APPLY 
    N.C.nodes('*') Item(Node)

要获取:

行号 名字 价值
1 col1 1
1 col2 福 1
2 col1 2
2 col2 福 2

请参阅此 db<>fiddle

但是,若要水平分布列,需要在查询不同的元素名称后生成动态 SQL。

附录:这是一个更新的 db<>fiddle,它还显示了一个动态 SQL 示例。

上面将所有值映射为 。如果您有数据,则可以进行适当的更改。如果需要将特定列映射到特定类型,则需要显式定义和填充名称到类型的映射表,并将其合并到动态 SQL 逻辑中。如果您希望结果列按特定顺序排列,则可能需要这样做。VARCHAR(MAX)NVARCHAR

附录 2:此更新的 db<>fiddle 现在包括列类型和排序逻辑。

--------------------------------------------------
-- Extract column names
--------------------------------------------------

DECLARE @Names TABLE (name VARCHAR(100))

INSERT @Names   
SELECT DISTINCT Item.Node.value('local-name(.)', 'varchar(max)')
FROM table1
CROSS APPLY data_xml.nodes('//xml_line/*') Item(Node)

--SELECT * FROM @Names

--------------------------------------------------
-- Define column-to-type mapping
--------------------------------------------------

DECLARE @ColumnTypeMap TABLE ( ColumnName SYSNAME, ColumnType SYSNAME, ColumnOrder INT)
INSERT @ColumnTypeMap
VALUES
    ('col1', 'int', 1),
    ('col2', 'varchar(10)', 2)

DECLARE @ColumnTypeDefault SYSNAME = 'varchar(max)'

--------------------------------------------------
-- Define SQL Templates
--------------------------------------------------

DECLARE @SelectItemTemplate VARCHAR(MAX) =
'    , N.C.value(<colpath>, <coltype>) <colname>
'

DECLARE @SqlTemplate VARCHAR(MAX) =
'SELECT
    N.C.value(''let $i := . return count(//xml_line[. << $i]) + 1'', ''int'') as LineNumber
<SelectItems>
FROM 
    table1
CROSS APPLY 
    data_xml.nodes(''//xml_line'') N(C)
'

--------------------------------------------------
-- Expand SQL templates into SQL
--------------------------------------------------

DECLARE @SelectItems VARCHAR(MAX) = (
    SELECT STRING_AGG(SI.SelectItem, '')
              WITHIN GROUP(ORDER BY ISNULL(T.ColumnOrder, 999), N.Name)
    FROM @Names N
    LEFT JOIN @ColumnTypeMap T ON T.ColumnName = N.name
    CROSS APPLY (
        SELECT SelectItem = REPLACE(REPLACE(REPLACE(
            @SelectItemTemplate
            , '<colpath>', QUOTENAME(N.name + '[1]', ''''))
            , '<colname>', QUOTENAME(N.name))
            , '<coltype>', QUOTENAME(ISNULL(T.ColumnType, @ColumnTypeDefault), ''''))
    ) SI(SelectItem)
)

DECLARE @Sql VARCHAR(MAX) = REPLACE(@SqlTemplate, '<SelectItems>', @SelectItems)

--------------------------------------------------
-- Execute
--------------------------------------------------

SELECT DynamicSql = @Sql

EXEC (@Sql)

结果(带有一些附加数据):

行号 col1 col2 酒吧
1 1 福 1 更多
2 2 福 2 东西

评论

0赞 T N 2/4/2023
我已经更新了上面的内容,以包含一个包含动态 SQL 示例的小提琴。