FLWOR ステートメントと繰り返し (XQuery)

XQuery では FLWOR 繰り返し構文が定義されています。FLWOR とは、for、let、where、order by、および return の頭文字です。

FLWOR ステートメントの構成要素を次に示します。

  • 1 つ以上の反復子変数を入力シーケンスにバインドする 1 つ以上の FOR 句。

    入力シーケンスは、XPath 式などの他の XQuery 式でもかまいません。その場合、ノードのシーケンス、またはアトミック値のシーケンスのいずれかを指定します。アトミック値のシーケンスを構成するには、リテラルまたはコンストラクタ関数を使用できます。構成された XML ノードは、SQL Server の入力シーケンスとしては使用できません。

  • 省略可能な let 句。この句は、特定の繰り返し処理の変数に値を割り当てます。割り当てる式として XPath 式などの XQuery 式を指定でき、ノードのシーケンスまたはアトミック値のシーケンスを返すことができます。アトミック値のシーケンスを構成するには、リテラルまたはコンストラクタ関数を使用できます。構成された 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 テーブルの型指定された xml 列である Instructions 列に対して指定されている点が異なります。特定の製品に対し、最初のワーク センター拠点で行われるすべての製造手順 (<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> 要素の文字列値を返します。

string 関数 (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 テーブルの Instructions 列には、製造手順が次の形式で保存されています。

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

次のクエリは、ワーク センター拠点の属性を子要素として返す <Location> 要素を含んだ新しい XML を生成します。

<Location>
   <LocationID>10</LocationID>
   <LaborHours>1.2</LaborHours>
   <SetupHours>.2</SteupHours>
   <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> 要素のシーケンスを取得します。

  • data 関数 (XQuery) を使用して各属性の値を抽出し、それを属性ではなくテキスト ノードとして結果の XML に追加します。

  • RETURN 句の式で、必要な XML を生成します。

結果の一部を次に示します。

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

let 句の使用

let 句を使用すると、繰り返し出現する式に名前を付けて変数とすることで、式を参照できます。SQL Server 2008 では、let 変数に割り当てられた式は、変数がクエリ内で参照されるたびにクエリに挿入されます。つまり、ステートメントは 1 回だけではなく、式が参照される回数だけ実行されます。

AdventureWorks データベース内の製造手順には、必要なツールとツールを使用する場所の情報が保存されています。次のクエリは、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 句を使用して、繰り返しの結果をフィルタ選択できます。これについては、次の例で AdventureWorks サンプル データベースを使用して説明します。

自転車を製造するときは、ワーク センター拠点をいくつか経て製造プロセスが進行します。ワーク センター拠点ごとに、一連の製造手順が定義されています。次のクエリは、あるモデルの自転車を製造するためのワーク センター拠点のうち、製造手順が 3 工程未満の拠点を取得します。つまり <step> 要素が 3 つ未満のものだけが取得されます。

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 式が単純な Boolean 型の値を 1 つ返す場合、その値が有効なブール値になります。

  3. where 式が 1 つ以上のノードを含んだシーケンスを返す場合、有効なブール値は True です。

  4. 上記以外の場合、静的エラーが発生します。

FLWOR での複数の変数のバインド

1 つの 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 変数を定義します。

  • $FirstStep の値が $Loc の値に依存しているので、2 つの式 /ManuInstructions/Location と $FirstStep in $Loc/Step[1] には相関関係があります。

  • $Loc に関連付けられた式により、<Location> 要素のシーケンスが生成されます。各 <Location> 要素について、$FirstStep により 1 つの <Step> 要素から成るシーケンス (シングルトン) が生成されます。

  • $Loc は、$FirstStep 変数に関連付けられた式で指定しています。

結果を次に示します。

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

次のクエリは、上のクエリと似ていますが、ProductModel テーブルの型指定された xml 列である Instructions 列を対象にしている点が異なります。XML の構築 (XQuery) を使用して、必要な XML を生成します。

SELECT Instructions.query('
     declare default 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 句で 2 つの変数 $WC および $S を定義します。$WC に関連付けられた式により、ある製造モデルの自転車の製造で使用されるワーク センター拠点のシーケンスが生成されます。$S 変数に代入されたパス式は、$WC で示すワーク センター拠点のシーケンスごとに製造手順のシーケンスを生成します。

  • return ステートメントでは、製造手順を値とし、LocationID を属性とする <Step> 要素を含んだ XML を生成します。

  • 結果の XML に現れるすべての名前空間の宣言を最上位の要素で行うために、XQuery のプロローグで declare default element namespace を使用しています。これにより、結果が読みやすくなります。既定の名前空間の詳細については、「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 句の使用

XQuery の並べ替えを行うには、FLWOR 式で order by 句を使用します。order by 句に渡した並べ替え式は、gt 演算子に使用できる型の値を返す必要があります。それぞれの並べ替え式は、シングルトン (項目が 1 つのシーケンス) になる必要があります。既定の並べ替えは昇順です。並べ替え式ごとに昇順か降順かをオプションとして選択できます。

注意注意

SQL Server に実装された XQuery で行う、文字列値を並べ替えるための比較には、常にバイナリの Unicode コード ポイントの照合順序が使用されます。

次のクエリは、AdditionalContactInfo 列から特定の顧客のすべての電話番号を取得します。結果は電話番号順に並べ替えます。

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.Contact
WHERE ContactID=3

アトミック化 (XQuery) のプロセスにより、<number> 要素を order by に渡す前にアトミック値の取得が行われます。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 でも宣言できます。

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.Contact
WHERE ContactID=3

属性値による並べ替えも行えます。たとえば次のクエリでは、新しく作成した、LocationID 属性および LaborHours 属性を含む <Location> 要素を 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>Almuminum 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" />

次の例のように、並べ替え条件は複数指定できます。この例のクエリは、<Employee> 要素をまず Title 属性の値で並べ替え、次に Administrator 属性の値で並べ替えます。

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 を使用できません。

関連項目

概念