SQL - Read an XML node from a table field - sql-server

I am using SQL Server 2008. I have a field called RequestParameters in one of my SQL table called Requests with XML data. An example would be:
<RequestParameters xmlns="http://schemas.datacontract.org/2004/07/My.Name.Space" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="1">
<Data z:Id="2" i:type="CheckoutRequest">
<UserGuid>7ec38c44-5aa6-49e6-9fc7-25e9028f2148</UserGuid>
<DefaultData i:nil="true" />
</Data>
</RequestParameters>
I ultimately want to retrieve the value of UserGuid. For that, I am doing this:
SELECT RequestParameters.value('(/RequestParameters/Data/UserGuid)[0]', 'uniqueidentifier') as UserGuid
FROM Requests
However, the results I am seeing are all NULL. What am I doing wrong?

You have to specify the default namespace and use [1] instead of [0].
WITH XMLNAMESPACES(default 'http://schemas.datacontract.org/2004/07/My.Name.Space')
SELECT RequestParameters.value('(/RequestParameters/Data/UserGuid)[1]', 'uniqueidentifier') as UserGuid
FROM Requests;
SQL Fiddle

declare #XML xml
set #XML = "<RequestParameters xmlns="http://schemas.datacontract.org/2004/07/My.Name.Space" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="1">
<Data z:Id="2" i:type="CheckoutRequest">
<UserGuid>7ec38c44-5aa6-49e6-9fc7-25e9028f2148</UserGuid>
<DefaultData i:nil="true" />
</Data>
</RequestParameters>"
select #XML.value('(/RequestParameters/Data /UserGuid)[1]', 'varchar')
'

Related

t-sql to create XML file using FOR XML Path, multilevel issues

I am creating an XML file to Upload to a 3rd party product. The file must begin with specific file and source information level and then it is followed with the specific data requirements/levels of EVENT and CREW members for those events.
I can create the initial level with the file/source information, and I have the data requirements exactly as they should be, but I cannot get them together in the same file between the "ROOT" level without the initial level repeating between each EVENT level or the an extra EVENT level as if they're nested. I've also managed to get a result with a ROW level that I did not define and the "tags" modified to < and &gt: instead of < >. I've done a good bit of research and tried using a union method, sub-selects, nesting methods as well many combinations of FOR XML PATH, AUTO, EXPLICIT, with and without elements. I've learned a lot, but I'm just not finding the right combination for the results I need.
The first example is the layout that is required. The second is one of the examples that is most common for my efforts, followed by the SQL that created it.
what it should be (FILEINFO level only once, only one EVENT level for each EVENT)
<ROOT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-24T08:00:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-24T14:14:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-24T14:14:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
<EVENT>
<DATE>2019-09-16T12:30:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T18:20:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T18:20:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
</ROOT>
what i'm getting:
<ROOT>
<EVENT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-16T08:00:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
</EVENT>
<EVENT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-16T08:00:00</DATE>
<NO>1</NO>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
<CREW>
<LAST_NAME>DOE</LAST_NAME>
<DEL_FLAG>false</DEL_FLAG>
<DATE_TIME_STAMP>2019-09-16T15:12:00</DATE_TIME_STAMP>
</CREW>
</EVENT>
</EVENT>
... ...
most recent/simplest attempt that creates the above:
SELECT
(SELECT SOURCE_ID FROM (select 'P' as SOURCE_ID) FILEINFO ) AS 'FILEINFO/SOURCE_ID'
,[DATE] AS 'EVENT/DATE'
,[NO] AS 'EVENT/NO'
,[DEL_FLAG] AS 'EVENT/DEL_FLAG'
,[DATE_TIME_STAMP] AS 'EVENT/DATE_TIME_STAMP'
,'DOE' as 'EVENT/CREW/LAST_NAME'
,[DEL_FLAG2] as 'EVENT/CREW/DEL_FLAG'
,[DATE_TIME_STAMP3] as 'EVENT/CREW/DATE_TIME_STAMP'
FROM [dbo].XMLForFILEExport x
FOR XML path('EVENT'), elements, ROOT('ROOT') ;
This is easy, just use a sub-select and deal with this like it was a *normal column:
This easy SELECT will return the single <FILEINFO>
SELECT 'P' AS [FILEINFO/SOURCE_ID]
FOR XML PATH(''),ROOT('ROOT');
You see, that I used an empty PATH(), but I set the ROOT().
This is the result
<ROOT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
</ROOT>
Now we can start to add your events. First I need a mockup table to simulate your issue
DECLARE #mockupEventTable TABLE(ID INT IDENTITY,[NO] INT, [DATE] DATETIME, EventText VARCHAR(100));
INSERT INTO #mockupEventTable VALUES(1,'20190916','Event 1')
,(2,'20190917','Event 2');
--The query
SELECT 'P' AS [FILEINFO/SOURCE_ID]
,(
SELECT e.[DATE]
,e.[NO]
,e.EventText
,'Doe' AS [CREW/LASTNAME]
FROM #mockupEventTable e
FOR XML PATH('EVENT'),TYPE
) AS [*]
FOR XML PATH(''),ROOT('ROOT');
The result
<ROOT>
<FILEINFO>
<SOURCE_ID>P</SOURCE_ID>
</FILEINFO>
<EVENT>
<DATE>2019-09-16T00:00:00</DATE>
<NO>1</NO>
<EventText>Event 1</EventText>
<CREW>
<LASTNAME>Doe</LASTNAME>
</CREW>
</EVENT>
<EVENT>
<DATE>2019-09-17T00:00:00</DATE>
<NO>2</NO>
<EventText>Event 2</EventText>
<CREW>
<LASTNAME>Doe</LASTNAME>
</CREW>
</EVENT>
</ROOT>
You can see, that the sub-select will create the inner XML just as you need it. We have to specify ,TYPE in order to get this as typed XML. Try the same without. You will get the XML escaped, as if it was simple text...
And I specify AS [*] (the same was AS [node()]) to indicate, that the XML "column" has no own name, but should be inserted as is. This is not mandatory (try it without), but it makes things more readable...
That's because you specified the PATH "EVENT" already. Also you can remove the EVENT in the field name, e.g. 'EVENT/CREW/DATE_TIME_STAMP' can just be 'CREW/DATE_TIME_STAMP'
TO achieve what required, you can generate the xml with EVENT elementsand then insert the FILEINFO.
DECLARE #x xml;
SET #x=(SELECT
[DATE] AS 'DATE'
,[NO] AS 'NO'
,[DEL_FLAG] AS 'DEL_FLAG'
,[DATE_TIME_STAMP] AS 'DATE_TIME_STAMP'
,'DOE' as 'CREW/LAST_NAME'
,[DEL_FLAG2] as 'CREW/DEL_FLAG'
,[DATE_TIME_STAMP3] as 'CREW/DATE_TIME_STAMP'
FROM [dbo].XMLForFILEExport x
FOR XML path('EVENT'), elements, ROOT('ROOT'))
SET #x.modify('
insert <FILEINFO><SOURCE_ID>P</SOURCE_ID></FILEINFO>
as first
into (/ROOT)[1]');

Querying XML Data stored in SQL Server

I have a column called Resume of type XML stored in a table in MS-SQL Server.
I want to retrieve all the candidates who are from the city Saginaw.
When I use the query
SELECT Resume.query('(: explicit namespace :)declare namespace ns="Namespace-Resume";
//ns:Address/ns:Addr.Location/ns:Location/ns:Loc.City')
FROM JobCandidate
I get all the values for the city which is fine but when I use the following query:
SELECT Resume.query('(: explicit namespace :)declare namespace ns="Namespace-Resume";
//ns:Location[#ns:Loc.City="Saginaw"]/ns:Name')
FROM JobCandidate
I get an error "There is not attribute named Loc.City".
The sample data is as below:
<ns:Resume xmlns:ns="Namespace-Resume">
<ns:Name>
<ns:Name.Prefix></ns:Name.Prefix>
<ns:Name.First>Shai</ns:Name.First>
<ns:Name.Middle></ns:Name.Middle>
<ns:Name.Last>Bassli</ns:Name.Last>
<ns:Name.Suffix></ns:Name.Suffix>
</ns:Name>
<ns:Address>
<ns:Addr.Type>Home</ns:Addr.Type>
<ns:Addr.Street>567 3rd Ave</ns:Addr.Street>
<ns:Addr.Location>
<ns:Location>
<ns:Loc.CountryRegion>US </ns:Loc.CountryRegion>
<ns:Loc.State>MI </ns:Loc.State>
<ns:Loc.City>Saginaw</ns:Loc.City>
</ns:Location>
</ns:Addr.Location>
</ns:Address>
</ns:Resume>
Your question is not all clear... I'll provide several approaches, one of them will hopefully point you the way (You can copy the whole lot into a query window and execute it stand-alone):
DECLARE #xml XML=
N'<ns:Resume xmlns:ns="Namespace-Resume">
<ns:Name>
<ns:Name.Prefix />
<ns:Name.First>Shai</ns:Name.First>
<ns:Name.Middle />
<ns:Name.Last>Bassli</ns:Name.Last>
<ns:Name.Suffix />
</ns:Name>
<ns:Address>
<ns:Addr.Type>Home</ns:Addr.Type>
<ns:Addr.Street>567 3rd Ave</ns:Addr.Street>
<ns:Addr.Location>
<ns:Location>
<ns:Loc.CountryRegion>US </ns:Loc.CountryRegion>
<ns:Loc.State>MI </ns:Loc.State>
<ns:Loc.City>Saginaw</ns:Loc.City>
</ns:Location>
</ns:Addr.Location>
</ns:Address>
</ns:Resume>';
--Read one element's text with namespaces wildcards
SELECT #xml.value(N'(/*:Resume/*:Name/*:Name.First/text())[1]',N'nvarchar(max)');
--Use a default namespace
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT #xml.value(N'(/Resume/Address/Addr.Location/Location/Loc.City/text())[1]',N'nvarchar(max)');
--Your sample XML includes one person only, but I assume there are more
--Use a predicate to get the name for a given location
DECLARE #location NVARCHAR(100)=N'Saginaw';--Change this for tests
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT #xml.value(N'(/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]/Name/Name.First/text())[1]',N'nvarchar(max)');
--Read several values of this node
--Use a predicate to get the name for a given location
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT r.value(N'(Name/Name.First/text())[1]',N'nvarchar(max)')
,r.value(N'(Name/Name.Last/text())[1]',N'nvarchar(max)')
FROM #xml.nodes(N'/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]') AS A(r);
UPDATE: The SELECT you provide in comment
The call to nodes() is missing? Try it like this:
DECLARE #location NVARCHAR(100)=N'Saginaw';
WITH XMLNAMESPACES(DEFAULT N'Namespace-Resume')
SELECT r.value(N'(Name/Name.First/text())[1]',N'nvarchar(max)')
,r.value(N'(Name/Name.Last/text())[1]',N'nvarchar(max)')
FROM JobCandidate
CROSS APPLY Resume.nodes(N'/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]') AS A(r);
UPDATE 2: Namespace according to your comment
DECLARE #xml XML=
N'<ns:Resume xmlns:ns="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume">
<ns:Name>
<ns:Name.Prefix />
<ns:Name.First>Shai</ns:Name.First>
<ns:Name.Middle />
<ns:Name.Last>Bassli</ns:Name.Last>
<ns:Name.Suffix />
</ns:Name>
<ns:Address>
<ns:Addr.Type>Home</ns:Addr.Type>
<ns:Addr.Street>567 3rd Ave</ns:Addr.Street>
<ns:Addr.Location>
<ns:Location>
<ns:Loc.CountryRegion>US </ns:Loc.CountryRegion>
<ns:Loc.State>MI </ns:Loc.State>
<ns:Loc.City>Saginaw</ns:Loc.City>
</ns:Location>
</ns:Addr.Location>
</ns:Address>
</ns:Resume>';
DECLARE #location NVARCHAR(100)=N'Saginaw';--Change this for tests
WITH XMLNAMESPACES(DEFAULT N'http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume')
SELECT r.value(N'(Name/Name.First/text())[1]',N'nvarchar(max)')
,r.value(N'(Name/Name.Last/text())[1]',N'nvarchar(max)')
FROM #xml.nodes(N'/Resume[(Address/Addr.Location/Location/Loc.City/text())[1]=sql:variable("#location")]') AS A(r);

Combine and modify XML in TSQL

Using SQL Server 2005, is it possible to combine XML and add an attribute at same time?
Unfortunately, due to project restrictions, I need a SQL Server 2005 solution.
Consider the following, where I need to combine XML from multiple rows within a new <root> element...
; WITH [TestTable] AS (
SELECT 7 AS [PkId], CAST('<data><id>11</id><id>12</id></data>' AS XML) AS [Data]
UNION ALL
SELECT 12, CAST('<data><id>22</id></data>' AS XML)
UNION ALL
SELECT 43, CAST('<data><id>33</id></data>' AS XML)
)
SELECT (
SELECT XMLDATA as [*]
FROM (
SELECT [Data] AS [*]
FROM [TestTable]
FOR XML PATH(''), TYPE
) AS DATA(XMLDATA)
FOR XML PATH('root')
)
This produces the desired output of...
<root>
<data><id>11</id><id>12</id></data>
<data><id>22</id></data>
<data><id>33</id></data>
</root>
But what I need to do, if possible, is add an attribute to the existing data element in each of the rows with the PkId value. The desired output would then look like this...
<root>
<data pkid="7"><id>11</id><id>12</id></data>
<data pkid="12"><id>22</id></data>
<data pkid="43"><id>33</id></data>
</root>
My gut feeling is that this is going to be impossible without the use of a cursor, but if anybody knows a way of doing it I'd love to hear it.
At the request of #MattA, here is an example of some random data in the table...
[PkId] [UserId] [SubmittedDate] [Data]
1 1 2015-03-24 12:34:56 '<data><id>1</id><id>2</id></data>'
2 1 2015-03-23 09:15:52 '<data><id>3</id></data>'
3 2 2015-03-22 16:01:23 '<data><id>4</id><id>5</id></data>'
4 1 2015-03-21 13:45:34 '<data><id>6</id></data>'
Please note, that to make the question easier, I stated that I needed the PkId column as the attribute to the data. This is not actually the case - instead I need the [SubmittedDate] column to be used. I apologise if this caused confusion.
Using UserId=1 as a filter, the XML I would like from the above would be...
<root>
<data submitteddate="2015-03-24T12:34:56"><id>1</id><id>2</id></data>
<data submitteddate="2015-03-23T09:15:52"><id>3</id></data>
<data submitteddate="2015-03-21T13:45:34"><id>6</id></data>
</root>
The date would be formatted using the 126 date format available from CONVERT
Here's the quick answer for you. XML does support "modify", but shredding on a small data set like this works quite well too.
Code
--The existing XML
DECLARE #XML XML = '<root>
<data><id>11</id></data>
<data><id>22</id></data>
<data><id>33</id></data>
</root>'
--XML Shredded Back to a table
;WITH
ShreddedXML AS (
SELECT
ID = FieldAlias.value('(id)[1]','int')
FROM
#XML.nodes('/root/data') AS TableAlias(FieldAlias)
), ArbitraryPKGenerator AS (
SELECT CURRENT_TIMESTAMP AS PKid,
ID
FROM ShreddedXML
)
SELECT A.PKId AS "#PKid",
A.ID AS "id"
FROM ArbitraryPKGenerator AS A
FOR XML PATH('data'), ROOT('root')
And the XML
<root>
<data PKid="2015-03-24T09:44:55.770">
<id>11</id>
</data>
<data PKid="2015-03-24T09:44:55.770">
<id>22</id>
</data>
<data PKid="2015-03-24T09:44:55.770">
<id>33</id>
</data>
</root>

Return XML cell data in XML query with SQL Server 2012

I have a table in SQL Server 2012 that contains some customer data. One of the columns in the table contains license data that is stored as XML. The type of the cell is nvarchar(MAX).
Is it possible to use FOR XML (or some other method) so that when the data is returned the XML from the license data is included as XML rather than a formatted string?
If I simply use FOR XML RAW, then the result is:
<Customers id="1" CustomerName="FirstCustomer"
LicenseData="<license customerid="1">...More data here...</license>" />
What I would liket to get is:
<Customers id="1" CustomerName="FirstCustomer">
<license customerid="1">
...More data here...
</license>
</Customers>
Is there any way to make that happen?
If the XML is a valid fragment then you can simply CAST it to XML.
SELECT CAST(MyColumn as XML) as MyXml
declare #temp table (id int, customername nvarchar(128), data nvarchar(max))
insert into #temp
select 1, 'FirstCustomer', '<license customerid="1"><element id="2">data1</element><element id="3"/></license>'
select id, customername, cast(data as xml)
from #temp
for xml raw
And you'll get results like this:
<row id="1" customername="FirstCustomer">
<license customerid="1">
<element id="2">data1</element>
<element id="3" />
</license>
</row>

Microsoft SQL Server xml data

This site has a technique to pass xml data around in Microsoft SQL Server:
DECLARE #productIds xml
SET #productIds ='<Products><id>3</id><id>6</id><id>15</id></Products>'
SELECT
ParamValues.ID.value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/id') as ParamValues(ID)
But what is the syntax if I add another field?
The following does NOT work:
DECLARE #productIds xml
SET #productIds ='<Products><id>3</id><descr>Three</descr><id>6</id><descr>six</descr><id>15</id><descr>Fifteen</descr></Products>'
SELECT
ParamValues.ID.value('.','VARCHAR(20)')
,ParamValues.descr.value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/id') as ParamValues(ID)
Note: Maybe I've constructed my xml wrong.
You need to use something like:
SELECT
ParamValues.ID.value('(id)[1]','VARCHAR(20)'),
ParamValues.ID.value('(descr)[1]','VARCHAR(20)')
FROM
#productIds.nodes('/Products') as ParamValues(ID)
That FROM statement there defines something like a "virtual table" called ParamValues.ID - you need to select the <Products> node into that virtual table and then access the properties inside it.
Furthermore, your XML structure is very badly chosen:
<Products>
<id>3</id>
<descr>Three</descr>
<id>6</id>
<descr>six</descr>
<id>15</id>
<descr>Fifteen</descr>
</Products>
You won't be able to select the individual pairs of id/descr - you should use something more like:
<Products>
<Product>
<id>3</id>
<descr>Three</descr>
</Product>
<Product>
<id>6</id>
<descr>six</descr>
</Product>
<Product>
<id>15</id>
<descr>Fifteen</descr>
</Product>
</Products>
Then you could retrieve all items using this SQL XML query:
SELECT
ParamValues.ID.value('(id)[1]','VARCHAR(20)') AS 'ID',
ParamValues.ID.value('(descr)[1]','VARCHAR(20)') AS 'Description'
FROM
#productIds.nodes('/Products/Product') as ParamValues(ID)
ID Descrition
3 Three
6 six
15 Fifteen
You must wrap each set of id and descr into one parent node. Say Row. Now you can access each pair like this.
DECLARE #productIds xml
SET #productIds ='<Products><Row><id>3</id><descr>Three</descr></Row><Row><id>6</id><descr>six</descr></Row><Row><id>15</id><descr>Fifteen</descr></Row></Products>'
SELECT
ParamValues.Row.query('id').value('.','VARCHAR(20)'),
ParamValues.Row.query('descr').value('.','VARCHAR(20)')
FROM #productIds.nodes('/Products/Row') as ParamValues(Row)

Resources