Extract data from xml column in SQL Server - sql-server

I have an XML column in SQL Server populated with:
<concession>
<schema>
<data schemaItem="title">Re-label to all boards</data>
<data schemaItem="problem">These boards have been tested</data>
<data schemaItem="solution">Ask to print new label and add the word "B" on the old serial numbers. so all serial numbers will be modified to new on B .......</data>
<data schemaItem="justification">Will help UK test resource</data>
<data schemaItem="liability">Us</data>
<data schemaItem="parts">
<part>075</part>
<part>076</part>
</data>
<data schemaItem="products">
<product>Pdq </product>
</data>
<data schemaItem="faultCode">ILB</data>
<data schemaItem="processCode">MAT</data>
<data schemaItem="quantity">273</data>
<data schemaItem="requestedExpiry">14/12/2011</data>
</schema>
</concession>
How do I extract the quantity value, ie: 273?
I've tried but no joy:
SELECT
[guid],XMLData,
(select xmlData.value('(/concession/schema/data)[1]', 'varchar(100)' )),
(select xmlData.value('(/concession/schema/data[schemaItem="quantity"])[0]', 'varchar(100)' ))
FROM
tc_Concession
I get the title ok but not the quantity.

Try using the Outer Apply With the nodes() method,
SELECT
m.c.value('#schemaItem', 'varchar(max)') as SchemaItem,
m.c.value('(text())[1]', 'nvarchar(max)') as Value
FROM Yourtablename
OUTER APPLY xml_data.nodes('concession/schema/data') as m(c)
This will give you the output as,
SchemaItem Value
title Re-label to all boards
problem These boards have been tested
solution Ask to print new label and add the word "B" on the old serial numbers. so all serial numbers will be modified to new on B .......
justification Will help UK test resource
liability Us
parts NULL
products NULL
faultCode ILB
processCode MAT
quantity 273
requestedExpiry 14/12/2011

You are very close. The attribute name needs to be preceeded by #, and the index should be 1-based. You don't need the sub-select - the .value() method can be used on it's own.
SELECT
[guid],
XMLData,
xmlData.value('(/concession/schema/data)[1]', 'varchar(100)' ),
xmlData.value('(/concession/schema/data[#schemaItem="quantity"])[1]', 'varchar(100)' )
FROM
tc_Concession
If the XML isn't guaranteed to be ordered then you should use the 2nd approach for the title too.

Related

How to select/query xml data from element and selecting sibling value in SQL

I have a SQL Server table called t for example which has 2 columns and lots of rows
ID (PK, int, not null)
Data (XML(.), not null)
In the Data field i have this XML (this I can't change the format of)
<ArrayOfDataAttribute xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataAttribute>
<Name>field1</Name>
<Value>default-value-example</Value>
</DataAttribute>
<DataAttribute>
<Name>field2</Name>
</DataAttribute>
<DataAttribute>
<Name>field5</Name>
<Value>False</Value>
</DataAttribute>
<DataAttribute>
<Name>field4</Name>
<Value>example value</Value>
</DataAttribute>
<DataAttribute>
<Name>field5</Name>
<Value>another value</Value>
</DataAttribute>
</ArrayOfDataAttribute>
I need to return from t the ID and the contents of for the sibling xml item name where it equals field4 or null/empty string if its not there for that row.
So i would end up with this if there were only one row
ID | field4
1 | example value
From reading online it looks like using 'nodes' would be the way to do it but I'm not getting anywhere, the examples I've found online seem to be where people are looking for a specific value. This is the closest I've got:
SELECT T2.Loc.value('.', 'varchar(max)')
FROM t
CROSS APPLY t.Data.nodes('/*:ArrayOfDataAttribute/*:DataAttribute/Name') as T2(Loc)
any help is gratefully appreciated
Many thanks
Richard
What you are looking for is a XQuery predicate and how to stuff a value into your XQuery. The first needs brackets ([]), the second can be achieved with the function sql:variable():
Try it like this:
DECLARE #YourTable TABLE(ID INT IDENTITY,[Data] XML);
INSERT INTO #YourTable VALUES
(N'<ArrayOfDataAttribute xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DataAttribute>
<Name>field1</Name>
<Value>default-value-example</Value>
</DataAttribute>
<DataAttribute>
<Name>field2</Name>
</DataAttribute>
<DataAttribute>
<Name>field5</Name>
<Value>False</Value>
</DataAttribute>
<DataAttribute>
<Name>field4</Name>
<Value>example value</Value>
</DataAttribute>
<DataAttribute>
<Name>field5</Name>
<Value>another value</Value>
</DataAttribute>
</ArrayOfDataAttribute>');
--A parameter for your search string
DECLARE #searchFor NVARCHAR(MAX)=N'field4'
--the query
SELECT t.ID
,t.[Data].value(N'(/ArrayOfDataAttribute
/DataAttribute[(Name/text())[1]=sql:variable("#searchFor")]
/Value
/text())[1]',N'nvarchar(max)') AS field4
FROM #YourTable t;
The XPath can be read as:
Dive into the array of attributes and look for the <DataAttribute> with a <Name> of a given value. There we need the text() within Value.
Hint: Although there are namespaces, the given sample makes no use of any of them. We can omit the declaration in this case...

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]');

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>

Query XML value in sql

I need to get some information from XML in SQL Server 2008, but I cannot even get basic attribute from it. All samples that I tried failed. Table name is Item, xml column name is Data.
Simplified xml looks like this:
<AnchoredXml xmlns="urn:schema:Microsoft.Rtc.Management.ScopeFramework.2008" SchemaWriteVersion="2">
<Key ScopeClass="Global">
<SchemaId Namespace="urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008" ElementName="Topology" />
<AuthorityId Class="Host" InstanceId="00000000-0000-0000-0000-000000000000" />
</Key>
<Dictionary Count="1">
<Item>
<Key />
<Value Signature="a3502dd0-8c16-4023-9eea-30ea1c7a3a2b">
<Topology xmlns="urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008">
<Services>
<Service RoleVersion="1" ServiceVersion="6" Type="Microsoft.Rtc.Management.Deploy.Internal.ServiceRoles.FileStoreService">
<ServiceId SiteId="1" RoleName="FileStore" Instance="1" />
<DependsOn />
<InstalledOn>
<ClusterId SiteId="1" Number="1" />
</InstalledOn>
<Ports xmlns="urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008" />
<FileStoreService xmlns="urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008" ShareName="lyncShare" />
</Service>
</Services>
</Topology>
</Value>
</Item>
</Dictionary>
</AnchoredXml>
I need to read information in AnchoredXml/Key/SchemaId/#NameSpace to select the right xml (there are more rows). Sample xml above is the right one. And after that I need to find the right service with
Type="Microsoft.Rtc.Management.Deploy.Internal.ServiceRoles.FileStoreService"
where is FileStoreService/#ShareName that I need.
I've tried to print the Namespace attributte for the start, but no sample code is working.
A few tries:
SELECT c.p.value('(#Namespace)[1]', 'varchar(50)') as 'Nmspace'
FROM Item
CROSS APPLY Data.nodes('/AnchoredXml/Key/SchemaId') c(p)
returns empty result set
SELECT Data.value('(/AnchoredXml/Key/SchemaId/#Namespace)[1]', 'varchar(50)')
FROM Item
returns NULL for all rows
SELECT
It.Data.exist('/AnchoredXml/Key/SchemaId[#Namespace="Microsoft.Rtc.Management.Deploy.Topology.2008"]')
FROM [xds].[dbo].[Item] AS It
returns 0's for all rows also without quotes ("")
A working sample code to get at least attribute test would be maybe sufficient and I would figure out the rest.
Could you please help me find errors in my queries or maybe identify some other problem?
Thanks
You're ignoring all the XML namespaces in your XML document! You need to pay attention to those and respect them!
There are XML namespaces on:
the root node <AnchoredXml>
(XML namespace: urn:schema:Microsoft.Rtc.Management.ScopeFramework.2008)
the subnode <Topology>
(XML ns: urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008)
the subnode <FileStoreService>
(XML ns: urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008)
Try this:
-- respect the XML namespaces!!
;WITH XMLNAMESPACES(DEFAULT 'urn:schema:Microsoft.Rtc.Management.ScopeFramework.2008',
'urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008' AS t,
'urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008' AS fss)
SELECT
ShareName = Data.value('(/AnchoredXml/Dictionary/Item/Value/t:Topology/t:Services/t:Service/fss:FileStoreService/#ShareName)[1]', 'varchar(50)')
FROM
dbo.Item
In my case, this returns:
ShareName
-----------
lyncShare

SQL - Read an XML node from a table field

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')
'

Resources