SQL Server 2000的FOR XML查询简介

来源:互联网 发布:des算法主要内容 编辑:程序博客网 时间:2024/06/01 08:25

由于XML本身的诸多优点,XML技术已被广泛的使用,目前的好多软件技术同XML紧密相关,比如微软的.net 平台对xml提供了强大的支持,提供System.Xml以及其子命名空间下的类型来操作xmlAdo.net通过核心类型DataSet出色的把关系型数据库同xml进行了紧密集成。由于平常许多开发人员使用.net 来操作Sql server的到数据集后再转换成xml,所以往往忽略Transact-SQL查询生成XML数据的强大功能。对于一些项目使用XML查询直接通过SQL生成xml会来的更为简便,所以我通过在实际项目中的使用和查阅一些资料写成一个知识点,一是温故而知新,二是对于一些开发者刚好需要这方面的技术而还没有找到比较快捷的学习方式提供一条途径。

SQL SERVER 2000中查询生成XML的语法表达式比较简洁,整个语法如下:

SELECT <select_list>

FROM <table_source>

WHERE <search_condition>

FOR XML AUTO | RAW | EXPLICIT [,XMLDATA ] [,ELEMENTS] [,BINARY BASE64]

下面我将以Northwind数据库来演示上面的表达式中所包含的各项功能,下面的查询语句和返回结果都通过SQL SERVER 2000查询分析器来执行和得到。

 

一. 使用AUTO模式

该模式我认为在生成单表xml数据方面是用得最多的,能满足一般的需要。先来看他的简单查询。

1.简单查询

查询语句:

SELECT CategoryID,

CategoryName

FROM Categories

WHERE CategoryID < 3 FOR XML AUTO

返回结果:

<Categories CategoryID="1" CategoryName="Beverages"/>

<Categories CategoryID="2" CategoryName="Condiments"/>

也可以使用别名,

查询语句:

SELECT CategoryID AS ID,

CategoryName,

GetDate() as CurrDate

FROM Categories MyTable

WHERE CategoryID < 3 FOR XML AUTO

返回结果:

<MyTable ID="1" CategoryName="Beverages" CurrDate="2005-06-24T11:09:52.937"/>

<MyTable ID="2" CategoryName="Condiments" CurrDate="2005-06-24T11:09:52.937"/>

2.连接查询

以两个表为例,

查询语句:

SELECT Categories.CategoryID,

Categories.CategoryName,

ProductID,

ProductName

FROM Categories 

JOIN Products ON Categories.CategoryID = Products.CategoryID AND ProductID <5

WHERE Categories.CategoryID < 3 FOR XML AUTO

返回结果:

<Categories CategoryID="1" CategoryName="Beverages">

<Products ProductID="1" ProductName="Chai"/>

</Categories>

<Categories CategoryID="2" CategoryName="Condiments">

<Products ProductID="2" ProductName="Chang"/>

</Categories>

可以看到表连接查询可以生成分层次的Xml,不过需要注意的是SELECT子句中的父表的列要排在子表的列的前面,否则会出现你不想看到的结果,如:

查询语句:

SELECT ProductID,Categories.CategoryID,Categories.CategoryName,ProductName

FROM Categories 

JOIN Products ON Categories.CategoryID = Products.CategoryID  and ProductID <5

WHERE Categories.CategoryID <3  FOR XML AUTO

返回结果:

<Products ProductID="1" ProductName="Chai">

         <Categories CategoryID="1" CategoryName="Beverages"/>

</Products>

         <Products ProductID="2" ProductName="Chang">

         <Categories CategoryID="1" CategoryName="Beverages"/>

</Products>

<Products ProductID="3" ProductName="Aniseed Syrup">

         <Categories CategoryID="2" CategoryName="Condiments"/>

</Products>

<Products ProductID="4" ProductName="Chef Anton&apos;s Cajun Seasoning">

         <Categories CategoryID="2" CategoryName="Condiments"/>

</Products>

3.使用ELEMENTS选项

使用该选项可以生成以元素为中心的Xml表示,默认为属性方式,不过属性方式节省空间。需要注意的是使用ELEMENTS选项是一种全是或全否的形式,不能得到一部分是以元素表示而另一部分以属性表示的Xml数据。

查询语句:

SELECT CategoryID,

CategoryName

FROM Categories

WHERE CategoryID < 3

FOR XML AUTO, ELEMENTS

返回结果:

<Categories>

         <CategoryID>1</CategoryID>

         <CategoryName>Beverages</CategoryName>

</Categories>

<Categories>

         <CategoryID>2</CategoryID>

         <CategoryName>Condiments</CategoryName>

</Categories>

在连接查询时,

查询语句:

SELECT Categories.CategoryID,

ProductID,

ProductName

FROM Categories 

JOIN Products ON Categories.CategoryID = Products.CategoryID  and ProductID <4

WHERE Categories.CategoryID <3  FOR XML AUTO, ELEMENTS

返回结果:

<Categories>

         <CategoryID>1</CategoryID>

         <Products>

<ProductID>1</ProductID>

                   <ProductName>Chai</ProductName>

         </Products>

         <Products>

                   <ProductID>2</ProductID>

                   <ProductName>Chang</ProductName>

         </Products>

</Categories>

<Categories>

         <CategoryID>2</CategoryID>

         <Products>

                   <ProductID>3</ProductID>

                   <ProductName>Aniseed Syrup</ProductName>

         </Products>

</Categories>

4.检索对二进制数据的XPath引用

这是对二进制数据的操作,

查询语句:

SELECT CategoryID,

Picture

FROM Categories

WHERE CategoryID = 1

FOR XML AUTO

返回结果:

<Categories CategoryID="1" Picture="dbobject/Categories[@CategoryID='1']/@Picture"/>

使用ELEMENTS方式,

查询语句:

SELECT CategoryID,

Picture

FROM Categories

WHERE CategoryID = 1

FOR XML AUTO,ELEMENTS

返回结果:

<Categories>

         <CategoryID>1</CategoryID>

         <Picture>dbobject/Categories[@CategoryID='1']/@Picture</Picture>

</Categories>

 

二.使用RAW模式

使用RAW模式不能使用ELEMENTS选项。

1.简单查询

查询语句:

SELECT CategoryID,

CategoryName AS Nanme

FROM Categories

WHERE CategoryID < 3

ORDER BY CategoryID DESC

FOR XML RAW

返回结果:

<row CategoryID="2" Nanme="Condiments"/>

<row CategoryID="1" Nanme="Beverages"/>

2.连接查询

查询语句:

SELECT Categories.CategoryID,

Categories.CategoryName,

ProductID,

ProductName

FROM Categories 

JOIN Products ON Categories.CategoryID = Products.CategoryID  and ProductID <4

WHERE Categories.CategoryID <= 2  FOR XML RAW

返回结果:

<row CategoryID="1" CategoryName="Beverages" ProductID="1" ProductName="Chai"/>

<row CategoryID="1" CategoryName="Beverages" ProductID="2" ProductName="Chang"/>

<row CategoryID="2" CategoryName="Condiments" ProductID="3" ProductName="Aniseed Syrup"/>

 

三.使用EXPLICIT模式

该模式使用起来相对比较复杂,不过它可以很灵活的控制返回的xml数据结构。在该查询中定义了两个表示元数据的额外列。Tag列唯一的确定用来在结果中表示每一行的xml标记,Parent列用来控制元素之间的嵌套关系。

1.使用通用表

EXPLICIT模式下有一个通用表的概念,使用数据列的名称来定义xml文档中的数据。结构如下:

ElementName!TagNumber!AttributeName!Directive

下面的说明来自联机丛书,

ElementName

是所得到的元素类属标识符(例如,如果将 Customers 指定为ElementName,则 <Customers> 是元素标记)。

TagNumber

是元素的标记号。借助于通用表中的两个元数据列(Tag  Parent),TagNumber用于表示 XML 树中的 XML 元素嵌套。每个TagNumber都准确对应于一个ElementName

AttributeName

 XML 特性的名称(如果没有指定Directive)或包含的元素名(如果Directive xmlcdata  element)。如果指定Directive,则AttributeName可以为空。这种情况下,列中包含的值直接由具有指定ElementName的元素所包含。

Directive

是可选命令。如果没有指定Directive,则必须指定AttributeName。如果没有指定AttributeName且没有指定Directive(如Customer!1),则表示元素命令(如 Customer!1!!element)且包含数据。

Directive有两种用途。该选项用于分别使用关键字 IDIDREF  IDREFS  IDIDREF  IDREFS 进行编码。还用于表示如何使用关键字 hideelementxmlxmltext  cdata 将字符串数据映射到 XML。大多数情况下允许在这些组中组合指令,但是不能在组本身中进行组合。

ID

可将元素特性指定为 ID 类型的特性。然后可以使用 IDREF  IDREFS 特性引用它们,以启用文档内的链接。但是,如果没有请求XMLDATA,则此关键字无效。

IDREF

指定为 IDREF 的特性可用于引用 ID 类型的特性,以启用文档内的链接。但是,如果没有请求 XMLDATA,则此关键字无效。

IDREFS

指定为 IDREFS 的特性可用于引用 ID 类型的特性,以启用文档内的链接。但是,如果没有请求 XMLDATA,则此关键字无效。

hide

不显示特性。这对于按照不出现在结果中的特性对结果进行排序可能很有用。

element

不生成特性。而是生成具有指定名称的包含元素(如果没有指定特性名则直接生成包含元素)。包含数据被编码为实体(例如,字符 < 变成 &lt;)。该关键字可以与 IDIDREF  IDREFS 组合。

xml

除了不进行实体编码外,该命令与元素命令相同(例如,字符 < 仍是 <)。除了 hide 外,该命令不能与任何其它命令一起使用。

xmltext

列内容应包在单个标记内,以便与文档的其它部分集成。如果指定了AttributeName,则标记名由指定名称替换;否则通过将内容放在容器的起始处而不进行实体编码,将特性追加到包含元素的当前特性列表。含有该命令的列必须是文本类型(varcharnvarcharcharnchartextntext)。该命令只能与 hide 一起使用。在提取存储在列中的溢出数据时该命令很有用。如果内容不是有效的 XML,则该行为不明确。

cdata

通过用 CDATA 节环绕数据来包含数据。不对内容进行实体编码。原始数据类型必须是文本类型(varcharnvarchartextntext)。该命令只能与 hide 一起使用。当使用该命令时,不应指定AttributeName

2.简单查询

在指定数据所在的元素的名称和标记时必须使用ElementName参数和Tag参数,也就是查询语句中表中存在的每一个实际的数据列都必须以ElementName!Tag开头。在没有指定AttributeName!Directive的情况下以元素值的形式返回。在只查询一个表的情况下Tag1ParentNULL

使用EXPLICIT模式来查询数据我认为使用倒推的方式会比较方便一些,比如我要得到如下结构,

<Category ID=CategoryID Name=CategoryName>Description</Category>

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

CategoryName AS [Category!1!Name],

Description AS [Category!1]

FROM Categories

WHERE categoryID > 6 FOR XML EXPLICIT

返回结果:

<Category ID="7" Name="Produce">Dried fruit and bean curd</Category>

<Category ID="8" Name="Seafood">Seaweed and fish</Category>

当然下面的语句同上面的等效,

SELECT 1 Tag,NULL Parent,

CategoryID  [Category!1!ID],

CategoryName  [Category!1!Name],

Description  [Category!1]

FROM Categories

WHERE categoryID > 6 FOR XML EXPLICIT

2.连接查询

EXPLICIT模式下的多表连接查询可能相对比较复杂些,先来看一个简单的例子,

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

Categories.CategoryID AS [Category!1!ID],

Categories.CategoryName AS [Category!1!!element],

ProductID  AS [Category!1!ProductID!element],

ProductName AS  [Category!1!ProductName!element]

FROM Categories 

JOIN Products ON Categories.CategoryID = Products.CategoryID AND ProductID <5

WHERE Categories.CategoryID < 3 FOR XML EXPLICIT

返回结果:

<Category ID="1">

         Beverages

         <ProductID>1</ProductID>

         <ProductName>Chai</ProductName>

</Category>

<Category ID="1">

         Beverages

         <ProductID>2</ProductID>

         <ProductName>Chang</ProductName>

</Category>

<Category ID="2">

         Condiments

         <ProductID>3</ProductID>

         <ProductName>Aniseed Syrup</ProductName>

</Category>

<Category ID="2">

         Condiments

         <ProductID>4</ProductID>

         <ProductName>Chef Anton&apos;s Cajun Seasoning</ProductName>

</Category>

从上面的结构可以看出数据的xml结构不是很舒服,存在比较大的冗余。想象中改进后的结构如下:

<Category ID="2" Name="Condiments">

         <Product>

                   <ProductID>3</ProductID>

                   <ProductName>Aniseed Syrup</ProductName>

         </Product>

         <Product>

                   <ProductID>4</ProductID>

                   <ProductName>Chef Anton&apos;s Cajun Seasoning</ProductName>

         </Product>

</Category>

<Category ID="1" Name="Beverages">

         <Product>

                   <ProductID>1</ProductID>

                   <ProductName>Chai</ProductName>

         </Product>

         <Product>

                   <ProductID>2</ProductID>

                   <ProductName>Chang</ProductName>

         </Product>

</Category>

从上面看出Category元素的Tag1ParentNULL,而Product元素的Tag2Parent1(即CategoryTag值)。这里有一个问题需要解决,那就是在TagParent中存在两套值,这里就需要使用UNION ALL运算符来实现这项功能,UNION ALL运算符的一项功能就是消除查询返回的重复的行。使用UNION ALL时需要注意的是结果集的列数必须相同。

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

CategoryName AS [Category!1!Name],

NULL  AS [Product!2!ProductID!element],

NULL  AS [Product!2!ProductName!element]

FROM Categories  WHERE CategoryID < 3

UNION ALL

SELECT 2 AS Tag,1 AS Parent,

Categories.CategoryID AS [Category!1!ID],

Categories.CategoryName AS [Category!1!Name],--这里可以使用NULL代替

ProductID  AS [Product!2!ProductID!element],

ProductName  AS [Product!2!ProductName!element]

FROM Categories

JOIN Products ON Categories.CategoryID = Products.CategoryID AND ProductID <5

WHERE Categories.CategoryID < 3

ORDER BY [Category!1!ID] DESC,[Product!2!ProductID!element] ASC

FOR XML EXPLICIT

返回结果:

<Category ID="2" Name="Condiments">

         <Product>

                   <ProductID>3</ProductID>

                   <ProductName>Aniseed Syrup</ProductName>

         </Product>

         <Product>

                   <ProductID>4</ProductID>

                   <ProductName>Chef Anton&apos;s Cajun Seasoning</ProductName>

         </Product>

</Category>

<Category ID="1" Name="Beverages">

         <Product>

                   <ProductID>1</ProductID>

                   <ProductName>Chai</ProductName>

         </Product>

         <Product>

                   <ProductID>2</ProductID>

                   <ProductName>Chang</ProductName>

         </Product>

</Category>

不带FOR XML EXPLICIT语句的结果为:

Tag  Parent      Category!1! ID Category!1!Name         Product!2!            Product!2!          

                                                                             ProductID!element      ProductName!element 

----   -------        ----------           ------------           --------------------------            ----------------------------

1    NULL    2             Condiments     NULL                      NULL

2    1         2             Condiments      3                            Aniseed Syrup

2    1         2             Condiments      4                            Chef Anton's Cajun Seasoning

1    NULL    1              Beverages        NULL                      NULL

2    1         1             Beverages        1                            Chai

2    1         1             Beverages        2                            Chang

3.使用EXPLICIT模式查询中的指令

这些指令是指通用表列的第四部分,通过它对数据进行进一步控制。

1>     最常用的elementxml指令

通过该指令把列的数据显示为一个子元素而不是属性。

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

CategoryName  [Category!1!Name],

Description  [Category!1!!element]

FROM Categories

WHERE categoryID > 6 FOR XML EXPLICIT

返回结果:

<Category ID="7" Name="Produce">Dried fruit and bean curd</Category>

<Category ID="8" Name="Seafood">Seaweed and fish</Category>

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

CategoryName  [Category!1!Name],

Description  [Category!1!Description!element]

FROM Categories

WHERE categoryID > 6 FOR XML EXPLICIT

返回结果:

<Category ID="7" Name="Produce">

         <Description>Dried fruit and bean curd</Description>

</Category>

<Category ID="8" Name="Seafood">

         <Description>Seaweed and fish</Description>

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

CategoryName  [Category!1],

Description  [Category!1!Description!element]

FROM Categories

WHERE categoryID > 6 FOR XML EXPLICIT

返回结果:

<Category ID="7">

         Produce

         <Description>Dried fruit and bean curd</Description>

</Category>

<Category ID="8">

         Seafood

         <Description>Seaweed and fish</Description>

</Category>

xml指令同element指令差不多,只是element指令会将一些xml下面的符号进行编码,如>被编码成&gt;5,而xml指令不会。

2>     使用hide指令

该指令是用来处理那些不想返回结果的列,许多人会有疑问觉得没有必要,只要在列中不增加就行了,条件和排序也可以直接使用列。我们可以想得到使用UNION ALL时就用得上了,看下面的例子。

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID!hide],

CategoryName AS [Category!1!Name],

NULL  AS [Product!2!ProductID!element],

NULL  AS [Product!2!ProductName!element]

FROM Categories  WHERE CategoryID < 3

UNION ALL

SELECT 2 ,1 ,

Categories.CategoryID,

NULL,

ProductID,

ProductName

FROM Categories

JOIN Products ON Categories.CategoryID = Products.CategoryID AND ProductID <5

WHERE Categories.CategoryID < 3

ORDER BY [Category!1!ID!hide] DESC,[Product!2!ProductID!element] ASC

FOR XML EXPLICIT

返回结果:

<Category Name="Condiments">

         <Product>

                   <ProductID>3</ProductID>

                   <ProductName>Aniseed Syrup</ProductName>

         </Product>

                   <Product>

                   <ProductID>4</ProductID>

         <ProductName>Chef Anton&apos;s Cajun Seasoning</ProductName>

         </Product>

</Category>

<Category Name="Beverages">

         <Product>

                   <ProductID>1</ProductID>

                   <ProductName>Chai</ProductName>

         </Product>

                   <Product><ProductID>2</ProductID>

                   <ProductName>Chang</ProductName>

         </Product>

</Category>

3>     使用xmltext指令

该指令是用来处理包含xml片断内容的数据列,没找到现成的表结构和数据进行测试,所以就在Categories表中增加了XmlData列名,类型为nvarchar,默认值设置为“<customData email="xxx(at)hotmail.com" />”。

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

XmlData AS [Category!1!CustomData!xmltext]

FROM Categories WHERE CategoryID = 8 FOR XML EXPLICIT

返回结果:

<Category ID="8"><CustomData email="xxx(at)hotmail.com"/></Category>

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

XmlData AS [Category!1!!xmltext]

FROM Categories WHERE categoryID = 8 FOR XML EXPLICIT

返回结果:

<Category ID="8" email="xxx(at)hotmail.com"></Category>

4>     使用cdata指令

使用该指令可以创建CDATA节防止一些字符数据被xml解析器。

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID],

XmlData AS [Category!1!!cdata]

FROM Categories WHERE categoryID = 6 FOR XML EXPLICIT

返回结果:

<Category ID="6"><![CDATA[<customData email="xxx(at)hotmail.com" />]]></Category>

5>     使用IDIDREFIDREFS指令以及XMLDATA选项

由于这里只能返回XDR构架,不支持XSD架构的检索,我就只给出一个例子,

查询语句:

SELECT 1 AS Tag,NULL AS Parent,

CategoryID AS [Category!1!ID!ID],

CategoryName AS [Category!1!Name],

NULL  AS [Product!2!ProductID!IDREF],

NULL  AS [Product!2!ProductName]

FROM Categories  WHERE CategoryID < 3

UNION ALL

SELECT 2 ,1 ,

Categories.CategoryID,

NULL,

ProductID,

ProductName

FROM Categories

JOIN Products ON Categories.CategoryID = Products.CategoryID AND ProductID <5

WHERE Categories.CategoryID < 3

ORDER BY [Category!1!ID!ID] ,[Product!2!ProductID!IDREF]

FOR XML EXPLICIT,XMLDATA

返回结果:

<Schema name="Schema2" xmlns="urn:schemas-microsoft-com:xml-data" xmlns:dt="urn:schemas-microsoft-com:datatypes">

         <ElementType name="Category" content="mixed" model="open">

                   <AttributeType name="ID" dt:type="id"/>

                   <AttributeType name="Name" dt:type="string"/>

                   <attribute type="ID"/>

                   <attribute type="Name"/>

         </ElementType>

         <ElementType name="Product" content="mixed" model="open">

                   <AttributeType name="ProductID" dt:type="idref"/>

                   <AttributeType name="ProductName" dt:type="string"/>

                   <attribute type="ProductID"/>

                   <attribute type="ProductName"/>

         </ElementType>

</Schema>

<Category xmlns="x-schema:#Schema2" ID="1" Name="Beverages">

         <Product ProductID="1" ProductName="Chai"/>

         <Product ProductID="2" ProductName="Chang"/>

</Category>

<Category xmlns="x-schema:#Schema2" ID="2" Name="Condiments">

         <Product ProductID="3" ProductName="Aniseed Syrup"/>

         <Product ProductID="4" ProductName="Chef Anton&apos;s Cajun Seasoning"/>

</Category>

 

四.使用BINARY BASE64 选项

使用该选项可以检索二进制字段,比如图像数据以base64的形式返回到xml文档。

1.使用RAW方式

查询语句:

SELECT CategoryID,

Picture

FROM Categories

WHERE CategoryID =1

ORDER BY CategoryID DESC

FOR XML RAW,BINARY BASE64

返回结果:

<row CategoryID="1" Picture="FRwvAAIAAAANAA4AFAAhAP////9CaXRtYXAgSW..."/>

2.使用AUTO方式

查询语句:

SELECT CategoryID,

Picture

FROM Categories

WHERE CategoryID =2

ORDER BY CategoryID DESC

FOR XML AUTO,BINARY BASE64

返回结果:

<Categories CategoryID="2" Picture="FRwvAAIAAAANAA4AFAAhAP////..."/>

查询语句:

SELECT CategoryID,

Picture

FROM Categories

WHERE CategoryID =1

ORDER BY CategoryID DESC

FOR XML AUTO,ELEMENTS,BINARY BASE64

返回结果:

<Categories>

         <CategoryID>1</CategoryID>

         <Picture>FRwvAAIAAAANAA4AFAAhAP////9CaXRt...</Picture>

</Categories>

 

         总结,通过上文比较全面的熟悉了SQL SERVER 2000下检索生成XML数据的功能。在平常的项目开发中你也许就会用到上述功能,使得项目的开发省去一部分的时间和精力。上述的Xml查询功能在ado.net下得到了很好的支持,这也是本人比较感兴趣的地方。本文是我对《SQL SERVER 2000XML数据库编程(第2版)》 一书第二章所讲技术的再次实践。如果需要联系可以访问我的Bloghttp://blog.csdn.net/zhzuo