【问题标题】:Preferred way to access data within XML columns in SQL Server在 SQL Server 中访问 XML 列中数据的首选方式
【发布时间】:2011-05-22 19:20:25
【问题描述】:

背景

最近我开始更多地使用 XML 作为 SQL Server 2005 中的列。昨天在一段时间的停机期间,我注意到我使用的两个链接表真的很碍事,这让我很厌烦不得不为几个连接编写更多的支持结构代码。

为了实际为这两个链接表生成数据,我将两个 XML 字段传递给我的存储过程,该存储过程写入主记录,将两个 XML 变量分解为 @tables 并将它们插入到带有新的实际表中SCOPE_IDENTITY() 来自主记录。

经过一段时间后,我决定完全取消这些表,只将 XML 存储在 XML 字段中。现在我知道这里有一些陷阱,比如一般查询性能,GROUP BY 不适用于 XML 数据。查询通常有点乱,但总的来说我喜欢我现在可以在获取数据时使用XElement

另外,这些东西不会改变。这是一次性的,所以我不必担心修改。

我想知道实际获取这些数据的最佳方法。我的很多查询都涉及根据子记录甚至子记录的标准获取主记录。数据库中的大多数存储过程都这样做,但规模要复杂得多,通常需要 UDF 和子查询才能有效工作,但我举了一个简单的例子来测试查询一些数据...

INSERT INTO Customers VALUES ('Tom', '', '<PhoneNumbers><PhoneNumber Type="1" Value="01234 456789" /><PhoneNumber Type="2" Value="01746 482954" /></PhoneNumbers>')
INSERT INTO Customers VALUES ('Andy', '', '<PhoneNumbers><PhoneNumber Type="2" Value="07948 598348" /></PhoneNumbers>')
INSERT INTO Customers VALUES ('Mike', '', '<PhoneNumbers><PhoneNumber Type="3" Value="02875 482945" /></PhoneNumbers>')
INSERT INTO Customers VALUES ('Steve', '', '<PhoneNumbers></PhoneNumbers>')

现在我可以看到两种抓取方式。

方法一

DECLARE @PhoneType INT
SET  @PhoneType = 2

SELECT ct.*
FROM Customers ct
WHERE ct.PhoneNumbers.exist('/PhoneNumbers/PhoneNumber[@Type=sql:variable("@PhoneType")]') = 1

真的吗? sql:variable 感觉有点不健康。但是,它确实有效。然而,以更有意义的方式访问数据显然更加困难。

方法二

SELECT ct.*, pt.PhoneType
FROM Customers ct
  CROSS APPLY ct.PhoneNumbers.nodes('/PhoneNumbers/PhoneNumber') AS nums(pn)
  INNER JOIN PhoneTypes pt ON pt.ID = nums.pn.value('./@Type[1]', 'int')
WHERE nums.pn.value('./@Type[1]', 'int') = @PhoneType

这更像。我已经可以很容易地扩展它来做连接和所有其他的好东西。我之前在表值函数上使用过CROSS APPLY,它非常好。与之前的查询相比,此查询的执行计划非常先进。诚然,我没有对这些表进行任何索引和诸如此类的操作,但这是整个批处理成本的 97%。

方法2(扩展)

SELECT ct.ID, ct.CustomerName, ct.Notes, pt.PhoneType
FROM Customers ct
  CROSS APPLY ct.PhoneNumbers.nodes('/PhoneNumbers/PhoneNumber') AS nums(pn)
  INNER JOIN PhoneTypes pt ON pt.ID = nums.pn.value('./@Type[1]', 'int')
WHERE nums.pn.value('./@Type[1]', 'int') IN (SELECT ID FROM PhoneTypes)

这里有很好的IN 子句。我也可以做类似pt.PhoneType = 'Work'

终于

所以我基本上得到了我想要的结果,但是在使用这种机制来询问少量 XML 数据时我应该注意什么吗?它会在精心搜索期间降低性能吗?存储此类标记样式数据的开销是否太大?

旁注

我过去曾使用过 sp_xml_preparedocumentOPENXML 之类的东西来将列表传递到存储过程中,但相比之下,这就像呼吸新鲜空气!

【问题讨论】:

    标签: sql-server sql-execution-plan cross-apply xml-column


    【解决方案1】:

    我们对存储在 XML 列中的一些关键信息项采取的一种方法是将它们“显示”为“父”表上的计算、持久属性。这是使用一个小存储函数完成的。

    效果很好,因为每次 XML 更改时该值仅计算一次 - 只要它不更改,就不会重新计算,该值会像任何其他列一样存储在表中。

    它也很棒,因为它可以被索引!因此,如果您正在搜索和/或加入这样的领域 - 这就像一个魅力!

    所以你基本上需要一个存储函数:

    CREATE FUNCTION [dbo].[GetPhoneNo1](@DataXML XML)
    RETURNS VARCHAR(50)
    WITH SCHEMABINDING
    AS BEGIN
          DECLARE @result VARCHAR(20)
    
          SELECT 
            @result = @DataXML.value('(/PhoneNumbers/PhoneNumber[@Type="1"]/@Value)[1]', 'VARCHAR(50)')
          RETURN @result
    END
    

    如果你没有类型 1 的电话号码,你只会得到一个 NULL。

    然后,您需要使用计算的持久列来扩展您的父表:

    ALTER TABLE dbo.Customers
       ADD PhoneNumberType1 AS dbo.GetPhoneNo1(PhoneNumbers)
    

    如您所见 - 它适用于单个条目,但不幸的是,您无法显示整个属性列表。但是,如果您有一些关键项目,例如 ID 或其他东西,您希望大多数行都有,那么这可能是一种更轻松、更有效地获取这些信息的非常好的和巧妙的方法。

    【讨论】:

    • 谢谢马克,这是一个非常好的技巧。我从来没有想过这样做!就像你说的那样,我的大部分 xml 数据将采用列表的形式(本质上链接到其他表),这是我不放入正确连接表的懒惰方式,但这对于其他一些人来说是一件好事我拥有的表类型(针对某些记录存储可选参数)。再次感谢,点赞。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多