在SQL Server中访问XML列中的数据的首选方法

时间:2010-12-09 11:13:53

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

背景

最近我开始使用XML作为SQL Server 2005中的一个列。在昨天的一段停机时间里,我注意到我使用的两个链接表真的只是在路上而且让我烦恼泪水必须为几个连接写出更多的支持结构代码。

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

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

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

我想知道实际获取此数据的最佳方法。我的很多疑问都涉及根据孩子或甚至子记录的标准获得主记录。数据库中的大多数sprocs都是这样做的,但是规模要大得多,通常需要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>')

现在我可以看到两种方法来抓住它。

方法1

DECLARE @PhoneType INT
SET  @PhoneType = 2

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

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

方法2

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这样的东西来将列表传递给sprocs,但这相当于呼吸新鲜空气!

1 个答案:

答案 0 :(得分:2)

我们对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或者其他东西,你期望你的大多数行都有,那么这可以是一种非常好用的方式,可以更容易,更有效地获取这些信息。