Service Broker 101 Lesson 6: XML and messages
So far we have looked at assembling the Service Broker components, sending messages, and handling them through stored procedures. But in all of the examples, we have only ever treated the message body as a single value that we do something with, like add to a table. This lesson will go through some of the fundamentals of querying XML values to extract information. If you’re already familiar with XML in SQL Server there probably won’t be anything new in here for you.
What is XML?
XML stands for extensible markup language. It’s a way of encoding information to send between services is a semi-structured way. XML works using the concept of elements, each element can contain other elements. An element can also contain a value, or it can be an empty element, and an element can have one or more attributes attached to it. Elements are tagged using the <> notation so an element called SomeElement would appear in XML as <SomeElement>value</SomeElement>
. If the element is empty, that can either be represented as <SomeElement></SomeElement>
or <SomeElement />
. Attributes are included in the opening tab for the elements, like so: <SomeElement Attribute: "AttributeValue">element value</SomeElement>
. A more complex example is:
<people>
<person personID: "123">
<firstName>
Larry
</firstName>
<lastName>
Smith
</lastName>
</person>
<person personID: "124">
<firstName>
Joanne
</firstName>
<lastName>
White
</lastName>
<middleName>
Rachel
</middleName>
<pet>
<name>
fluffles
</name>
<animalType>
Dog
</animalType>
</pet>
<pet>
<name>
wuffles
</name>
</pet
</person>
</people>
So, what we have here is an element called people. This contains 2 person elements with personID attributes attached to them. These both contain firstName and lastName elements, but personID 124 also has a middleName and two pet elements. These pet elements each hold a name element, but only fluffles has an animalType element.
In this way, we can use XML to hold only the data we actually know. Unless we have specified an XML Schema (outside the scope of this class) there is no validation on what elements or attributes an element should contain.
Retrieving values
Often when we send service broker messages, the message body will contain XML. In these cases, the activation stored procedure often needs to extract the values from the constituent elements in order to act appropriately. SQL Server provides 5 methods you can call from an XML object to extract specific information, as part of the XQuery functionality. We will start with the value method:
DECLARE @XML XML = '<people><person id="5"><firstName>Larry</firstName><lastName>Smith</lastName></person></people>'
SELECT @XML.value('(/people/person/firstName)[1]', 'VARCHAR(100)');
SELECT @XML.value('(/people/person[@id="5"][1]/firstName)[1]', 'VARCHAR(100)');
SELECT @XML.value('/people[1]/person[1]/firstName[1]', 'VARCHAR(100)');
This returns the value “Larry”. There are a few things to note here:
- The value method is called as if the XML object is a class and the value is a method in an object oriented language.
- The path to the element you want to reach has to travel down form the outermost element, with each element starting with a “/”
- You must specify the ordinal number of the element you want to use. This is done by wrapping the path in brackets and specifying the ordinal at the end of the brackets e.g. the first query simply says return the value of the very first firstName, in the people>person>firstName hierarchy.
- You can optionally specify ordinals for any of the elements along the path, but can only do away with the round brackets and final ordinal if you specify other ordinals for every level along the way as in the third query. You can also filter at each element level based on attributes using [@attribute=”AttributeValue”] and can combine this with an element ordinal.
- You need to specify the data type you want to convert the value to as the second parameter, in this case I have specified
VARCHAR(100)
.
This is fine if you want to essentially send one row of information, so in a situation when each message would contain one person’s information. It is even a possible approach if you know you will always send a fixed number of rows, so if each person would always have three pets attached to them. But what if you want to allow your customers to have a variable number of pets (a far more plausible scenario). That requires you to use the nodes method
Nodes
The nodes method is used to shred XML. Shredding is a term that basically means turn the XML into more of a table structure. A call to the nodes method includes a specified path through the XML, and returns a table containing one row per element at the level specified, with each row containing the whole XML. From there you can use the value method to navigate up and down from the specified element, and return the value(s) you want. For example:
DECLARE @XML XML = '<people><person id="4"><firstName>John</firstName><lastName>Smith</lastName></person><person id="5"><firstName>Larry</firstName><lastName>Smith</lastName></person></people>'
SELECT Person.Person.value('(./firstName)[1]' , 'VARCHAR(100)')
FROM @XML.nodes('/people/person') AS Person(Person);
SELECT Person.FirstName.value('.' , 'VARCHAR(100)')
FROM @XML.nodes('/people/person/firstName') AS Person(FirstName);
Note that in the first SELECT
we still need to specify the ordinal position, this is because there may still be multiple firstName elements contained in the person element. In the second example, because we are already at the firstName element, we know that there is only one firstName at our level and do not need the ordinal.
If you ever want to travel up the XML path after using nodes, you can use the ‘.’ notation, ‘.’ means stay at this level but each additional ‘.’ takes you up a level.
This can be a bit tricky to get your head around, so if you are struggling it may be best to do some more reading or experimenting on your own. The basic idea is that the nodes function will return one row for each element at the level you specify. Each row will contain the entire XML, but with a pointer to that row’s element, and any further methods you call against the data will start their paths at that pointer. I’ve included one more example to try and make this make sense:
<people>
<person id="4">
<firstName>
John
</firstName>
<lastName>
Smith
</lastName>
</person>
<person id="5">
<firstName>
Larry
</firstName>
<lastName>
Smith
</lastName>
</person>
</people>
--Assuming the above XML is contained in @XML
SELECT --something
FROM @XML.nodes('/people/person/firstName') AS Person(FirstName);
-- This will return a 2 row table. Each row contains the whole XML.
-- One row's pointer starts at line 3
-- One row's pointer starts at line 11
-- To access this, the select needs to reference Person.FirstName and call an XQuery function like 'value'
SELECT --something
FROM @XML.nodes('/people/person') AS Person(Person);
-- This will return a 2 row table. Each row contains the whole XML.
-- One row's pointer starts at line 2
-- One row's pointer starts at line 10
-- To access this, the select needs to reference Person.Person and call an XQuery function like 'value'
That’s it for this lesson, next lesson will go through an example of how to put everything together in a working application.
Trackbacks & Pingbacks