FLWOR 语句和迭代 (XQuery)

适用范围:SQL Server

XQuery 定义了 FLWOR 迭代语法。 FLWOR 是 forletwhereorder byreturn 的缩写词。

FLWOR 语句由以下几个部分组成:

  • 用于将一个或多个迭代器变量绑定到输入序列的一个或多个 FOR 子句。

    输入序列可以是其他 XQuery 表达式,例如 XPath 表达式。 它们既可以是节点序列也可以是原子值序列。 原子值序列可以使用文字或构造函数来构造。 不允许构造的 XML 节点作为 SQL Server 中的输入序列。

  • 可选的 let 子句。 该子句将一个值分配给用于特定迭代的给定变量。 分配的表达式可以为 XQuery 表达式(如 XPath 表达式),并可返回一个节点序列或一个原子值序列。 原子值序列可以通过使用文字或构造函数来构造。 不允许构造的 XML 节点作为 SQL Server 中的输入序列。

  • 迭代器变量。 通过使用 as 关键字,此变量可包含一个可选的类型判断。

  • 可选的 where 子句。 此子句将对迭代应用筛选谓词。

  • 可选的 order by 子句。

  • return 表达式。 return 子句中的表达式用于构造 FLWOR 语句的结果。

例如,以下查询循环访问第一个制造位置的 <Step> 元素,并返回节点的 <Step> 字符串值:

declare @x xml  
set @x='<ManuInstructions ProductModelID="1" ProductModelName="SomeBike" >  
<Location LocationID="L1" >  
  <Step>Manu step 1 at Loc 1</Step>  
  <Step>Manu step 2 at Loc 1</Step>  
  <Step>Manu step 3 at Loc 1</Step>  
</Location>  
<Location LocationID="L2" >  
  <Step>Manu step 1 at Loc 2</Step>  
  <Step>Manu step 2 at Loc 2</Step>  
  <Step>Manu step 3 at Loc 2</Step>  
</Location>  
</ManuInstructions>'  
SELECT @x.query('  
   for $step in /ManuInstructions/Location[1]/Step  
   return string($step)  
')  

结果如下:

Manu step 1 at Loc 1 Manu step 2 at Loc 1 Manu step 3 at Loc 1  

以下查询与上一个查询相似,只不过它是针对 ProductModel 表中的 Instructions 列(类型化的 xml 列)指定的。 查询循环访问特定产品的第一个工作中心位置的所有制造步骤、 <step> 元素。

SELECT Instructions.query('  
   declare namespace AWMI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";  
for $Step in //AWMI:root/AWMI:Location[1]/AWMI:step  
      return  
           string($Step)   
') as Result  
FROM Production.ProductModel  
where ProductModelID=7  

请注意上述查询的以下方面:

  • $Step 是迭代器变量。

  • 路径表达式//AWMI:root/AWMI:Location[1]/AWMI:step生成输入序列。 此序列是第一个Location><元素节点的元素节点子级的序列。<step>

  • 不使用可选的谓词子句 where

  • 表达式 return 从 <step> 元素返回字符串值。

字符串 函数 (XQuery) 用于检索节点的 <step> 字符串值。

下面是部分结果:

Insert aluminum sheet MS-2341 into the T-85A framing tool.   
Attach Trim Jig TJ-26 to the upper and lower right corners of   
the aluminum sheet. ....         

以下是其他允许使用的输入序列的示例:

declare @x xml  
set @x=''  
SELECT @x.query('  
for $a in (1, 2, 3)  
  return $a')  
-- result = 1 2 3   
  
declare @x xml  
set @x=''  
SELECT @x.query('  
for $a in   
   for $b in (1, 2, 3)  
      return $b  
return $a')  
-- result = 1 2 3  
  
declare @x xml  
set @x='<ROOT><a>111</a></ROOT>'  
SELECT @x.query('  
  for $a in (xs:string( "test"), xs:double( "12" ), data(/ROOT/a ))  
  return $a')  
-- result test 12 111  

在 SQL Server 中,不允许使用异类序列。 尤其不允许使用由原子值和节点混合组成的序列。

迭代经常与转换 XML 格式的 XML 构造 语法一起使用,如下一个查询所示。

在 AdventureWorks 示例数据库中,Production.ProductModel 表的“指令”列中存储的制造指令采用以下形式:

<Location LocationID="10" LaborHours="1.2"   
            SetupHours=".2" MachineHours=".1">  
  <step>describes 1st manu step</step>  
   <step>describes 2nd manu step</step>  
   ...  
</Location>  
...  

以下查询将构造具有作为子元素返回的工作中心位置属性的元素的新 XML <Location> :

<Location>  
   <LocationID>10</LocationID>  
   <LaborHours>1.2</LaborHours>  
   <SetupHours>.2</SetupHours>  
   <MachineHours>.1</MachineHours>  
</Location>  
...  

以下是查询语句:

SELECT Instructions.query('  
     declare namespace AWMI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";  
        for $WC in /AWMI:root/AWMI:Location  
        return  
          <Location>  
            <LocationID> { data($WC/@LocationID) } </LocationID>  
            <LaborHours>   { data($WC/@LaborHours) }   </LaborHours>  
            <SetupHours>   { data($WC/@SetupHours) }   </SetupHours>  
            <MachineHours> { data($WC/@MachineHours) } </MachineHours>  
          </Location>  
') as Result  
FROM Production.ProductModel  
where ProductModelID=7  

请注意上述查询的以下方面:

  • FLWOR 语句检索特定产品的元素序列 <Location> 。

  • 数据函数(XQuery)用于提取每个属性的值,以便将其作为文本节点而不是属性添加到生成的 XML。

  • RETURN 子句中的表达式将构造所需的 XML。

这是部分结果:

<Location>  
  <LocationID>10</LocationID>  
  <LaborHours>2.5</LaborHours>  
  <SetupHours>0.5</SetupHours>  
  <MachineHours>3</MachineHours>  
</Location>  
<Location>  
   ...  
<Location>  
...  

使用 let 子句

可以使用 let 子句来命名可通过引用变量来引用的重复表达式。 每次在查询中引用 let 变量时,都会将分配给该变量的表达式插入到查询中。 也就是说,引用该表达式多少次,就执行多少次此语句。

AdventureWorks2022 数据库中,生产说明包含有关所需的工具以及在何处使用这些工具的信息。 以下查询使用 let 子句列出构建生产模型所需的工具以及各工具的使用位置。

SELECT Instructions.query('  
     declare namespace AWMI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";  
        for $T in //AWMI:tool  
            let $L := //AWMI:Location[.//AWMI:tool[.=data($T)]]  
        return  
          <tool desc="{data($T)}" Locations="{data($L/@LocationID)}"/>  
') as Result  
FROM Production.ProductModel  
where ProductModelID=7  

使用 where 子句

可以使用子 where 句筛选迭代的结果。 下面的示例说明了这一点。

在生产自行车时,生产过程经过了一系列生产车间。 每个生产车间定义一个生产步骤序列。 以下查询仅检索那些生产某个自行车型号并且生产步骤少于三步的生产车间。 也就是说,它们少于三 <step> 个元素。

SELECT Instructions.query('  
     declare namespace AWMI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";  
for $WC in /AWMI:root/AWMI:Location  
      where count($WC/AWMI:step) < 3  
      return  
          <Location >  
           { $WC/@LocationID }   
          </Location>  
') as Result  
FROM Production.ProductModel  
where ProductModelID=7  

请注意上述查询的以下方面:

  • 关键字where使用 count() 函数对每个工作中心位置中的子元素数><step进行计数。

  • return 表达式将构造您希望从迭代结果生成的 XML。

结果如下:

<Location LocationID="30"/>   

where 子句中的表达式的结果使用下列规则按指定的顺序转换为布尔值。 这些规则与路径表达式中的谓词规则相同,只不过不允许使用整数:

  1. 如果 where 表达式返回一个空序列,则其有效的布尔值为 False。

  2. 如果 where 表达式返回一个简单的布尔类型值,则该值为有效的布尔值。

  3. 如果 where 表达式返回至少包含一个节点的序列,则有效的布尔值为 True。

  4. 否则,它将出现静态错误。

FLWOR 中的多个变量绑定

可以用单个 FLWOR 表达式将多个变量绑定到输入序列。 在下面的示例中,查询针对非类型化的 xml 变量指定。 FLOWR 表达式返回每个<Location>元素中的第一个<Step>元素子元素。

declare @x xml  
set @x='<ManuInstructions ProductModelID="1" ProductModelName="SomeBike" >  
<Location LocationID="L1" >  
  <Step>Manu step 1 at Loc 1</Step>  
  <Step>Manu step 2 at Loc 1</Step>  
  <Step>Manu step 3 at Loc 1</Step>  
</Location>  
<Location LocationID="L2" >  
  <Step>Manu step 1 at Loc 2</Step>  
  <Step>Manu step 2 at Loc 2</Step>  
  <Step>Manu step 3 at Loc 2</Step>  
</Location>  
</ManuInstructions>'  
SELECT @x.query('  
   for $Loc in /ManuInstructions/Location,  
       $FirstStep in $Loc/Step[1]  
   return   
       string($FirstStep)  
')  

请注意上述查询的以下方面:

  • 表达式 for 定义 $Loc 和 $FirstStep 变量。

  • two/ManuInstructions/Location$FirstStep in $Loc/Step[1] 表达式相互关联,$FirstStep 的值取决于 $Loc 的值。

  • $Loc 生成的元素序列 <Location> 关联的表达式。 对于每个 <Location> 元素, $FirstStep 生成一 <Step> 个元素序列,即一个单一实例。

  • $Loc 在与 $FirstStep 变量相关联的表达式中指定。

结果如下:

Manu step 1 at Loc 1   
Manu step 1 at Loc 2  

以下查询类似,只是针对 ProductModel 表的“指令”列(类型化 xml)指定了它。 XML 构造 (XQuery) 用于生成所需的 XML。

SELECT Instructions.query('  
     declare default element namespace "https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";  
for $WC in /root/Location,  
            $S  in $WC/step  
      return  
          <Step LocationID= "{$WC/@LocationID }" >  
            { $S/node() }  
          </Step>  
') as Result  
FROM  Production.ProductModel  
WHERE ProductModelID=7  

请注意上述查询的以下方面:

  • for 子句定义了两个变量:$WC$S。 在生产某个自行车产品型号时,与 $WC 相关联的表达式将生成一系列生产车间。 分配给 $S 变量的路径表达式将为 $WC 中的每个生产车间序列生成一个相应的步骤序列。

  • 返回语句构造 XML <Step> ,该元素包含制造步骤和 LocationID 作为其属性。

  • 声明 默认元素命名空间 用于 XQuery prolog,以便生成的 XML 中的所有命名空间声明显示在顶级元素上。 这使结果的可读性更强。 有关默认命名空间的详细信息,请参阅 在 XQuery 中处理命名空间。

下面是部分结果:

<Step xmlns=  
    "https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions"     
  LocationID="10">  
     Insert <material>aluminum sheet MS-2341</material> into the <tool>T-   
     85A framing tool</tool>.   
</Step>  
...  
<Step xmlns=  
      "https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions"     
    LocationID="20">  
        Assemble all frame components following blueprint   
        <blueprint>1299</blueprint>.  
</Step>  
...  

使用 order by 子句

通过使用 FLWOR 表达式中的 order by 子句可在 XQuery 中进行排序。 传递给子句的 order by 排序表达式必须返回类型对 gt 运算符有效的值。 每个排序表达式必须针对每一项生成一个单独的序列。 默认情况下,按升序进行排序。 您也可以选择为每个排序表达式指定升序或降序顺序。

注意

始终使用二进制 Unicode 代码点排序规则对 SQL Server 中 XQuery 实现执行的字符串值的比较。

以下查询将从 AdditionalContactInfo 列检索有关特定客户的所有电话号码。 结果按电话号码进行排序。

USE AdventureWorks2022;  
GO  
SELECT AdditionalContactInfo.query('  
   declare namespace act="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes";  
   declare namespace aci="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo";  
   for $a in /aci:AdditionalContactInfo//act:telephoneNumber   
   order by $a/act:number[1] descending  
   return $a  
') As Result  
FROM Person.Person  
WHERE BusinessEntityID=291;  

请注意,Atomization (XQuery) 进程在传递给元素之前检索元素order by的<number>原子值。 可以使用 data() 函数编写表达式,但这不是必需的。

order by data($a/act:number[1]) descending  

结果如下:

<act:telephoneNumber xmlns:act="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">  
  <act:number>333-333-3334</act:number>  
</act:telephoneNumber>  
<act:telephoneNumber xmlns:act="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">  
  <act:number>333-333-3333</act:number>  
</act:telephoneNumber>  

可以使用 WITH XMLNAMESPACES 声明命名空间,而不是在查询 prolog 中声明。

WITH XMLNAMESPACES (  
   'https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes' AS act,  
   'https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo'  AS aci)  
  
SELECT AdditionalContactInfo.query('  
   for $a in /aci:AdditionalContactInfo//act:telephoneNumber   
   order by $a/act:number[1] descending  
   return $a  
') As Result  
FROM Person.Person  
WHERE BusinessEntityID=291;  

还可以按属性值进行排序。 例如,以下查询检索新创建的 <Location> 元素,这些元素的 LocationID 和 LaborHours 属性按降序顺序按 LaborHours 属性排序。 结果,将首先返回工时最长的生产车间。

SELECT Instructions.query('  
     declare namespace AWMI="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelManuInstructions";  
for $WC in /AWMI:root/AWMI:Location   
order by $WC/@LaborHours descending  
        return  
          <Location>  
             { $WC/@LocationID }   
             { $WC/@LaborHours }   
          </Location>  
') as Result  
FROM Production.ProductModel  
WHERE ProductModelID=7;  

结果如下:

<Location LocationID="60" LaborHours="4"/>  
<Location LocationID="50" LaborHours="3"/>  
<Location LocationID="10" LaborHours="2.5"/>  
<Location LocationID="20" LaborHours="1.75"/>  
<Location LocationID="30" LaborHours="1"/>  
<Location LocationID="45" LaborHours=".5"/>  

在以下查询中,将按元素名称对结果进行排序。 此查询将从产品目录中检索特定产品的规范。 规范是元素的 <Specifications> 子级。

SELECT CatalogDescription.query('  
     declare namespace  
 pd="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelDescription";  
      for $a in /pd:ProductDescription/pd:Specifications/*   
     order by local-name($a)  
      return $a  
    ') as Result  
FROM Production.ProductModel  
where ProductModelID=19;  

请注意上述查询的以下方面:

  • 表达式/p1:ProductDescription/p1:Specifications/*返回的元素子元素Specifications<> 。

  • order by (local-name($a)) 表达式将按元素名称的本地部分对序列进行排序。

结果如下:

<Color>Available in most colors</Color>  
<Material>Aluminum Alloy</Material>  
<ProductLine>Mountain bike</ProductLine>  
<RiderExperience>Advanced to Professional riders</RiderExperience>  
<Style>Unisex</Style>    

其排序表达式返回空值的节点将被列在序列的开头,如以下示例中所示:

declare @x xml  
set @x='<root>  
  <Person Name="A" />  
  <Person />  
  <Person Name="B" />  
</root>  
'  
select @x.query('  
  for $person in //Person  
  order by $person/@Name  
  return   $person  
')  

结果如下:

<Person />  
<Person Name="A" />  
<Person Name="B" />  

您可以指定多个排序条件,如以下示例中所示。 此示例中的查询先按 Title 和 Administrator 属性值对元素进行排序 <Employee> 。

declare @x xml  
set @x='<root>  
  <Employee ID="10" Title="Teacher"        Gender="M" />  
  <Employee ID="15" Title="Teacher"  Gender="F" />  
  <Employee ID="5" Title="Teacher"         Gender="M" />  
  <Employee ID="11" Title="Teacher"        Gender="F" />  
  <Employee ID="8" Title="Administrator"   Gender="M" />  
  <Employee ID="4" Title="Administrator"   Gender="F" />  
  <Employee ID="3" Title="Teacher"         Gender="F" />  
  <Employee ID="125" Title="Administrator" Gender="F" /></root>'  
SELECT @x.query('for $e in /root/Employee  
order by $e/@Title ascending, $e/@Gender descending  
  
  return  
     $e  
')  

结果如下:

<Employee ID="8" Title="Administrator" Gender="M" />  
<Employee ID="4" Title="Administrator" Gender="F" />  
<Employee ID="125" Title="Administrator" Gender="F" />  
<Employee ID="10" Title="Teacher" Gender="M" />  
<Employee ID="5" Title="Teacher" Gender="M" />  
<Employee ID="11" Title="Teacher" Gender="F" />  
<Employee ID="15" Title="Teacher" Gender="F" />  
<Employee ID="3" Title="Teacher" Gender="F" />  

实现限制

限制如下:

  • 排序表达式必须经过同类类型化。 这是通过静态检查来确定的。

  • 无法控制对空序列的排序。

  • 不支持对 order by 使用 empty least、empty greatest 和 collation 关键字

另请参阅

XQuery 表达式