ASA SQL User's Guide
Using XML in the Database
Obtaining query results as XML
FOR XML EXPLICIT allows you to control the structure of the XML document returned by the query. The query must be written in a particular way so that information about the nesting you desire is specified within the query result. The optional directives supported by FOR XML EXPLICIT allow you to configure the treatment of individual columns. For example, you can control whether a column appears as element or attribute content, or whether a column is used only to order the result, rather than appearing in the generated XML.
For an example of how to write a query using FOR XML EXPLICIT, see Writing an EXPLICIT mode query.
In EXPLICIT mode, the first two columns in the SELECT statement must be named Tag and Parent, respectively. Tag and Parent are metadata columns, and their values are used to determine the parent-child relationship, or nesting, of the elements in the XML document that is returned by the query.
Tag column This is the first column specified in the select list. The Tag column stores the tag number of the current element. Permitted values for tag numbers are 1 to 255.
Parent column This column stores the tag number for the parent of the current element. If the value in this column is NULL, the row is placed at the top level of the XML hierarchy.
For example, consider a query that returns the following result set when FOR XML EXPLICIT is not specified. (The purpose of the first_name!1 and id!2 data columns is discussed in the following section, Adding data columns to the query).
Tag | Parent | first_name!1 | id!2 |
---|---|---|---|
1 | NULL | 'Beth' | NULL |
2 | NULL | NULL | '102' |
In this example, the values in the Tag column are the tag numbers for each element in the result set. The Parent column for both rows contains the value NULL. This means that both elements are generated at the top level of the hierarchy, giving the following result when the query includes the FOR XML EXPLICIT clause:
<first_name>Beth</first_name> <id>102</id>
However, if the second row had the value 1 in the Parent column, the result would look as follows:
<first_name>Beth <id>102</id> </first_name>
For an example of how to write a query using FOR XML EXPLICIT, see Writing an EXPLICIT mode query.
In addition to the Tag and Parent columns, the query must also contain one or more data columns. The names of these data columns control how the columns are interpreted during tagging. Each column name is split into fields separated by an exclamation mark (!). The following fields can be specified for data columns:
ElementName!TagNumber!AttributeName!Directive
ElementName the name of the element. For a given row, the name of the element generated for the row is taken from the ElementName field of the first column with a matching tag number. If there are multiple columns with the same TagNumber, the ElementName is ignored for subsequent columns with the same TagNumber. In the example above, the first row generates an element called <first_name>.
TagNumber the tag number of the element. For a row with a given tag value, all columns with the same value in their TagNumber field will contribute content to the element that corresponds to that row.
AttributeName specifies that the column value is an attribute of the ElementName element. For example, if a data column had the name prod_id!1!color, then color would appear as an attribute of the <prod_id> element.
Directive this optional field allows you to control the format of the XML document further. You can specify any one of the following values for Directive:
hide indicates that this column is ignored for the purpose of generating the result. This directive can be used to include columns that are only used to order the table. The attribute name is ignored and does not appear in the result.
For an example using the hide directive, see Using the hide directive.
element indicates that the column value is inserted as a nested element with the name AttributeName, rather than as an attribute.
For an example using the element directive, see Using the element directive.
xml indicates that the column value is inserted with no quoting. If the AttributeName is specified, the value is inserted as an element with that name. Otherwise, it is inserted with no wrapping element. If this directive is not used, then markup characters are quoted unless the column is of type XML. For example, the value <a/> would be inserted as <a/>.
For an example using the xml directive, see Using the xml directive.
cdata indicates that the column value is to be inserted as a CDATA section. The AttributeName is ignored.
For an example using the cdata directive, see Using the cdata directive.
Data in BINARY, LONG BINARY, IMAGE, and VARBINARY columns is automatically returned in base64-encoded format when you execute a query that contains FOR XML EXPLICIT. By default, any NULL values in the result set are omitted. You can change this behavior by changing the setting of the FOR_XML_NULL_TREATMENT option.
For more information about the FOR_XML_NULL_TREATMENT option, see FOR_XML_NULL_TREATMENT option [database] and FOR XML and NULL values.
Suppose you want to write a query using FOR XML EXPLICIT that generates the following XML document:
<employee emp_id='129'> <customer cust_id='107' region='Eastern'/> <customer cust_id='119' region='Western'/> <customer cust_id='131' region='Eastern'/> </employee>
<employee emp_id='195'> <customer cust_id='109' region='Eastern'/> <customer cust_id='121' region='Central'/> </employee>
You do this by writing a SELECT statement that returns the following result set in the exact order specified, and then appending FOR XML EXPLICIT to the query.
Tag | Parent | employee!1!emp_id | customer!2!cust_id | customer!2!region |
---|---|---|---|---|
1 | NULL | 129 | NULL | NULL |
2 | 1 | 129 | 107 | Eastern |
2 | 1 | 129 | 119 | Western |
2 | 1 | 129 | 131 | Central |
1 | NULL | 195 | NULL | NULL |
2 | 1 | 195 | 109 | Eastern |
2 | 1 | 195 | 121 | Central |
When you write your query, only some of the columns for a given row become part of the generated XML document. A column is included in the XML document only if the value in the TagNumber field (the second field in the column name) matches the value in the Tag column.
In the example, the third column is used for the two rows that have the value 1 in their Tag column. In the fourth and fifth columns, the values are used for the rows that have the value 2 in their Tag column. The element names are taken from the first field in the column name. In this case, <employee> and <customer> elements are created.
The attribute names come from the third field in the column name, so an emp_id attribute is created for <employee> elements, while cust_id and region attributes are generated for <customer> elements.
The following steps explain how to construct the FOR XML EXPLICIT query that generates an XML document similar to the one found at the beginning of this section using the sample database.
To write a FOR XML EXPLICIT query
Write a SELECT statement to generate the top-level elements.
In this example, the first SELECT statement in the query generates the <employee> elements. The first two values in the query must be the Tag and Parent column values. The <employee> element is at the top of the hierarchy, so it is assigned a Tag value of 1, and a Parent value of NULL.
NoteIf you are writing an EXPLICIT mode query that uses a UNION, then only the column names specified in the first SELECT statement are used. Column names that are to be used as element or attribute names must be specified in the first SELECT statement because column names specified in subsequent SELECT statements are ignored. |
To generate the <employee> elements for the table above, your first SELECT statement is as follows:
SELECT 1 AS tag, NULL AS parent, emp_id AS [employee!1!emp_id], NULL AS [customer!2!cust_id], NULL AS [customer!2!region] FROM employee
Write a SELECT statement to generate the child elements.
The second query generates the <customer> elements. Because this is an EXPLICIT mode query, the first two values specified in all the SELECT statements must be the Tag and Parent values. The <customer> element is given the tag number 2, and because it is a child of the <employee> element, it has a Parent value of 1. The first SELECT statement has already specified that emp_id, cust_id, and region are attributes.
SELECT 2, 1, emp_id, cust_id, region FROM employee KEY JOIN sales_order
Add a UNION ALL to the query to combine the two SELECT statements together:
SELECT 1 AS tag, NULL AS parent, emp_id AS [employee!1!emp_id], NULL AS [customer!2!cust_id], NULL AS [customer!2!region] FROM employee UNION ALL
SELECT 2, 1, emp_id, cust_id, region FROM employee KEY JOIN sales_order
Add an ORDER BY clause to specify the order of the rows in the result. The order of the rows is the order that is used in the resulting document.
SELECT 1 AS tag, NULL AS parent, emp_id AS [employee!1!emp_id], NULL AS [customer!2!cust_id], NULL AS [customer!2!region] FROM employee UNION ALL
SELECT 2, 1, emp_id, cust_id, region FROM employee KEY JOIN sales_order ORDER BY 3, 1 FOR XML EXPLICIT
For information about the syntax of EXPLICIT mode, see Parameters.
The following example query retrieves information about the orders placed by employees. In this example, there are three types of elements: <emp>, <order>, and <dept>. The <emp> element has id and name attributes, the <order> element has a name attribute, and the <dept> element has a date attribute.
SELECT 1 tag, NULL parent, emp_id [emp!1!id], emp_fname [emp!1!name], NULL [order!2!date], NULL [dept!3!name] FROM employee UNION ALL
SELECT 2, 1, emp_id, NULL, order_date, NULL FROM employee KEY JOIN sales_order UNION ALL
SELECT 3, 1, emp_id, NULL, NULL, dept_name FROM employee e JOIN department d ON e.dept_id=d.dept_id ORDER BY 3, 1 FOR XML EXPLICIT
You get the following result from this query:
<emp id="102" name="Fran"> <dept name="R & D"/> </emp> <emp id="105" name="Matthew"> <dept name="R & D"/> </emp>
<emp id="129" name="Philip"> <order date="2000-07-24"/> <order date="2000-07-13"/> <order date="2000-06-24"/> <order date="2000-06-08"/> ... <dept name="Sales"/> </emp>
<emp id="148" name="Julie"> <dept name="Finance"/> </emp> ...
If you wish to generate sub-elements rather than attributes, you can add the element directive to the query, as follows:
SELECT 1 tag, NULL parent, emp_id [emp!1!id!element], emp_fname [emp!1!name!element], NULL [order!2!date!element], NULL [dept!3!name!element] FROM employee
UNION ALL SELECT 2, 1, emp_id, NULL, order_date, NULL FROM employee KEY JOIN sales_order UNION ALL
SELECT 3, 1, emp_id, NULL, NULL, dept_name FROM employee e JOIN department d ON e.dept_id=d.dept_id ORDER BY 3, 1 FOR XML EXPLICIT
You get the following result from this query:
<emp> <id>102</id> <name>Fran</name> <dept> <name>R & D</name> </dept> </emp>
<emp> <id>105</id> <name>Matthew</name> <dept> <name>R & D</name> </dept> </emp>
<emp> <id>129</id> <name>Philip</name> <order> <date>2000-07-24</date> </order> <order> <date>2000-07-13</date> </order> <order> <date>2000-06-24</date> </order> ... <dept> <name>Sales</name> </dept> </emp> ...
In the following query, the employee ID is used to order the result, but the employee ID does not appear in the result because the hide directive is specified:
SELECT 1 tag, NULL parent, emp_id [emp!1!id!hide], emp_fname [emp!1!name], NULL [order!2!date], NULL [dept!3!name] FROM employee UNION ALL
SELECT 2, 1, emp_id, NULL, order_date, NULL FROM employee KEY JOIN sales_order UNION ALL
SELECT 3, 1, emp_id, NULL, NULL, dept_name FROM employee e JOIN department d ON e.dept_id=d.dept_id ORDER BY 3, 1 FOR XML EXPLICIT
This query returns the following result:
<emp name="Fran"> <dept name="R & D"/> </emp> <emp name="Matthew"> <dept name="R & D"/> </emp>
<emp name="Philip"> <order date="2000-07-24"/> <order date="2000-07-13"/> <order date="2000-06-24"/> <order date="2000-06-08"/> ... <dept name="Sales"/> </emp>
<emp name="Julie"> <dept name="Finance"/> </emp> ...
By default, when the result of a FOR XML EXPLICIT query contains characters that are not valid XML name characters, the invalid are escaped (for information see Invalid column names) unless the column is of type XML. For example, the following query generates XML that contains an ampersand (&):
SELECT 1 AS tag, NULL AS parent, id AS [customer!1!id!element], company_name AS [customer!1!company_name] FROM customer WHERE id = '115' FOR XML EXPLICIT
In the result generated by this query, the ampersand is quoted because the column is not of type XML:
<customer company_name="Sterling & Co."> <id>115</id> </customer>
The xml directive indicates that the column value is inserted into the generated XML with no quoting. If you execute the same query as above with the xml directive:
SELECT 1 AS tag, NULL AS parent, id AS [customer!1!id!element], company_name AS [customer!1!company_name!xml] FROM customer WHERE id = '115' FOR XML EXPLICIT
the ampersand is not quoted in the result:
<customer> <id>115</id> <company_name>Sterling & Co.</company_name> </customer>
Note that this XML is not well-formed because it contains an ampersand, which is a special character in XML. When XML is generated by a query, it is your responsibility to ensure that the XML is well-formed and valid: Adaptive Server Anywhere does not check whether the XML being generated is well-formed or valid.
When you specify the xml directive, the AttributeName field is ignored, and elements are generated rather than attributes.
The following query uses the cdata directive to return the customer name in a CDATA section:
SELECT 1 AS tag, NULL AS parent, id AS [product!1!id], description AS [product!1!cdata] FROM product FOR XML EXPLICIT
The result produced by this query lists the description for each product in a CDATA section. Data contained in the CDATA section is not quoted:
<product id="300"> <![CDATA[Tank Top]]> </product> <product id="301"> <![CDATA[V-neck]]> </product>
<product id="302"> <![CDATA[Crew Neck]]> </product> <product id="400"> <![CDATA[Cotton Cap]]> </product> ...