XML Workshop I - Generating XML output using FOR XML with AUTO and RAW

Introduction

In the last 2 sessions on XML processing, we have seen various examples of reading and generating XML data. In case you have not seen the last two sessions, you could find them here.

  1. Advanced XML Processing
    This article shows several examples that generates XML data using the FOR XML keyword. This article demonstrated examples using AUTO and RAW modes.
  2. More Advanced XML Processing Examples
    This article shows several examples that queries/retrieves values from an XML variable/column

In this session, we will see XML processing examples using PATH mode. I would suggest you read the previous article which presented several examples using RAW and AUTO modes.

FOR XML PATH

RAW and AUTO modes are good enough for most of the XML formatting requirements. PATH mode provides a few additional formatting capabilities that we will examine through the examples given below.

Example 1: Generating a hierarchy of XML nodes

1 /*

2 Using the PATH mode, you can generate hierarchies of XML nodes. The XML generation is

3 controlled by the ALIAS of the column. In the example given below, a new node is

4 created which holds "ItemNumber" and "Quantity". Note the slash ("/") used within the

5 column name, which really controls the XML generation.

6 */

7

8 SELECT

9 OrderNumber AS 'orderNumber',

10 ItemNumber AS 'item/itemNumber',

11 Qty AS 'item/Quantity'

12 FROM OrderDetails FOR XML PATH('orderInfo'), TYPE, ELEMENTS, ROOT('order')

13

14 /*

15 OUTPUT:

16

17

18

19 00001

20

21 A001

22 10

23

24

25

26 00001

27

28 A002

29 20

30

31

32

33 */

Example 2: Generating a List of Values

1 /*

2 We just saw how the PATH keyword can be used to re-structure the XML

3 generated by FOR XML. Let us now look at a few other scenarios where

4 the PATH keyword is useful.

5

6 One of the situations, where the PATH keyword can come handy is to create a list

7 of values. The following query returns a list of Item Numbers from the Order table.

8 */

9

10 SELECT

11 ItemNumber AS 'data()'

12 FROM

13 OrderDetails

14 FOR XML PATH('')

15

16 /*

17 Output:

18

19 A001 A002 A003

20 */

21

22 /*

23 You will note that the above query returns the results as a SPACE separated

24 value. Most of the times we work with COMMA separated values, not SPACE separated

25 values. So let us see how to generate a COMMA separated value list.

26

27 There is no direct way to do this. But we can do it with a small trick. Look at

28 the following example

29 */

30

31 SELECT

32 ',' + ItemNumber AS 'data()'

33 FROM

34 OrderDetails

35 FOR XML PATH('')

36

37 /*

38 Output:

39

40 ,A001 ,A002 ,A003

41 */

42

43 /*

44 Well, we are almost done. But it is not really good still. You will note that the

45 string starts with a COMMA. Lets use the STUFF function to remove this. Here is the

46 final query.

47 */

48

49 SELECT STUFF(

50 (SELECT

51 ',' + ItemNumber AS 'data()'

52 FROM

53 OrderDetails

54 FOR XML PATH('')),1,1,'')

55

56 /*

57 Output:

58

59 A001 ,A002 ,A003

60 */

61

62 /*

63 I just found Jamie's blog which suggests that using the above approach is

64 better than the other options to create a list of values. You can find the blog

65 here: http://blogs.conchango.com/jamiethomson/archive/2007/04/05/T_2D00_SQL_3A00_-A-T_2D00_SQL-Poser--_2D00_--Part-3.aspx

66 */

PATH v/s AUTO and RAW

PATH provides more control over the hierarchy by manipulating the column alias. This would be very handy most of the times.EXPLICIT provides more control over the XML generation, but the usage is pretty complex. I will present a step by step approach to master the EXPLICIT usage in the next article.

Let us take a trial assignment and see how each XML mode (AUTO, RAW and PATH) can be used to accomplish the given task. Let us assume that we have 3 tables: Customer, City and Country. We need to retrieve data from the above tables, in the XML structure given below.

1 <customersByRegion>

2 <country name="USA" currency="US Dollars">

3 <city name="NY">

4 <customer id="MK" name="John Mark" phone="111-111-1111"/>

5 <customer id="WS" name="Will Smith" phone="222-222-2222"/>

6 city>

7 <city name="NJ">

8 <customer id="EN" name="Elizabeth Lincoln" phone="333-333-3333"/>

9 city>

10 country>

11 <country name="England" currency="Pound Sterling">

12 <city name="London">

13 <customer id="TH" name="Thomas Hardy" phone="444-444-4444"/>

14 city>

15 country>

16 <country name="India" currency="Rupees">

17 <city name="New Delhi">

18 <customer id="JS" name="Jacob Sebastian" phone="555-555-5555"/>

19 city>

20 country>

21 customersByRegion>

Let us run the script to generate the tables and insert the data that we need.

1 CREATE TABLE Countries (CountryID INT, CountryName VARCHAR(20), Currency VARCHAR(20))

2 CREATE TABLE Cities (CityID INT, CityName VARCHAR(20), CountryID INT)

3 CREATE TABLE Customers (CustomerNumber VARCHAR(2), CustomerName VARCHAR(40), PhoneVARCHAR(20), CityID INT)

4

5 INSERT INTO Countries(CountryID, CountryName, Currency)

6 SELECT 1 AS CountryID, 'USA' AS CountryName, 'US Dollars' as Currency UNION

7 SELECT 2, 'England', 'Pound Sterling' UNION

8 SELECT 3, 'India', 'Rupee'

9

10 INSERT INTO Cities(CityID, CityName, CountryID)

11 SELECT 1 AS CityID, 'NY' AS CityName, 1 AS CountryID UNION

12 SELECT 2, 'NJ', 1 UNION

13 SELECT 3, 'London', 2 UNION

14 SELECT 4, 'New Delhi', 3

15

16 INSERT INTO Customers(CustomerNumber, CustomerName, Phone, CityID)

17 SELECT 'MK' AS CustomerNumber, 'John Mark' AS CustomerName, '111-111-1111' ASPhone, 1 AS CityID UNION

18 SELECT 'WS', 'Will Smith', '222-222-2222', 1 UNION

19 SELECT 'EN', 'Elizabeth Lincoln', '333-333-3333', 2 UNION

20 SELECT 'TH', 'Thomas Hardy', '444-444-4444', 3 UNION

21 SELECT 'JS', 'Jacob Sebastian', '555-555-5555', 4

Let us start with PATH and see if we can generate the above XML structure with it. Let us write the first version of our TSQL asfollows.

1 SELECT

2 Country.CountryName AS 'country/name',

3 Country.Currency AS 'country/currency',

4 City.CityName AS 'country/city/name',

5 Customer.CustomerNumber AS 'country/city/customer/id',

6 Customer.CustomerName AS 'country/city/customer/name',

7 Customer.Phone AS 'country/city/customer/phone'

8 FROM

9 Customers Customer

10 INNER JOIN Cities City ON (City.CityID = Customer.CityID)

11 INNER JOIN Countries Country ON (Country.CountryID = City.CountryID)

12 ORDER BY CountryName, CityName

13 FOR XML PATH

14

15 /*

16 OUTPUT:

17

18

19

20 England

21 Pound Sterling

22

23 London

24

25 TH

26 Thomas Hardy

27 444-444-4444

28

29

30

31

32

33

34 India

35 Rupee

36

37 New Delhi

38

39 JS

40 Jacob Sebastian

41 555-555-5555

42

43

44

45

46

47 ...

48

49 ...

50 */

Well, not very good. You must have noticed that the format is not what we needed. We need the values as Attributes, but this query returns values as XML nodes. So, let us look at the following query which generates results where values are presented asAttributes.

1 SELECT

2 Country.CountryName AS 'country/@name',

3 Country.Currency AS 'country/@currency',

4 City.CityName AS 'country/city/@name',

5 Customer.CustomerNumber AS 'country/city/customer/@id',

6 Customer.CustomerName AS 'country/city/customer/@name',

7 Customer.Phone AS 'country/city/customer/@phone'

8 FROM

9 Customers Customer

10 INNER JOIN Cities City ON (City.CityID = Customer.CityID)

11 INNER JOIN Countries Country ON (Country.CountryID = City.CountryID)

12 ORDER BY CountryName, CityName

13 FOR XML PATH(''), ROOT('CustomersByRegion')

14

15 /*

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43 */

We have results very close to what we need. But we are missing something. You will notice that the results are not grouped. “USA” has 3 nodes in the XML data generated by the query. This should be grouped into a single node. However, PATH does not provide a way to do that. Let us look into other modes to see if they can help in this scenario.

Let us move on to AUTO mode. The following code tries to generate the required XML structure using the AUTO mode.

1 SELECT

2 Country.CountryName AS [name],

3 Country.Currency,

4 City.CityName AS [name],

5 Customer.CustomerNumber AS [id],

6 Customer.CustomerName AS [name],

7 Customer.Phone

8 FROM

9 Customers Customer

10 INNER JOIN Cities City ON (City.CityID = Customer.CityID)

11 INNER JOIN Countries Country ON (Country.CountryID = City.CountryID)

12 ORDER BY CountryName, CityName

13 FOR XML AUTO

14

15 /*

16 OUTPUT:

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37 */

Here again, we are able to get perfect XML except the ROOT element. XML is not always useful without the REQUIRED root element. AUTO mode does not provide a way to specify a ROOT element for the resultant XML. Let us see if we can find a workaround to get this done. The following code shows a workaround to fix the above limitation.

1 SELECT CAST ('' + (SELECT

2 Country.CountryName AS [name],

3 Country.Currency,

4 City.CityName AS [name1],

5 Customer.CustomerNumber AS [id],

6 Customer.CustomerName AS [name2],

7 Customer.Phone

8 FROM

9 Customers Customer

10 INNER JOIN Cities City ON (City.CityID = Customer.CityID)

11 INNER JOIN Countries Country ON (Country.CountryID = City.CountryID)

12 ORDER BY CountryName, CityName

13 FOR XML AUTO) + '' AS XML)

14

15 /*

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37 */

Now let us see if we can use RAW mode for generating the result that we need. The RAW mode does not provide a way to generate a hierarchy of XML nodes. However, a combination of RAW and AUTO can be used to generate the results that we need. The following code demonstrates it.

1 SELECT CAST(

2 (SELECT

3 Country.CountryName AS [name],

4 Country.Currency,

5 City.CityName AS [name],

6 Customer.CustomerNumber AS [id],

7 Customer.CustomerName AS [name],

8 Customer.Phone

9 FROM

10 Customers Customer

11 INNER JOIN Cities City ON (City.CityID = Customer.CityID)

12 INNER JOIN Countries Country ON (Country.CountryID = City.CountryID)

13 ORDER BY CountryName, CityName

14 FOR XML AUTO) AS XML)

15 FOR XML RAW('CustomersByRegion')

16

17 /*

18 OUTPUT:

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41 */

Conclusions

We have seen several examples of AUTO, RAW and PATH modes and I guess the usage of the above modes might be clearly demonstrated in the examples we have seen so far. There is one more mode, EXPLICIT, which I have not presented yet. TheEXPLICIT mode provides much more control over the structure of the generated XML. However, it is much more complex to use. In the next article, I will present a step-by-step tutorial to understand the usage of the EXPLICIT mode