I have a XML column in database that may look like that ones:
<sql-connection-info name="myname" server="(local)\SQLEXPRESS" other-attribute="value" />
<oracle-connection-info name="othername" server="address" other-attribute="value" />
and so on. The names of nodes and attributes can be nearly anything. I need to iterate over the attribute/value pair on the first node. Every sample I have seen was for known node/attribute names.
When I tried to use
#xmlColumn.query("/#*")
I get this error
XQuery [query()]: Top-level attribute nodes are not supported.
Is this possible in TSQL? If yes how can I do this?
declare #xmlColumn xml = '<sql-connection-info name="myname" server="(local)\SQLEXPRESS" other-attribute="value" />'
select T.N.value('local-name(.)', 'varchar(max)') as Name,
T.N.value('.', 'varchar(max)') as Value
from #xmlColumn.nodes('//#*') as T(N)
Result:
Name Value
---------------- -------------------
name myname
server (local)\SQLEXPRESS
other-attribute value
You can use
#xmlColumn.query("/node()[1]")
to get the first node of each entry. node() matches any element node.
From your post I do not understand if you want to have e. g. the name attribute of the first node of your entry. Then you would use:
#xmlColumn.query("/node()[1]/#name")
Related
In my application I am running a rather complex query using FOR XML PATH() to construct an xml result. I am using this query in several stored procedures but each the root node and the first level node names are different. What I would like to do is create a scalar user defined function that takes the two node names as parameters and returns the correct xml. The expected output would look like this:
<{root node name}>
<{first level node name} attr1="value1" attr2="value2">
<detail attr1="value3">
<error code="1" descr="some error"/>
<error code="3" descr="some other error"/>
</detail>
</{first level node name}>
<error code="4" descr="yet another error"/>
</{root node name}>
With {root node name} and {first level node name} being passed as parameters
I know I can use dynamic SQL to do this but it can be very cumbersome and I would like to avoid it. I could also do it easily in C# or using XSLT but I need to do this entirely in SQL as it will be called from SQL Service broker handlers.
Unfortunately FOR XML PATH requires a literal for the node name. I also tried creating the xml with known generic node names and then trying to replace them using XQuery, but that also requires literals. Both the XQuery has to be a literal and the node names used in node constructors need to be literals as well (cannot use sql:variable())
Is there any way create XML with dynamic node names?
As the elements names are built out of the result set's column names this cannot work (other than dynamic SQL or CLR), but there is rescue:
DECLARE #rowName NVARCHAR(10)=N'TheRow';
DECLARE #rootName NVARCHAR(10)=N'TheRoot';
DECLARE #OutputXml XML=
CAST(
REPLACE(REPLACE(
CAST((SELECT TOP 2 [name],[type]
FROM sys.objects
FOR XML PATH(N'ThisIsAStringOnlyUsedAsRowElementName')
,ROOT(N'TheSameIdeaForTheRootName')
,TYPE) AS NVARCHAR(MAX))
,N'ThisIsAStringOnlyUsedAsRowElementName',#rowName)
,N'TheSameIdeaForTheRootName',#rootName) AS XML);
SELECT #outputXML;
The result
<TheRoot>
<TheRow>
<name>spt_fallback_db</name>
<type>U </type>
</TheRow>
<TheRow>
<name>spt_fallback_dev</name>
<type>U </type>
</TheRow>
</TheRoot>
In general I'd avoid string manipulations on an XML (due to possible side effects and performance impact). But I think this is a legit workaround...
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');
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
Hi I need to populate table by selecting value from child node
XML looks like this
<Transmitters>
<Id>1</Id><CoverageLevel>2</CoverageLevel>
<Id>2</Id><CoverageLevel>4</CoverageLevel>
<Id>3</Id><CoverageLevel>6</CoverageLevel>
</Transmitters>
and table has two fields
Transmitter(Id, CoverageLevel)
INSERT INTO
Transmitter([idTransmitter], [coverageLevel])
SELECT
ParamValues.T.value('Id[1]', 'nvarchar(50)'),
ParamValues.T.value('CoverageLevel[1]', 'nvarchar(50)')
FROM
#otherTransmitter.nodes('//Transmitters') AS ParamValues(T)
but its not working?
Your XML is not very well formatted for this kind of task - you don't have any useable child nodes inside <Transmitters> that you can depend on.
If your XML looked like this:
<Transmitters>
<Transmitter>
<Id>1</Id><CoverageLevel>2</CoverageLevel>
</Transmitter>
<Transmitter>
<Id>2</Id><CoverageLevel>4</CoverageLevel>
</Transmitter>
<Transmitter>
<Id>3</Id><CoverageLevel>6</CoverageLevel>
</Transmitter>
</Transmitters>
then you could use the XPath expression
#otherTransmitter.nodes('/Transmitters/Transmitter') AS ParamValues(T)
to get a hold of your values.
You don't have that - you only have individual <Id> and <CoverageLevel> inside your <Transmitters> - there's no "container" XML tag that holds together those elements that belong together.
There's really no XPath that will allow you to properly enumerate those nodes.....
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'