【问题标题】:Converting a single XML bracket column to multiple columns in T-SQL SQL-SERVER在 T-SQL SQL-SERVER 中将单个 XML 括号列转换为多列
【发布时间】:2020-06-22 03:31:35
【问题描述】:

我看到过类似的问题,但 XML 格式一直不同,因为我的 XML 格式不遵循“标准”结构。我的表如下所示(以 XML 括号作为行值的单列):

|VAL|
|<person name="bob" age="22" city="new york" occupation="student"></person>|
|<person name="bob" age="22" city="new york" occupation="student"></person>|

而我正在寻找的结果是:

|Name|age|city    |occupation|
|bob |22 |new york|student   |
|bob |22 |new york|student   |

我可以使用这些列名创建硬编码脚本,但问题是我有超过 20 个表,这些表都需要自定义脚本。我的想法是,有一种方法可以让我动态地考虑到目标表和源表 (xml),我可以有一个生成这些数据的过程。

【问题讨论】:

    标签: sql sql-server tsql stored-procedures dynamic


    【解决方案1】:

    你的问题还不是很清楚...

    据我了解,您有各种各样的不同 XML,并且您想通用地阅读它们。如果这是真的,我建议您的下一个问题,在您的示例数据中反映这一点。

    一个普遍的说法是:如果您想动态设置结果集的 descriptive 元素(在这种情况下:列的名称),则无法绕过动态创建的语句. T-SQL 依赖于您必须提前知道的一些事情

    试试这个:

    我设置了一个样机场景来模拟您的问题(请在您的下一个问题中尝试自己这样做):

    DECLARE @tbl TABLE(ID INT IDENTITY, Descr VARCHAR(100), VAL XML);
    INSERT INTO @tbl VALUES
     ('One person',N'|<person name="bob" age="22" city="new york" occupation="student"></person>')
    ,('One more person','<person name="bob" age="22" city="new york" occupation="student"></person>')
    ,('One country','<country name="Germany" capital="Berlin" continent="Europe"></country>');
    

    --此查询依赖于预先知道的所有可能属性。
    --common 属性,如name,返回一个人和一个国家
    --differing 属性返回为 NULL。 -- 一个优点可能是,如果合适,您可以使用特定的数据类型。

    SELECT t.ID 
          ,t.Descr
          ,t.VAL.value('(/*[1]/@name)[1]','nvarchar(max)') AS [name] 
          ,t.VAL.value('(/*[1]/@age)[1]','nvarchar(max)') AS [age] 
          ,t.VAL.value('(/*[1]/@city)[1]','nvarchar(max)') AS [city] 
          ,t.VAL.value('(/*[1]/@occupation)[1]','nvarchar(max)') AS [occupation] 
          ,t.VAL.value('(/*[1]/@city)[1]','nvarchar(max)') AS [city] 
          ,t.VAL.value('(/*[1]/@capital)[1]','nvarchar(max)') AS [capital] 
          ,t.VAL.value('(/*[1]/@continent)[1]','nvarchar(max)') AS [continent] 
    FROM @tbl t;
    

    --此查询返回经典 EAV(实体属性值)列表
    --在这个结果中,您可以在自己的行中获得每个属性

    SELECT t.ID 
          ,t.Descr
          ,A.attrs.value('local-name(.)','nvarchar(max)') AS AttrName
          ,A.attrs.value('.','nvarchar(max)') AS AttrValue
    FROM @tbl t
    CROSS APPLY t.VAL.nodes('/*[1]/@*') A(attrs);
    

    这两种方法都可能生成为字符串级别的语句,然后由EXEC()sp_executesql 执行。

    提示:一种方法可能是将 EAV 列表插入到容错暂存表中,然后使用 条件聚合PIVOT 或硬编码的 VIEW 从​​那里继续。

    动态方法

    为了读取&lt;person&gt; 元素,我们需要这个:

    SELECT t.ID 
          ,t.Descr
          ,t.VAL.value('(/*[1]/@name)[1]','nvarchar(max)') AS [name] 
          ,t.VAL.value('(/*[1]/@age)[1]','nvarchar(max)') AS [age] 
          ,t.VAL.value('(/*[1]/@city)[1]','nvarchar(max)') AS [city] 
          ,t.VAL.value('(/*[1]/@occupation)[1]','nvarchar(max)') AS [occupation] 
    FROM @tbl t
    WHERE VAL.value('local-name(/*[1])','varchar(100)')='person';
    

    我们所要做的就是生成变化的部分:

    试试这个:

    一个带有真实桌子的新模型

    CREATE TABLE SimulateYourTable(ID INT IDENTITY, Descr VARCHAR(100), VAL XML);
    INSERT INTO SimulateYourTable VALUES
     ('One person',N'|<person name="bob" age="22" city="new york" occupation="student"></person>')
    ,('One more person','<person name="bob" age="22" city="new york" occupation="student"></person>')
    ,('One country','<country name="Germany" capital="Berlin" continent="Europe"></country>');
    

    --过滤&lt;person&gt;实体

    DECLARE @entityName NVARCHAR(100)='person';
    

    --这是一个代表命令的字符串

    DECLARE @cmd NVARCHAR(MAX)=
    'SELECT t.ID 
          ,t.Descr
          ***columns here***
    FROM SimulateYourTable t
    WHERE VAL.value(''local-name(/*[1])'',''varchar(100)'')=''***name here***''';
    

    --有了这个我们可以创建所有的列
    --提示:使用 SQL Server 2017+ 有 STRING_AGG() - 更简单!

    DECLARE @columns NVARCHAR(MAX)=
    (
        SELECT CONCAT(',t.VAL.value(''(/*[1]/@',Attrib.[name],')[1]'',''nvarchar(max)'') AS ',QUOTENAME(Attrib.[name]))
        FROM SimulateYourTable t
        CROSS APPLY t.VAL.nodes('//@*') AllAttrs(a)
        CROSS APPLY (SELECT a.value('local-name(.)','varchar(max)')) Attrib([name])
        WHERE VAL.value('local-name(/*[1])','varchar(100)')=@entityName
        GROUP BY Attrib.[name]
        FOR XML PATH(''),TYPE
    ).value('.','nvarchar(max)'); 
    

    --现在我们把它塞进我们的命令中

    SET @cmd=REPLACE(@cmd,'***columns here***',@columns);
    SET @cmd=REPLACE(@cmd,'***name here***',@entityName);
    

    --这是命令。
    --提示:您可以使用它来创建物理视图,而无需输入...

    PRINT @cmd;
    

    您可以使用EXEC(@cmd)来执行这条动态SQL并检查结果。

    【讨论】:

    • 我在 EAV 营地。很多人认为这是邪恶的。有时这是谨慎的选择
    • 您好,感谢您的回复。抱歉,我的问题不清楚。我的想法是我有 50 个具有不同字段(即城市、首都、大陆等)的 XML,并且所有这些都需要写入 SQL 表,但您似乎理解了这个问题。你是说我必须为每个表创建一个单独的查询吗?我仍在测试解决方案,因此很抱歉尚未接受答案。
    • @Anton,如果您必须为每个表编写一个单独的查询,这取决于您的需要...... EAV 方法可能就足够了......但如果你想要一个具有动态命名列的特定结果集,您将需要一个特定查询。如果您必须键入此查询再次取决于您的需要:-)。所有这些都可以完全动态地为每个案例进行分析和生成。如果您有一组已知的结构,我倾向于将 EAV 创建为临时表和该表上方的特定视图。
    • @JohnCappelletti 感谢您谨慎这个词。我现在将这个术语应用到我的活跃词汇中:-)
    • @Anton 这又取决于您的需求和数据。您可以为临时表使用SPARSE 列来减少所需的空间...您可以使用查询作为我的第一种方法,但对每种 XML 使用一个查询,并在需要时直接从 XML 中读取数据。您多久需要一次?这是在改变数据吗?多少行?共有多少列?还有很多问题...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-01
    • 1970-01-01
    相关资源
    最近更新 更多