Select XML multiple only a few nodes with the same name - sql-server

I'm trying to construct a soap message, and I was able to construct the entire message using a single select. Except the problem is, on only a few occasions the same node name is repeated twice.
So for example the required output result should be like so, with two separate id root nodes:
<SoapDocument>
<recordTarget>
<patientRole>
<id root="1.2.3.4" extension="1234567" />
<id root="1.2.3.5.6" extension="0123456789" />
</patientRole>
</recordTarget>
</SoapDocument>
I tried to use my sparse knowledge of xpath to construct the node names like so:
select
'1.2.3.4' AS 'recordTarget/patientRole/id[1]/#root',
'1234567' AS 'recordTarget/patientRole/id[1]/#extension',
'1.2.3.5.6' AS 'recordTarget/patientRole/id[2]/#root',
'0123456789' AS 'recordTarget/patientRole/id[2]/#extension'
FOR XML PATH('SoapDocument'),TYPE
Apparently xpath naming can't be applied to column names id[1] and id[2] like that? Am I missing something here or should the notation be different? What would be the easiest way to constuct the desired result?

From your question I assume, this is not tabular data, but fixed values and you are creating a medical document, assumably a CDA.
Try this:
SELECT
(
SELECT
'1.2.3.4' AS 'id/#root',
'1234567' AS 'id/#extension',
'',
'1.2.3.5.6' AS 'id/#root',
'0123456789' AS 'id/#extension'
FOR XML PATH('patientRole'),TYPE
) AS [SoapDocument/recordTarget]
FOR XML PATH('')
The result:
<SoapDocument>
<recordTarget>
<patientRole>
<id root="1.2.3.4" extension="1234567" />
<id root="1.2.3.5.6" extension="0123456789" />
</patientRole>
</recordTarget>
</SoapDocument>
Some explanation: The empty element in the middle allows you to place two elements with the same name in one query. There are various approaches how you get this into your surrounding tags. This is just one possibility.
UPDATE
I'd like to point to BdR's own answer! Great finding and worth an up-vote!

A little more elaboration on the answer from Shnugo, as it got me trying out some things using an "empty column".
If you do not give the emtpy column a name, it will reset to the XML root node. So the following columns will start from the XML root of the selection you are in at that point. However, if you explicitly name the empty separator column, then the following columns will continue in the hierarchy as set by that column name.
So the selection below will also result in the desired result. It's subtly different, but in my case it allows me to avoid using subselections.
select
'1.2.3.4' AS 'recordTarget/patientRole/id/#root',
'1234567' AS 'recordTarget/patientRole/id/#extension',
'' AS 'recordTarget/patientRole',
'1.2.3.5.6' AS 'recordTarget/patientRole/id/#root',
'0123456789' AS 'recordTarget/patientRole/id/#extension'
FOR XML PATH('SoapDocument'),TYPE

This should do the job:
WITH CTE AS (
SELECT *
FROM (VALUES('1.2.3.4','1234567'),
('1.2.3.5.6','0123456789')) V ([root], [extension]))
SELECT (SELECT (SELECT (SELECT [root] AS [#root],
[extension] AS [#extension]
FROM CTE
FOR XML PATH('id'), TYPE)
FOR XML PATH('patientRole'), TYPE)
FOR XML PATH ('recordTarget'), TYPE)
FOR XML PATH ('SoapDocument');

Related

SQL Query values out of XML using

I can't figure out what I'm doing wrong. I have XML stored in a table column as text. I'm taking the ID, and the XML text and querying it into a temp table that stores the XML as XML type.
Each Order in the XML has multiple licenses in it that I need to pull out and create a new table with OrderID and License ID. But I can't tell what I'm doing wrong.
So, I'm trying to start basic but I can't seem to even just get the Account Info from the first Node.
The XML looks like this:
<ns1:OrderFromCRM xmlns:ns1="http://company.com/licensing/neworder/v2">
<ns1:AccountInfo Name="Company Name" AccountId="A012345" />
<ns1:OrderInfo CRMOrderId="S147360" Date="2/23/2017 12:00:00 AM" ffEmail="emailaddress.#gmail.com" >
<ns1:Licensing>
<ns1:Foundations>
<ns1:Foundation LicenseId="L012345678" Action="Create" Environment="Production" Type="Enterprise">
<Metadata>
<AllowedInstances>999</AllowedInstances>
</Metadata>
</ns1:Foundation>
<ns1:Foundation LicenseId="L012345698" Action="Create" Environment="Production" Type="Enterprise">
<Metadata>
<AllowedInstances>999</AllowedInstances>
</Metadata>
</ns1:Foundation>
</ns1:Foundations>
<ns1:Licenses Type="Create">
<ns1:License LicenseId="L0123451234" ProductFamily="Fam1" Product="EStudio" LicenseType="Perpetual" StartDate="2017-02-23" ExpiryDate="2017-12-18" MaintenanceExpiryDate="2017-12-18">
<ns1:Capabilities>
<ns1:Capability Name="T1" />
<ns1:Capability Name="Q1" />
<ns1:Capability Name="B1" />
</ns1:Capabilities>
</ns1:License>
<ns1:License LicenseId="L333356675" ProductFamily="Fam1" Product="EStudio" LicenseType="Perpetual" StartDate="2017-02-23" ExpiryDate="2017-12-18" MaintenanceExpiryDate="2017-12-18">
<ns1:Capabilities>
<ns1:Capability Name="T1" />
<ns1:Capability Name="Q1" />
<ns1:Capability Name="B1" />
</ns1:Capabilities>
</ns1:License>
The SQL I wrote is:
CREATE TABLE #demoData
(
ActivationCode NVARCHAR(100) NOT NULL,
OrderXMLText XML
)
SELECT OrderId, OrderXMLText.value('(/OrderFromCRM/AccountInfo)[1]', 'varchar(30)')
FROM #DEMODATA
I mentioned I need the OrderID and the LicenseId but even with this, I can't get anything. Am I on right track? First, what am I missing? Second, once this is formatted correctly, how do I get the nested LicenseIds in the XML?
Thanks so much for any help. I've been trying to make this work for a couple days
You’re missing the namespace so the query isn’t matching the xml, therefore it doesn’t find the elements you’re querying.
Add
;WITH XMLNAMESPACES
https://learn.microsoft.com/en-us/sql/t-sql/xml/with-xmlnamespaces
Assuming you have a complete, valid XML document in your table (the one you're showing is lacking several closing tags), then you could try something like this:
;WITH XMLNAMESPACES('http://company.com/licensing/neworder/v2' AS ns1)
SELECT
ActivationCode,
CRMOrderId = XC.value('#CRMOrderId', 'varchar(100)'),
FoundationsLicenseId = xcf.value('#LicenseId', 'varchar(50)'),
LicensesLicenseId = xcl.value('#LicenseId', 'varchar(50)')
FROM
#demoData
CROSS APPLY
OrderXMLText.nodes('/ns1:OrderFromCRM/ns1:OrderInfo') AS XT(XC)
CROSS APPLY
XC.nodes('ns1:Licensing/ns1:Foundations/ns1:Foundation') AS XTF(XCF)
CROSS APPLY
XC.nodes('ns1:Licensing/ns1:Licenses/ns1:License') AS XTL(XCL)
First of all, you need to include and respect the XML namespace in your XQuery - that's what I do with the WITH XMLNAMESPACES() directive.
Next, you need to use .nodes() to get a list of XML fragments for each <OrderInfo> node, which is located below the root node (<OrderFromCRM>). This is the first CROSS APPLY. This returns a list of XML fragments, one for each <OrderInfo> node.
Then you need to reach into these XML fragments, and again use CROSS APPLY with the .nodes() function to get a list of the <Foundation> elements (contained in the <Licensing>/<Foundations> subtree to get the license Id's from those nodes. In addition, you need a second CROSS APPLY to get all the <License> subnodes under <Licensing>/<Licenses> to get those LicenseId attributes.
That should return an output something like:
Hope this helps you some!

Extracting XML in a column from a SQL Server database

I have read dozens of posts and have tried numerous SQL queries to try and get this figured out. Sadly, I'm not a SQL expert (not even a novice) nor am I an XML expert. I understand basic queries from SQL, and understand XML tags, mostly.
I'm trying to query a database table, and have the data show a list of values from a column that contains XML. I'll give you an example of the data. I won't burden you with everything I have tried.
Here is an example of field inside of the column I need. So this is just one row, I would need to query the whole table to get all of the data I need.
When I select * from [table name] it returns hundreds of rows and when I double click in the column name of 'Document' on one row, I get the information I need.
It looks like this:
<code_set xmlns="">
<name>ExampleCodeTable</name>
<last_updated>2010-08-30T17:49:58.7919453Z</last_updated>
<code id="1" last_updated="2010-01-20T17:46:35.1658253-07:00"
start_date="1998-12-31T17:00:00-07:00"
end_date="9999-12-31T16:59:59.9999999-07:00">
<entry locale="en-US" name="T" description="Test1" />
</code>
<code id="2" last_updated="2010-01-20T17:46:35.1658253-07:00"
start_date="1998-12-31T17:00:00-07:00"
end_date="9999-12-31T16:59:59.9999999-07:00">
<entry locale="en-US" name="Z" description="Test2" />
</code>
<displayExpression>[Code] + ' - ' + [Description]</displayExpression>
<sortColumn>[Description]</sortColumn>
</code_set>
Ideally I would write it so it runs the query on the table and produces results like this:
Code Description
--------------------
(Data) (Data)
Any ideas? Is it even possible? The dozens of things I have tried that are always posted in stack, either return Nulls or fail.
Thanks for your help
Try something like this:
SELECT
CodeSetId = xc.value('#id', 'int'),
Description = xc.value('(entry/#description)[1]', 'varchar(50)')
FROM
dbo.YourTableNameHere
CROSS APPLY
YourXmlColumn.nodes('/code_set/code') AS XT(XC)
This basically uses the built-in XQuery to get an "in-memory" table (XT) with a single column (XC), each containing an XML fragment that represents each <code> node inside your <code_set> root node.
Once you have each of these XML fragments, you can use the .value() XQuery operator to "reach in" and grab some pieces of information from it, e.g. it's #id (attribute by the name of id), or the #description attribute on the contained <entry> subelement.
The following query will read the xml field in every row, then shred certain values into a tabular result set.
SELECT
-- get attribute [attribute name] from the parent node
parent.value('./#attribute name','varchar(max)') as ParentAttributeValue,
-- get the text value of the first child node
child.value('./text()', 'varchar(max)') as ChildNodeValueFromFirstChild,
-- get attribute attribute [attribute name] from the first child node
child.value('./#attribute name', 'varchar(max)') as ChildAttributeValueFromFirstChild
FROM
[table name]
CROSS APPLY
-- create a handle named parent that references that <parent node> in each row
[xml field name].nodes('//xpath to parent name') AS ParentName(parent)
CROSS APPLY
-- create a handle named child that references first <child node> in each row
parent.nodes('(xpath from parent/to child)[0]') AS FirstChildNode(child)
GO
Please provide the exact values you want to shred from the XML for a more precise answer.

SQL Server : read XML data

I have this query taken from the site www.SQLauthority.com:
DECLARE #MyXML XML
SET #MyXML = '<SampleXML>
<Colors>
<Color1>White</Color1>
<Color2>Blue</Color2>
<Color3>Black</Color3>
<Color4 Special="Light">Green</Color4>
<Color5>Red</Color5>
</Colors>
<Fruits>
<Fruits1>Apple</Fruits1>
<Fruits2>Pineapple</Fruits2>
<Fruits3>Grapes</Fruits3>
<Fruits4>Melon</Fruits4>
</Fruits>
</SampleXML>'
SELECT
a.b.value('Colors[1]/Color1[1]','varchar(10)') AS Color1,
a.b.value('Colors[1]/Color2[1]','varchar(10)') AS Color2,
a.b.value('Colors[1]/Color3[1]','varchar(10)') AS Color3,
a.b.value('Colors[1]/Color4[1]/#Special','varchar(10)')+' '+
+a.b.value('Colors[1]/Color4[1]','varchar(10)') AS Color4,
a.b.value('Colors[1]/Color5[1]','varchar(10)') AS Color5,
a.b.value('Fruits[1]/Fruits1[1]','varchar(10)') AS Fruits1,
a.b.value('Fruits[1]/Fruits2[1]','varchar(10)') AS Fruits2,
a.b.value('Fruits[1]/Fruits3[1]','varchar(10)') AS Fruits3,
a.b.value('Fruits[1]/Fruits4[1]','varchar(10)') AS Fruits4
FROM #MyXML.nodes('SampleXML') a(b)
I am not getting a better picture of how the nodes fetching from the xml data.
I have few queries regarding this.
what is a(b) in this?
how the structure will change if i have another node inside colors and all the existing child nodes appended to that?
ie:
<Colorss>
<Colors>
<Color1>White</Color1>
<Color2>Blue</Color2>
<Color3>Black</Color3>
<Color4 Special="Light">Green</Color4>
<Color5>Red</Color5>
</Colors>
<Colorss>
<Fruits>
<Fruits1>Apple</Fruits1>
<Fruits2>Pineapple</Fruits2>
<Fruits3>Grapes</Fruits3>
<Fruits4>Melon</Fruits4>
</Fruits>
what does it mean by a.b.value? When I mouse over it shows a is derived table. Can I check value of the table a?
Any help in this will be appreciated.
what is a(b) in this?
The call to .nodes('SampleXML') is a XQuery function which returns a pseudo table which contains one column of an XML fragment for each of the elements that this XPath expression matches - and the a(b) is the table alias (a) for that column, and b is the name of the column in that pseudo table containing the XML fragments.
what does it mean by a.b.value?
This is based on the above - a is the table alias for that temporary, inline pseudo table, b is the column name for the column in that table, and .value() is another XQuery function that will extract a single value from XML, based on the XPath expression (first argument) and it will return it as the datatype specified in the second argument.
You should check out those introductions to XQuery support in SQL Server to understand better:
Introduction to XQuery in SQL Server 2005
XQuery basics
and there are numerous other introductions and tutorials on XQuery - just search with your favorite search engine and you'll get tons of hits!
here's my stab # it:
a-refers to root;b-refers to root and child node
DECLARE #MyXML XML
SET #MyXML = '<SampleXML>
<Colors>
<Color1>White</Color1>
<Color2>Blue</Color2>
<Color3>Black</Color3>
<Color4 Special="Light">Green</Color4>
<Color5>Red
<Color6>Black44</Color6>
<Color7>Black445</Color7>
</Color5>
</Colors>
<Fruits>
<Fruits1>Apple</Fruits1>
<Fruits2>Pineapple</Fruits2>
<Fruits3>Grapes</Fruits3>
<Fruits4>Melon</Fruits4>
</Fruits>
</SampleXML>'
to get an inner child
SELECT
a.c.value('Colors1/Color11','varchar(10)') AS Color1,
a.c.value('Colors1/Color21','varchar(10)') AS Color2,
a.c.value('Colors1/Color31','varchar(10)') AS Color3,
a.c.value('Colors1/Color41/#Special','varchar(10)') AS Color4,
a.c.value('Colors1/Color51','varchar(10)') AS Color5,
a.c.value('Colors1/Color51/Color71','varchar(50)') AS Color6a,
a.c.value('Colors1/Color51/Color61','varchar(50)') AS Color6b, a.c.value('Fruits1/Fruits11','varchar(10)') AS Fruits1,
a.c.value('Fruits1/Fruits21','varchar(10)') AS Fruits2,
a.c.value('Fruits1/Fruits31','varchar(10)') AS Fruits3,
a.c.value('Fruits1/Fruits41','varchar(10)') AS Fruits4
FROM #MyXML.nodes('SampleXML') a(c)
A nodes() method invocation with the query expression /root/Color(n) would return a rowset with three rows, each containing a logical copy of the original XML document, and with the context item set to one of the nodes
see here

SQL Server 'To XML' Tag Name

I have the following code in the select block of my query which picks out rows from a table and outputs them in XML:
select ...
...
,substring(
(
Select RC_1.Master_Code AS [TopographyTDR]
From apex.Histo_Result_Coding as RC_1
Where RC_1.Histo_Report = Histo_Result_Coding.Histo_Report
ORDER BY RC_1.Histo_report
For XML auto
), 1, 1000) [TDRCodes]
...
and this gives an output similar to that shown below:
<RC_1 TopographyTDR="T77100"/><RC_1 TopographyTDR="T77100"/>
<RC_1 TopographyTDR="T01000"/><RC_1 TopographyTDR="T01000"/>
<RC_1 TopographyTDR="EGFR "/> <RC_1 TopographyTDR="GHER2"/>
<RC_1 TopographyTDR="T04020"/><RC_1 TopographyTDR="T04020"/>
<RC_1 TopographyTDR="T77100"/><RC_1 TopographyTDR="T77100"/>
This is the correct data, but I need the tag to be 'TopographyTDR' without the RC_1. i.e. the data should look like:
<TopographyTDR="T77100"/><TopographyTDR="T77100"/>
<TopographyTDR="T01000"/><TopographyTDR="T01000"/>
<TopographyTDR="EGFR "/> <TopographyTDR="GHER2"/>
<TopographyTDR="T04020"/><TopographyTDR="T04020"/>
<TopographyTDR="T77100"/><TopographyTDR="T77100"/>
Is there a simple way to do this? i.e. to avoid having the table name appear in the XML tag text?
Thanks in advance.
You can use for xml path instead of for xml auto and specify tag names explicitly.
Something like:
Select RC_1.Master_Code AS 'TopographyTDR'
From apex.Histo_Result_Coding as RC_1
Where RC_1.Histo_Report = Histo_Result_Coding.Histo_Report
ORDER BY RC_1.Histo_report
for XML path('')
Update:
Looking at your desired output more precisely - it doesn't looks like valid xml.
Despite on missing root node (it could be omitted for simplicity, I suppose), this format has fundamental problem: tag like <TopographyTDR="T77100"/> in fact doesn't has tag name but only has attribute TopographyTDR having value T77100. Are you sure you want such a pseudo-xml data?
Your desired format is not allowed. An XML node must have a tag name and a content or attributes. And you'll need a root...
You must use PATH instead of AUTO. Look at this:
select top 3 name
from sys.objects
for xml path(''),ROOT('root');
select top 3 name AS [#attrib]
from sys.objects
for xml path('item'),ROOT('root')

How to get a particular attribute from XML element in SQL Server

I have something like the following XML in a column of a table:
<?xml version="1.0" encoding="utf-8"?>
<container>
<param name="paramA" value="valueA" />
<param name="paramB" value="valueB" />
...
</container>
I am trying to get the valueB part out of the XML via TSQL
So far I am getting the right node, but now I can not figure out how to get the attribute.
select xmlCol.query('/container/param[#name="paramB"]') from LogTable
I figure I could just add /#value to the end, but then SQL tells me attributes have to be part of a node. I can find a lot of examples for selecting the child nodes attributes, but nothing on the sibling atributes (if that is the right term).
Any help would be appreciated.
Try using the .value function instead of .query:
SELECT
xmlCol.value('(/container/param[#name="paramB"]/#value)[1]', 'varchar(50)')
FROM
LogTable
The XPath expression could potentially return a list of nodes, therefore you need to add a [1] to that potential list to tell SQL Server to use the first of those entries (and yes - that list is 1-based - not 0-based). As second parameter, you need to specify what type the value should be converted to - just guessing here.
Marc
Depending on the the actual structure of your xml, it may be useful to put a view over it to make it easier to consume using 'regular' sql eg
CREATE VIEW vwLogTable
AS
SELECT
c.p.value('#name', 'varchar(10)') name,
c.p.value('#value', 'varchar(10)') value
FROM
LogTable
CROSS APPLY x.nodes('/container/param') c(p)
GO
-- now you can get all values for paramB as...
SELECT value FROM vwLogTable WHERE name = 'paramB'

Resources