【问题标题】:Dynamically selecting multiple values from XML in SQL Server在 SQL Server 中从 XML 中动态选择多个值
【发布时间】:2021-10-29 14:25:18
【问题描述】:

我有许多结构完全不同的大型 XML 文档,其中包含许多不同级别的元素,我想从中选择几十个项目。问题是在完全不同的命名空间中有许多不同的文档类型,我都有相似的信息。所以我想让选择数据驱动。

我有一张桌子,我们称之为metadata。想象一下 f1 可能是某人的名字,f2 是他们的帐号,f3 是电话号码,等等:

doctype    fldid     xpath    
---------  --------  --------------------------
abc        f1        /document/g/h[1]
abc        f2        /document/k/j/p/r/p[1]
abc        f3        /document/a/e/d[1]
def        f1        /info/d[1]
def        f2        /info/r/e/d[1]
def        f3        /info/e/s[1]

我们有一个名为mydatatable的数据表:

docid    doctype    docfield
-------  ---------  ------------------------
1        abc        <document><n>.....

所以我想我可以做一个查询:

select 
    metadata.fldid as fldid, 
    mydatatable.docfield.value(metadata.xpath, 'nvarchar(max)') as data
from 
    mydatatable
inner join 
    metadata on mydatatable.doctype = metadata.doctype
where 
    mydatatable.docid = 1

我期待输出:

fldid   data
------  --------------
f1      Tom
f2      344534534
f3      999-555-1212

但是,我得到了这个错误:

XML 数据类型方法“value”的参数 1 必须是字符串字面量。

所以这意味着我需要提前了解所有文档结构,或者我需要在元数据表中游标并将查询放入动态 SQL 中以创建此查询?

请注意,我不是在谈论具有相同元素名称的多个节点。我说的是具有不同(或相似)xpath 的所有不同元素名称。

【问题讨论】:

    标签: sql-server xml


    【解决方案1】:

    是的,您需要动态 SQL 来执行此操作。但是您不需要游标,只需构建查询并一次性执行即可。

    我认为您可能只是构建了一系列 if else XQuery 表达式,在您的情况下代码会生成

    if(sql:column("md.xpath") = "/document/g/h[1]") then '/document/g/h[1]'
    else if(sql:column("md.xpath") = "/document/k/j/p/r/p[1]") then '/document/k/j/p/r/p[1]'
    else if(sql:column("md.xpath") = "/document/a/e/d[1]") then '/document/a/e/d[1]'
    else if(sql:column("md.xpath") = "/info/d[1]") then '/info/d[1]'
    else if(sql:column("md.xpath") = "/info/r/e/d[1]") then '/info/r/e/d[1]'
    else if(sql:column("md.xpath") = "/info/e/s[1]") then '/info/e/s[1]'
    else ()
    

    所以你可以像这样使用动态 SQL:

    我已经参数化了@docid,你不必这样做,你可以像你所做的那样输入文字,但不要直接从其他地方注入数据。

    注意正确使用QUOTENAME 来转义字符串

    DECLARE @xq nvarchar(max) = (
        SELECT STRING_AGG(CAST(
          'if(sql:column("md.xpath") = ' + QUOTENAME(md.xpath, '"') + ') then ' + QUOTENAME(md.xpath, '''')
          AS nvarchar(max)), '
    else ')
          + '
    else ()'
        FROM metadata md
    );
    
    DECLARE @sql nvarchar(max) = N'
    select
      md.fldid,
      dt.docfield.value(''(
    ' + @xq + ''')[1]'',''nvarchar(max)'') as data
    from mydatatable dt
    inner join metadata md on dt.doctype = md.doctype
    where dt.docid = @docid;
    ';
    
    PRINT @sql;  -- for testing
    
    DECLARE @docid int = 1;
    
    EXEC @sp_executesql
      @sql,
      N'@docid int',
        @docid = @docid;
    

    【讨论】:

    • 这不可能扩展。实际应用中字段多于3个,doctypes多于2个。我希望元数据表中有 200-300 个条目。如果否则将变得非常巨大。而且我没有在这个问题中包含所有的命名空间杂耍。
    • 另外,我使用的是 SQL 2016。STRING_AGG 于 2017 年首次推出。
    猜你喜欢
    • 1970-01-01
    • 2013-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-15
    • 1970-01-01
    • 2017-02-05
    相关资源
    最近更新 更多