EXPLICIT モードの使用
トピック「FOR XML を使用した XML の構築」で説明したように、RAW モードと AUTO モードでは、クエリ結果から生成される XML の構造を厳密に制御することはできません。一方、EXPLICIT モードを使用すると、クエリ結果から生成される XML の構造を柔軟に制御することができます。
ただし、必要な XML に関する追加情報 (XML 内の入れ子構造など) をクエリの一部として明示的に指定するために、EXPLICIT モードのクエリを記述する際には特殊な記述方法が必要になります。このため、要求する XML によっては、EXPLICIT モードのクエリを記述する作業が複雑になることがあります。EXPLICIT モードのクエリを記述するよりも、PATH モードで入れ子を使用する方が作業が容易になる場合もあります。
EXPLICIT モードでは、目的の XML をクエリによって記述するので、正しい形式で有効な XML が生成されるかどうかを確認する必要があります。
EXPLICIT モードでの行セットの処理
EXPLICIT モードでは、クエリを実行した結果として生じた行セットが XML に変換されます。EXPLICIT モードで XML ドキュメントを作成するには、特殊な形式の行セットが必要になります。そのためには、XML を処理ロジックで生成できるように、決められた形式の行セット (ユニバーサル テーブル) を生成できるような選択クエリを記述する必要があります。
まず、クエリで次の 2 つのメタデータ列を生成します。
- 1 列目は Tag という名前で、現在の要素のタグ番号 (整数型) を指定します。行セットから構築される要素ごとに、一意のタグ番号をクエリが提供しなければなりません。
- 2 列目は Parent という名前で、親要素のタグ番号を指定します。これにより、Tag 列と Parent 列の階層情報が定義されます。
XML を生成する際には、これらのメタデータ列の値が、列名の情報と共に使用されます。列名は、特殊な方法でクエリ内に指定します。Parent 列の値が 0 または NULL であれば、その要素に親要素がないことを示します。このような要素は、最上位の要素として XML に追加されます。
クエリで生成したユニバーサル テーブルが、どのように処理され、XML として生成されるか、例を検証しましょう。ここでは、次のユニバーサル テーブルを生成するクエリを記述したとします。
このユニバーサル テーブルについて説明します。
- 最初の 2 列は Tag 列と Parent 列で、これらはメタデータ列です。これらの値は、階層を決定します。
- 列名は、このトピックの後半で説明するように、決められた方法で指定する必要があります。
- このユニバーサル テーブルから XML を生成する際、このテーブルのデータは列方向に (列グループに) パーティション分割されます。このグループ化は、Tag 列の値と列名によって決まります。XML の生成時には、各行ごとに 1 つの列グループが選択され、1 つの要素が構築されます。この処理は、この例では次のように行われます。
- 1 行目の Tag 列の値は 1 なので、同じタグ番号を列名に含んでいる Customer!1!cid 列と Customer!1!name 列でグループが形成されます。これらの列が行の処理に使用され、生成される要素の形式は <
Customer id=... name=...
> のようになります。列名の形式については、このトピックの後半で説明します。 - Tag 列の値が 2 の行については、Order!2!id 列と Order!2!date 列でグループが形成され、<
Order id=... date=... /
> という形式の要素が生成されます。 - Tag 列の値が 3 の行については、OrderDetail!3!id!id 列と OrderDetail!3!pid!idref 列でグループが形成されます。これらの列からは、<
OrderDetail id=... pid=...
> という形式の要素が生成されます。
- 1 行目の Tag 列の値は 1 なので、同じタグ番号を列名に含んでいる Customer!1!cid 列と Customer!1!name 列でグループが形成されます。これらの列が行の処理に使用され、生成される要素の形式は <
- XML 階層の生成時、行は順番に処理されます。XML 階層は、次のように決定されます。
1 行目では、Tag 列に値 1 が指定され、Parent 列には NULL が指定されています。したがって、対応する <
Customer
> 要素は、XML の最上位要素として追加されます。<Customer cid="C1" name="Janine">
2 行目では、Tag 列に値 2 が指定され、Parent 列には値 1 が指定されています。したがって、<
Order
> 要素は、<Customer
> 要素の子要素として追加されます。<Customer cid="C1" name="Janine"> <Order id="O1" date="1/20/1996">
3 行目と 4 行目では、Tag 列に値 3 が指定され、Parent 列には値 2 が指定されています。したがって、2 つの <
OrderDetail
> 要素は <Order
> 要素の子要素として追加されます。<Customer cid="C1" name="Janine"> <Order id="O1" date="1/20/1996"> <OrderDetail id="OD1" pid="P1"/> <OrderDetail id="OD2" pid="P2"/>
最後の行では、Tag 列に値 1 が指定され、Parent 列には値 1 が指定されています。したがって、もう 1 つの <
Order
> 子要素が <Customer
> 親要素に追加されます。<Customer cid="C1" name="Janine"> <Order id="O1" date="1/20/1996"> <OrderDetail id="OD1" pid="P1"/> <OrderDetail id="OD2" pid="P2"/> </Order> <Order id="O2" date="3/29/1997"> </Customer>
つまり、EXPLICIT モードでは、Tag メタデータ列と Parent メタデータ列の値、各列名に提供されている情報、および列の正しい順序に基づいて、必要な XML を生成できるということになります。
ユニバーサル テーブルの行の順序
XML の生成時、ユニバーサル テーブルの行は順番に処理されます。そのため、親と関連付けられている適切な子インスタンスを取得するには、各親ノードの直後がその親の子ノードになるという順序で、行セットの行が並んでいる必要があります。
ユニバーサル テーブルでの列名の指定
EXPLICIT モードのクエリを記述する際に、結果の行セットの列名は、この形式を使用して指定します。列名では、ディレクティブを使用して、要素と属性の名前や他の追加情報などを含めた変換情報を指定します。
次に一般的な形式を示します。
ElementName!TagNumber!AttributeName!Directive
各部分の説明は、次のとおりです。
- ElementName
結果の要素の汎用識別子。たとえば、ElementName として Customers が指定されている場合、<Customers> 要素が生成されます。
- TagNumber
要素に割り当てられる一意なタグの値。この値と Tag および Parent の 2 つのメタデータ列の組み合わせにより、生成される XML 内の要素の入れ子構造が決定されます。
AttributeName
指定した ElementName 要素に作成する属性の名前。これは、Directive を指定しない場合の動作です。Directive に xml、cdata、または element のいずれかが指定されている場合、この値は ElementName 要素の子要素の構築に使用され、列値がその子要素に追加されます。
Directive を指定した場合、AttributeName は省略できます。たとえば、ElementName!TagNumber!!Directive という形式を指定したとします。この場合、列値は、ElementName に直接格納されます。
Directive
Directive は、XML を生成するための追加情報を指定する場合に使用しますが、省略可能です。Directive には、2 つの目的があります。1 つ目の目的は、値を ID、IDREF、および IDREFS としてエンコードすることです。それには、Directives として、ID、IDREF、および IDREFS キーワードを指定します。これらのディレクティブを指定すると、属性の型が上書きされます。これにより、ドキュメント内でのリンクを作成できるようになります。
Directive を使用する 2 つ目の目的は、文字列データをどのように XML にマップするかを指定することです。Directive としては、hide、element、elementxsinil、xml、xmltext、cdata の各キーワードを使用できます。hide ディレクティブを指定した場合は、ノードが非表示になります。このディレクティブは、ノードを並べ替えるだけの目的で値を取得し、生成する XML にはその値を表示しない場合に役立ちます。
element ディレクティブを指定すると、属性ではなく子要素 (含まれる要素) が生成されます。含まれる要素のデータは、エンティティとしてエンコードされます。たとえば、文字 < は < になります。列の値が NULL であれば、要素は生成されません。列の値が NULL である場合にも要素を生成する場合は、elementxsinil ディレクティブを指定します。このディレクティブを指定すると、xsi:nil=TRUE 属性を持つ要素が生成されます。
xml ディレクティブは、element ディレクティブと似ていますが、エンティティのエンコードが行われない点が異なります。また、element ディレクティブは ID、IDREF、または IDREFS と組み合わせて使用できますが、xml ディレクティブは hide 以外のディレクティブと組み合わせて使用することができない点にも注意してください。
cdata ディレクティブを使用した場合は、データが CDATA セクション内に取り込まれます。CDATA セクションの内容については、エンティティのエンコードが行われません。元のデータ型は、varchar、nvarchar、text、または ntext などのテキスト型でなければなりません。このディレクティブと組み合わせて使用できるのは、hide ディレクティブだけです。このディレクティブを使用する場合、AttributeName は指定できません。
この 2 つのグループ間でディレクティブを組み合わせることは、ほとんどの場合に可能ですが、同じグループ内のディレクティブを組み合わせることはできません。
Directive と AttributeName が指定されていない場合を考えます。たとえば、Customer!1 の場合、Customer!1!!element のように element ディレクティブが暗黙的に指定され、列のデータが ElementName に格納されます。
xmltext ディレクティブを指定すると、列の内容は 1 つのタグで囲まれ、ドキュメントの残りの部分に統合されます。このディレクティブは、OPENXML によって列に格納されている未使用のオーバーフロー XML データを収集する場合に役に立ちます。詳細については、「OPENXML による XML へのクエリ」を参照してください。
AttributeName を指定した場合、タグ名は指定した名前に置き換えられます。それ以外の場合、属性は囲み要素の現在の属性リストに追加され、内容は囲み要素の内容として先頭に追加されます。このとき、エンティティのエンコードは行われません。このディレクティブを指定する列は、varchar、nvarchar、char、nchar、text、または ntext などのテキスト型でなければなりません。このディレクティブと組み合わせて使用できるのは、hide ディレクティブだけです。このディレクティブは、列に格納されているオーバーフロー データを収集する場合に役立ちます。内容が、正しい形式の XML でない場合は、動作が不確定な状態になります。
例
EXPLICIT モードの使用方法の例を次に示します。
A. 従業員情報を取得する
この例では、各従業員の従業員 ID と名前を取得します。AdventureWorks データベースの場合、employeeID は Employee テーブルから取得できます。従業員名は、Contact テーブルから取得できます。これらのテーブルを結合する際には、ContactID 列を使用します。
FOR XML EXPLICIT 変換で、次に示す XML が生成されるようにするとします。
<Employee EmpID="1" >
<Name FName="Guy" LName="Gilbert" />
</Employee>
...
階層は 2 レベルなので、SELECT クエリを 2 つ記述して、UNION ALL を適用します。<Employee
> 要素とその属性の値を取得する 1 つ目のクエリを次に示します。<Employee
> 要素は最上位要素なので、このクエリでは、この要素の Tag 列に値 1 を割り当て、Parent 列には NULL を割り当てます。
SELECT 1 as Tag,
NULL as Parent,
EmployeeID as [Employee!1!EmpID],
NULL as [Name!2!FName],
NULL as [Name!2!LName]
FROM HumanResources.Employee E, Person.Contact C
WHERE E.ContactID = C.ContactID
2 つ目のクエリを次に示します。このクエリでは、<Name
> 要素の値を取得します。<Name
> 要素の Tag 列に値 2 を割り当て、Parent 列には値 1 を割り当てて、親要素が <Employee
> であることを示します。
SELECT 2 as Tag,
1 as Parent,
EmployeeID,
FirstName,
LastName
FROM HumanResources.Employee E, Person.Contact C
WHERE E.ContactID = C.ContactID
UNION ALL を使用してこれらのクエリを結合し、FOR XML EXPLICIT を適用して、必要な ORDER BY 句を指定します。行セットは、まず EmployeeID で並べ替え、次に name で並べ替えます。このように並べ替えると、name に NULL が設定されている行が先に表示されるようになります。FOR XML 句を指定せずに次のクエリを実行すると、ユニバーサル テーブルが生成されます。
最終的なクエリは、次のようになります。
SELECT 1 as Tag,
NULL as Parent,
EmployeeID as [Employee!1!EmpID],
NULL as [Name!2!FName],
NULL as [Name!2!LName]
FROM HumanResources.Employee E, Person.Contact C
WHERE E.ContactID = C.ContactID
UNION ALL
SELECT 2 as Tag,
1 as Parent,
EmployeeID,
FirstName,
LastName
FROM HumanResources.Employee E, Person.Contact C
WHERE E.ContactID = C.ContactID
ORDER BY [Employee!1!EmpID],[Name!2!FName]
FOR XML EXPLICIT
結果の一部を次に示します。
<Employee EmpID="1">
<Name FName="Guy" LName="Gilbert" />
</Employee>
<Employee EmpID="2">
<Name FName="Kevin" LName="Brown" />
</Employee>
...
1 つ目の SELECT では、結果の行セット内の列名を指定しています。この名前により、2 つの列グループが形成されます。Tag 列の値が 1 のグループでは、Employee が要素であり、EmpID が属性であると識別されます。Tag 列の値が 2 のグループでは、<Name
> が要素であり、FName と LName が属性であると識別されます。
次の表に、このクエリを実行した結果として生成される行セットの一部を示します。
Tag Parent Employee!1!EmpID Name!2!FName Name!2!LName
----------- ----------- ---------------- -------------------
1 NULL 1 NULL NULL
2 1 1 Guy Gilbert
1 NULL 2 NULL NULL
2 1 2 Kevin Brown
1 NULL 3 NULL NULL
2 1 3 Roberto Tamburello
...
結果の XML ツリーを生成する際、ユニバーサル テーブル内の行は、次のように処理されます。
1 行目の Tag 列の値は 1 です。これにより、Tag 列に値 1 が割り当てられた列グループとして Employee!1!EmpID が識別されます。この列では、Employee は要素名として識別されます。その後、EmpID 属性を持つ <Employee
> 要素が作成されます。これらの属性には、対応する列の値が割り当てられます。
2 列目の Tag 列の値は 2 です。これにより、Tag 列に値 2 が割り当てられた列グループとして Name!2!FName、Name!2!LName が識別されます。これらの列では、Name が要素名として識別されます。<Name
> 要素の作成時には、FName 属性と LName 属性が識別されます。これらの属性には、対応する列の値が割り当てられます。この行の Parent 列には値 1 が割り当てられています。したがって、この要素は、1 行目の <Employee
> 要素に子要素として追加されます。
この処理が、行セット内の残りの行に対して繰り返されます。FOR XML EXPLICIT で行セットを順に処理して、必要な XML を生成できるようにするには、ユニバーサル テーブル内の行の並び順が重要になります。
B. element ディレクティブを指定する
この例は例 A と似ていますが、次に示すように要素中心の XML を生成する点が異なります。
<Employee EmpID=...>
<Name>
<FName>...</FName>
<LName>...</LName>
</Name>
</Employee>
クエリも例 A と似ていますが、列名に ELEMENT ディレクティブを指定する点が異なります。そのため、<Name
> 要素には、属性ではなく、<FName
> 子要素と <LName
> 子要素が追加されます。Employee!1!EmpID 列では ELEMENT ディレクティブが指定されていないので、EmpID は <Employee
> 要素の属性として追加されます。
SELECT 1 as Tag,
NULL as Parent,
EmployeeID as [Employee!1!EmpID],
NULL as [Name!2!FName!ELEMENT],
NULL as [Name!2!LName!ELEMENT]
FROM HumanResources.Employee E, Person.Contact C
WHERE E.ContactID = C.ContactID
UNION ALL
SELECT 2 as Tag,
1 as Parent,
EmployeeID,
FirstName,
LastName
FROM HumanResources.Employee E, Person.Contact C
WHERE E.ContactID = C.ContactID
ORDER BY [Employee!1!EmpID],[Name!2!FName!ELEMENT]
FOR XML EXPLICIT
結果の一部を次に示します。
<Employee EmpID="1">
<Name>
<FName>Guy</FName>
<LName>Gilbert</LName>
</Name>
</Employee>
<Employee EmpID="2">
<Name>
<FName>Kevin</FName>
<LName>Brown</LName>
</Name>
</Employee>
...
C. elementxsinil ディレクティブを指定する
ELEMENT ディレクティブを指定して要素中心の XML を生成する際、列に NULL 値が格納されていると、EXPLICIT モードでは対応する要素が生成されません。必要に応じて ELEMENTXSINIL ディレクティブを指定し、NULL 値の列に対して要素を生成するように要求することができます。このとき、xsi:nil 属性に値 TRUE が設定されます。
次のクエリは、従業員の住所を含む XML を生成します。AddressLine2 列と City 列には、列名に ELEMENTXSINIL ディレクティブが指定されています。この設定により、NULL 値が格納されている AddressLine2 列と City 列に対して行セットに要素が生成されます。
SELECT 1 as Tag,
NULL as Parent,
EmployeeID as [Employee!1!EmpID],
E.AddressID as [Employee!1!AddressID],
NULL as [Address!2!AddressID],
NULL as [Address!2!AddressLine1!ELEMENT],
NULL as [Address!2!AddressLine2!ELEMENTXSINIL],
NULL as [Address!2!City!ELEMENTXSINIL]
FROM HumanResources.EmployeeAddress E, Person.Address A
WHERE E.ContactID = A.ContactID
UNION ALL
SELECT 2 as Tag,
1 as Parent,
EmployeeID,
E.AddressID,
A.AddressID,
AddressLine1,
AddressLine2,
City
FROM HumanResources.EmployeeAddress E, Person.Address A
WHERE E.AddressID = A.AddressID
ORDER BY [Employee!1!EmpID],[Address!2!AddressID]
FOR XML EXPLICIT
結果の一部を次に示します。
<Employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
EmpID="1" AddressID="61">
<Address AddressID="61">
<AddressLine1>7726 Driftwood Drive</AddressLine1>
<AddressLine2 xsi:nil="true" />
<City>Monroe</City>
</Address>
</Employee>
...
D. EXPLICIT モードを使用して兄弟を構築する
販売注文情報を提供する XML を生成するとします。<SalesPerson
> 要素と <OrderDetail
> 要素は兄弟です。各注文には、<OrderHeader
> 要素が 1 つ、<SalesPerson
> 要素が 1 つ、<OrderDetail
> 要素が 1 つ以上あります。
<OrderHeader SalesOrderID=... OrderDate=... CustomerID=... >
<SalesPerson SalesPersonID=... />
<OrderDetail SalesOrderID=... LineTotal=... ProductID=... OrderQty=... />
<OrderDetail SalesOrderID=... LineTotal=... ProductID=... OrderQty=.../>
...
</OrderHeader>
<OrderHeader ...</OrderHeader>
次に示す EXPLICIT モードのクエリは、上記の形式の XML を生成します。このクエリでは、<OrderHeader
> 要素の Tag 列に 1 を、<SalesPerson
> 要素には 2 を、<OrderDetail
> 要素には 3 を指定しています。<SalesPerson
> 要素と <OrderDetail
> 要素は兄弟なので、これらの要素の Parent 列に値 1 を指定し、<OrderHeader
> 要素を親要素として特定しています。
SELECT 1 as Tag,
0 as Parent,
SalesOrderID as [OrderHeader!1!SalesOrderID],
OrderDate as [OrderHeader!1!OrderDate],
CustomerID as [OrderHeader!1!CustomerID],
NULL as [SalesPerson!2!SalesPersonID],
NULL as [OrderDetail!3!SalesOrderID],
NULL as [OrderDetail!3!LineTotal],
NULL as [OrderDetail!3!ProductID],
NULL as [OrderDetail!3!OrderQty]
FROM Sales.SalesOrderHeader
WHERE SalesOrderID=43659 or SalesOrderID=43661
UNION ALL
SELECT 2 as Tag,
1 as Parent,
SalesOrderID,
NULL,
NULL,
SalesPersonID,
NULL,
NULL,
NULL,
NULL
FROM Sales.SalesOrderHeader
WHERE SalesOrderID=43659 or SalesOrderID=43661
UNION ALL
SELECT 3 as Tag,
1 as Parent,
SOD.SalesOrderID,
NULL,
NULL,
SalesPersonID,
SOH.SalesOrderID,
LineTotal,
ProductID,
OrderQty
FROM Sales.SalesOrderHeader SOH,Sales.SalesOrderDetail SOD
WHERE SOH.SalesOrderID = SOD.SalesOrderID
AND (SOH.SalesOrderID=43659 or SOH.SalesOrderID=43661)
ORDER BY [OrderHeader!1!SalesOrderID], [SalesPerson!2!SalesPersonID],
[OrderDetail!3!SalesOrderID],[OrderDetail!3!LineTotal]
FOR XML EXPLICIT
結果の一部を次に示します。
<OrderHeader SalesOrderID="43659" OrderDate="2001-07-01T00:00:00" CustomerID="676">
<SalesPerson SalesPersonID="279" />
<OrderDetail SalesOrderID="43659" LineTotal="10.373000" ProductID="712" OrderQty="2" />
<OrderDetail SalesOrderID="43659" LineTotal="28.840400" ProductID="716" OrderQty="1" />
<OrderDetail SalesOrderID="43659" LineTotal="34.200000" ProductID="709" OrderQty="6" />
...
</OrderHeader>
<OrderHeader SalesOrderID="43661" OrderDate="2001-07-01T00:00:00" CustomerID="442">
<SalesPerson SalesPersonID="282" />
<OrderDetail SalesOrderID="43661" LineTotal="20.746000" ProductID="712" OrderQty="4" />
<OrderDetail SalesOrderID="43661" LineTotal="40.373000" ProductID="711" OrderQty="2" />
...
</OrderHeader>
E. ID ディレクティブと IDREF ディレクティブを指定する
この例は、例 C とほぼ同じですが、クエリで ID、IDREF の各ディレクティブを指定している点のみが異なります。これらのディレクティブは、<OrderHeader
> 要素と <OrderDetail
> 要素の SalesPersonID 属性の型を上書きします。これにより、ドキュメント内のリンクが形成されます。上書きされた型を確認するには、スキーマが必要です。そのため、このクエリでは、FOR XML 句に XMLDATA オプションを指定して、スキーマを取得しています。
SELECT 1 as Tag,
0 as Parent,
SalesOrderID as [OrderHeader!1!SalesOrderID!id],
OrderDate as [OrderHeader!1!OrderDate],
CustomerID as [OrderHeader!1!CustomerID],
NULL as [SalesPerson!2!SalesPersonID],
NULL as [OrderDetail!3!SalesOrderID!idref],
NULL as [OrderDetail!3!LineTotal],
NULL as [OrderDetail!3!ProductID],
NULL as [OrderDetail!3!OrderQty]
FROM Sales.SalesOrderHeader
WHERE SalesOrderID=43659 or SalesOrderID=43661
UNION ALL
SELECT 2 as Tag,
1 as Parent,
SalesOrderID,
NULL,
NULL,
SalesPersonID,
NULL,
NULL,
NULL,
NULL
FROM Sales.SalesOrderHeader
WHERE SalesOrderID=43659 or SalesOrderID=43661
UNION ALL
SELECT 3 as Tag,
1 as Parent,
SOD.SalesOrderID,
NULL,
NULL,
SalesPersonID,
SOH.SalesOrderID,
LineTotal,
ProductID,
OrderQty
FROM Sales.SalesOrderHeader SOH,Sales.SalesOrderDetail SOD
WHERE SOH.SalesOrderID = SOD.SalesOrderID
AND (SOH.SalesOrderID=43659 or SOH.SalesOrderID=43661)
ORDER BY [OrderHeader!1!SalesOrderID!id], [SalesPerson!2!SalesPersonID],
[OrderDetail!3!SalesOrderID!idref],[OrderDetail!3!LineTotal]
FOR XML EXPLICIT, XMLDATA
結果の一部を次に示します。スキーマでは、ID ディレクティブと IDREF ディレクティブにより、<OrderHeader
> 要素と <OrderDetail
> 要素の SalesOrderID 属性の型が上書きされています。これらのディレクティブを削除すると、スキーマはこれらの属性の元の型を返します。
<Schema name="Schema1" xmlns="urn:schemas-microsoft-com:xml-data" xmlns:dt="urn:schemas-microsoft-com:datatypes">
<ElementType name="OrderHeader" content="mixed" model="open">
<AttributeType name="SalesOrderID" dt:type="id" />
<AttributeType name="OrderDate" dt:type="dateTime" />
<AttributeType name="CustomerID" dt:type="i4" />
<attribute type="SalesOrderID" />
<attribute type="OrderDate" />
<attribute type="CustomerID" />
</ElementType>
<ElementType name="SalesPerson" content="mixed" model="open">
<AttributeType name="SalesPersonID" dt:type="i4" />
<attribute type="SalesPersonID" />
</ElementType>
<ElementType name="OrderDetail" content="mixed" model="open">
<AttributeType name="SalesOrderID" dt:type="idref" />
<AttributeType name="LineTotal" dt:type="number" />
<AttributeType name="ProductID" dt:type="i4" />
<AttributeType name="OrderQty" dt:type="i2" />
<attribute type="SalesOrderID" />
<attribute type="LineTotal" />
<attribute type="ProductID" />
<attribute type="OrderQty" />
</ElementType>
</Schema>
<OrderHeader xmlns="x-schema:#Schema1" SalesOrderID="43659" OrderDate="2001-07-01T00:00:00" CustomerID="676">
<SalesPerson SalesPersonID="279" />
<OrderDetail SalesOrderID="43659" LineTotal="10.373000" ProductID="712" OrderQty="2" />
...
</OrderHeader>
...
F. ID ディレクティブと IDREFS ディレクティブを指定する
要素の属性は、ID 型の属性として指定することができます。さらに、この属性を参照する IDREFS 属性を使用できます。これにより、ドキュメント内のリンク作成が可能になります。これは、リレーショナル データベースにおける主キーと外部キーのリレーションシップに似ています。
この例では、ID ディレクティブと IDREFS ディレクティブを使用して、ID 型と IDREFS 型の属性を作成する方法を示します。ID は整数値にできないので、この例では ID 値を変換します。つまり、型キャストを行っています。ID 値にはプレフィックスを使用します。
次のような XML を生成するとします。
<Customer CustomerID="C1" SalesOrderIDList=" O11 O22 O33..." >
<SalesOrder SalesOrderID="O11" OrderDate="..." />
<SalesOrder SalesOrderID="O22" OrderDate="..." />
<SalesOrder SalesOrderID="O33" OrderDate="..." />
...
</Customer>
< Customer
> 要素の SalesOrderIDList 属性は、複数の値を指定できる属性であり、< SalesOrder
> 要素の SalesOrderID 属性を参照しています。このリンクを確立するには、SalesOrderID 属性を ID 型として宣言し、< Customer
> 要素の SalesOrderIDList 属性を IDREFS 型として宣言する必要があります。各顧客が複数の注文を発注することが考えられるので、IDREFS 型を使用しています。
IDREFS も、複数の値を保持できます。このため、別の SELECT 句を使用して、同じタグ、親、およびキーの情報を再利用する必要があります。さらに、IDREFS 値を構成する一連の行が、それぞれの親要素の直後にグループ化された状態で表示されるように、ORDER BY 句を指定します。
求める XML を生成するクエリを次に示します。このクエリでは、ID ディレクティブと IDREFS ディレクティブを使用して、指定した列名の型を上書きしています (SalesOrder!2!SalesOrderID!ID、Customer!1!SalesOrderIDList!IDREFS)。
SELECT 1 as Tag,
0 as Parent,
C.CustomerID [Customer!1!CustomerID],
NULL [Customer!1!SalesOrderIDList!IDREFS],
NULL [SalesOrder!2!SalesOrderID!ID],
NULL [SalesOrder!2!OrderDate]
FROM Sales.Customer C
UNION ALL
SELECT 1 as Tag,
0 as Parent,
C.CustomerID,
'O-'+CAST(SalesOrderID as varchar(10)),
NULL,
NULL
FROM Sales.Customer C, Sales.SalesOrderHeader SOH
WHERE C.CustomerID = SOH.CustomerID
UNION ALL
SELECT 2 as Tag,
1 as Parent,
C.CustomerID,
NULL,
'O-'+CAST(SalesOrderID as varchar(10)),
OrderDate
FROM Sales.Customer C, Sales.SalesOrderHeader SOH
WHERE C.CustomerID = SOH.CustomerID
ORDER BY [Customer!1!CustomerID] ,
[SalesOrder!2!SalesOrderID!ID],
[Customer!1!SalesOrderIDList!IDREFS]
FOR XML EXPLICIT
G. 結果の XML に要素と属性が表示されないように、hide ディレクティブを使用する
この例では、hide ディレクティブの使用方法を示します。クエリから返されたユニバーサル テーブル内の行を並べ替える目的でクエリから属性を返し、最終的な結果の XML ドキュメントにはその属性を含めない場合に、このディレクティブが役立ちます。
このクエリでは、次の XML を生成します。
<ProductModel ProdModelID="19" Name="Mountain-100">
<Summary>
<SummaryDescription>
<Summary> element from XML stored in CatalogDescription column
</SummaryDescription>
</Summary>
</ProductModel>
このクエリでは、求める XML を生成しています。Tag 列に値 1 と 2 を持つ 2 つの列グループが識別されます。
このクエリでは、xml データ型の query() メソッド (xml データ型) を使用し、xml 型の CatalogDescription 列に対するクエリを実行して、概要情報を取得します。また、このクエリでは、xml データ型の value() メソッド (xml データ型) を使用して、CatalogDescription 列から ProductModelID 値を取得します。この値は、結果の行セットを並べ替えるために必要ですが、結果の XML ドキュメントでは必要ありません。そのため、列名 [Summary!2!ProductModelID!hide] には hide ディレクティブが含まれています。この列が SELECT ステートメントに含まれていなければ、xml 型の [ProductModel!1!ProdModelID] 列と [Summary!2!SummaryDescription] 列で行セットを並べ替える必要がありますが、ORDER BY 句では xml 型を使用することはできません。このため、[Summary!2!ProductModelID!hide] 列を追加し、これを ORDER BY 句で指定しています。
SELECT 1 as Tag,
0 as Parent,
ProductModelID as [ProductModel!1!ProdModelID],
Name as [ProductModel!1!Name],
NULL as [Summary!2!ProductModelID!hide],
NULL as [Summary!2!SummaryDescription]
FROM Production.ProductModel
WHERE CatalogDescription is not null
UNION ALL
SELECT 2 as Tag,
1 as Parent,
ProductModelID,
Name,
CatalogDescription.value('
declare namespace PD="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelDescription";
(/PD:ProductDescription/@ProductModelID)[1]', 'int'),
CatalogDescription.query('
declare namespace pd="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelDescription";
/pd:ProductDescription/pd:Summary')
FROM Production.ProductModel
WHERE CatalogDescription is not null
ORDER BY [ProductModel!1!ProdModelID],[Summary!2!ProductModelID!hide]
FOR XML EXPLICIT
go
結果を次に示します。
<ProductModel ProdModelID="19" Name="Mountain-100">
<Summary>
<SummaryDescription>
<pd:Summary xmlns:pd="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelDescription" >
<p1:p xmlns:p1="http://www.w3.org/1999/xhtml">Our top-of-the-line competition mountain bike. Performance-enhancing options include the innovative HL Frame, super-smooth front suspension, and traction for all terrain. </p1:p>
</pd:Summary>
</SummaryDescription>
</Summary>
</ProductModel>
H. element ディレクティブとエンティティのエンコードを指定する
この例では、element ディレクティブと xml ディレクティブの違いを説明します。element ディレクティブを指定した場合はデータがエンティティとしてエンコードされますが、xml ディレクティブを指定した場合はその処理が行われません。このクエリでは、<Summary> 要素に、<Summary>This is summary description</Summary>
のように XML が割り当てられています。
次のクエリについて考えてみます。
SELECT 1 as Tag,
0 as Parent,
ProductModelID as [ProductModel!1!ProdModelID],
Name as [ProductModel!1!Name],
NULL as [Summary!2!SummaryDescription!ELEMENT]
FROM Production.ProductModel
WHERE ProductModelID=19
UNION ALL
SELECT 2 as Tag,
1 as Parent,
ProductModelID,
NULL,
'<Summary>This is summary description</Summary>'
FROM Production.ProductModel
WHERE ProductModelID=19
FOR XML EXPLICIT
結果は次のとおりです。概要説明がエンティティとしてエンコードされています。
<ProductModel ProdModelID="19" Name="Mountain-100">
<Summary>
<SummaryDescription><Summary>This is summary description</Summary></SummaryDescription>
</Summary>
</ProductModel>
ここで、Summary!2!SummaryDescription!xml
のように、列名に element ディレクティブではなく xml ディレクティブを指定すると、概要情報のエンコード処理が行われません。
<ProductModel ProdModelID="19" Name="Mountain-100">
<Summary>
<SummaryDescription>
<Summary>This is summary description</Summary>
</SummaryDescription>
</Summary>
</ProductModel>
次のクエリでは、静的な XML 値を割り当てる代わりに、xml 型の query() メソッドを使用して、xml 型の CatalogDescription 列から製品モデルの概要情報を取得します。この場合、結果は xml 型であることがわかっているので、エンコード処理が適用されません。
SELECT 1 as Tag,
0 as Parent,
ProductModelID as [ProductModel!1!ProdModelID],
Name as [ProductModel!1!Name],
NULL as [Summary!2!SummaryDescription]
FROM Production.ProductModel
WHERE CatalogDescription is not null
UNION ALL
SELECT 2 as Tag,
1 as Parent,
ProductModelID,
Name,
(SELECT CatalogDescription.query('
declare namespace pd="https://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ProductModelDescription";
/pd:ProductDescription/pd:Summary'))
FROM Production.ProductModel
WHERE CatalogDescription is not null
ORDER BY [ProductModel!1!ProdModelID],Tag
FOR XML EXPLICIT
I. cdata ディレクティブを指定する
cdata ディレクティブが指定されている場合、含まれているデータはエンティティとしてエンコードされず、CDATA セクションに配置されます。cdata 属性は、無名でなければなりません。
次のクエリでは、製品モデルの概要説明を CDATA セクションに配置しています。
SELECT 1 as Tag,
0 as Parent,
ProductModelID as [ProductModel!1!ProdModelID],
Name as [ProductModel!1!Name],
'<Summary>This is summary description</Summary>'
as [ProductModel!1!!cdata] -- no attribute name so ELEMENT assumed
FROM Production.ProductModel
WHERE ProductModelID=19
FOR XML EXPLICIT
結果を次に示します。
<ProductModel ProdModelID="19" Name="Mountain-100">
<![CDATA[<Summary>This is summary description</Summary>]]>
</ProductModel>
J. xmltext ディレクティブを指定する
この例では、EXPLICIT モードを使用した SELECT ステートメントで、xmltext ディレクティブによりオーバーフロー列のデータを指定する方法を示します。
Person テーブルについて考えます。このテーブルには、XML ドキュメントの未使用部分を格納している Overflow 列があります。
CREATE TABLE Person(PersonID varchar(5), PersonName varchar(20), Overflow nvarchar(200))
INSERT INTO Person VALUES ('P1','Joe',N'<SomeTag attr1="data">content</SomeTag>')
INSERT INTO Person VALUES ('P2','Joe',N'<SomeTag attr2="data"/>')
INSERT INTO Person VALUES ('P3','Joe',N'<SomeTag attr3="data" PersonID="P">content</SomeTag>')
このクエリでは、Person テーブルから列を取得します。Overflow 列には AttributeName が指定されていませんが、ユニバーサル テーブルの列名の一部として、xmltextディレクティブが設定されています。
SELECT 1 as Tag, NULL as parent,
PersonID as [Parent!1!PersonID],
PersonName as [Parent!1!PersonName],
overflow as [Parent!1!!xmltext] -- No AttributeName; xmltext directive
FROM Person
FOR XML EXPLICIT
結果の XML ドキュメントは次のようになります。
- Overflow 列には AttributeName が指定されていませんが、xmltext ディレクティブが指定されているので、<
overflow
> 要素の属性は、囲み要素である <Parent
> の属性リストに追加されます。 - <
xmltext
> 要素の PersonID 属性は、同じ要素レベルで取得される PersonID 属性と競合するので、<xmltext
> 要素の属性は無視されます。これは、PersonID 属性の値が NULL であっても同じです。一般的に、属性はオーバーフローに含まれる同じ名前の属性を上書きします。
結果を次に示します。
<Parent PersonID="P1" PersonName="Joe" attr1="data">content</Parent>
<Parent PersonID="P2" PersonName="Joe" attr2="data"></Parent>
<Parent PersonID="P3" PersonName="Joe" attr3="data">content</Parent>
オーバーフロー データにサブ要素がある場合に同じクエリを指定すると、Overflow 列内のサブ要素は、囲み要素である <Parent
> のサブ要素として追加されます。
たとえば、次のように Person テーブルのデータを変更したとします。Overflow 列にサブ要素が含まれています。
TRUNCATE TABLE Person
INSERT INTO Person VALUES ('P1','Joe',N'<SomeTag attr1="data">content</SomeTag>')
INSERT INTO Person VALUES ('P2','Joe',N'<SomeTag attr2="data"/>')
INSERT INTO Person VALUES ('P3','Joe',N'<SomeTag attr3="data" PersonID="P"><name>PersonName</name></SomeTag>')
同じクエリを実行すると、<xmltext
> 要素のサブ要素は、囲み要素である <Parent
> のサブ要素として追加されます。
SELECT 1 as Tag, NULL as parent,
PersonID as [Parent!1!PersonID],
PersonName as [Parent!1!PersonName],
overflow as [Parent!1!!xmltext] -- no AttributeName, xmltext directive
FROM Person
FOR XML EXPLICIT
次に結果を示します。
<Parent PersonID="P1" PersonName="Joe" attr1="data">content</Parent>
<Parent PersonID="P2" PersonName="Joe" attr2="data"></Parent>
<Parent PersonID="P3" PersonName="Joe" attr3="data">
<name>PersonName</name>
</Parent>
AttributeName と xmltext ディレクティブの両方を指定した場合、<overflow
> 要素の属性は、囲み要素である <Parent
> のサブ要素の属性として追加されます。AttributeName に指定された名前がサブ要素の名前になります。
このクエリでは、AttributeName (<overflow
>) と xmltext ディレクティブの両方が指定されています。**
SELECT 1 as Tag, NULL as parent,
PersonID as [Parent!1!PersonID],
PersonName as [Parent!1!PersonName],
overflow as [Parent!1!overflow!xmltext] -- overflow is AttributeName
-- xmltext is directive
FROM Person
FOR XML EXPLICIT
結果を次に示します。
<Parent PersonID="P1" PersonName="Joe">
<overflow attr1="data">content</overflow>
</Parent>
<Parent PersonID="P2" PersonName="Joe">
<overflow attr2="data" />
</Parent>
<Parent PersonID="P3" PersonName="Joe">
<overflow attr3="data" PersonID="P">
<name>PersonName</name>
</overflow>
</Parent>
このクエリでは、PersonName 属性に element ディレクティブを指定します。これにより、PersonName 属性は、囲み要素である <Parent
> 要素のサブ要素として追加されます。<xmltext
> の属性も、依然として、囲み要素 <Parent
> に追加されます。<overflow
> 要素の内容 (サブ要素) は、囲み要素である <Parent
> 要素の他のサブ要素の前に追加されます。
SELECT 1 as Tag, NULL as parent,
PersonID as [Parent!1!PersonID],
PersonName as [Parent!1!PersonName!element], -- element directive
overflow as [Parent!1!!xmltext]
FROM Person
FOR XML EXPLICIT
結果を次に示します。
<Parent PersonID="P1" attr1="data">content<PersonName>Joe</PersonName>
</Parent>
<Parent PersonID="P2" attr2="data">
<PersonName>Joe</PersonName>
</Parent>
<Parent PersonID="P3" attr3="data">
<name>PersonName</name>
<PersonName>Joe</PersonName>
</Parent>
xmltext 列のデータにルート要素の属性が含まれている場合、これらの属性は XML データ スキーマに反映されません。結果の XML ドキュメントのその部分に関して、MSXML パーサーによる検証は行われません。次に例を示します。
SELECT 1 as Tag,
0 as Parent,
N'<overflow a="1"/>' as 'overflow!1!!xmltext'
FOR XML EXPLICIT, xmldata
結果は次のとおりです。返されたスキーマでは、オーバーフロー要素の属性 a が欠落している点に注意してください。
<Schema name="Schema2"
xmlns="urn:schemas-microsoft-com:xml-data"
xmlns:dt="urn:schemas-microsoft-com:datatypes">
<ElementType name="overflow" content="mixed" model="open">
</ElementType>
</Schema>
<overflow xmlns="x-schema:#Schema2" a="1">
</overflow>
参照
関連項目
RAW モードの使用
AUTO モードの使用
FOR XML を使用した XML の構築