I'm working with XML data and want to return the values of two child nodes. I can get this work with a single node but not return two seperate columns. How do I write the xPath and xQuery to return two columns?
DECLARE #x XML
Set #x = '
<MT_BoxTextCtrl>
<DataSpec>ShipID_3_1_1</DataSpec>
<Label>Mode</Label>
<Size>230,30</Size>
<Units />
<UserLoValue />
</MT_BoxTextCtrl>
<MT_BoxTextCtrl>
<DataSpec>ShipID_3_1_2</DataSpec>
<Label>Sub Mode</Label>
<Size>230,30</Size>
<Units />
<UserLoValue />
</MT_BoxTextCtrl>
<MT_AlarmCtrl>
<AlarmRngIsReversed>False</AlarmRngIsReversed>
<CustomQuery />
<DataSpec>ShipID_9_1_1</DataSpec>
<HiValue>1</HiValue>
<Label />
</MT_AlarmCtrl>
<MT_AlarmCtrl>
<AlarmRngIsReversed>False</AlarmRngIsReversed>
<CustomQuery />
<DataSpec>ShipID_9_1_5</DataSpec>
<HiValue>1</HiValue>
<Label>In 500M DP Zone</Label>
</MT_AlarmCtrl>'
Select T.c.value('.', 'varchar(30)') as 'DataSpec'
from #x.nodes('//DataSpec') T(c)
Select T.c.value('.', 'varchar(30)') as Label
from #x.nodes('//Label') T(c)
thanks
Try this:
SELECT
NodeType = XC.value('local-name(.)', 'varchar(25)'),
DataSpec = XC.value('(DataSpec)[1]', 'varchar(30)'),
Label = XC.value('(Label)[1]', 'varchar(30)')
FROM
#x.nodes('/*') XT(XC)
This basically takes every top-level node and returns a "virtual" table of those XML fragments. From those XML fragments, I grab the node name (the "type" of the XML node in question), the DataSpec and the Label subelements (their textual values).
I get an output like this:
Related
I have this XPath expression, which sum all nodes in the deepest XML hierarchy, without using nodes names:
select #data.value('sum(//*[not(*)])', 'float')
How do I make an exception of one node, by its name?
Say this is the xml:
<c>
<b1>
<a>1</a>
<d>4</d>
<g>5</g>
</b1>
<b1>
<a>7</a>
<d>1</d>
<g>2</g>
</b1>
</c>
I would like the sum to contain "d" and "g", without "a", but "a" will be pass as parameter and so need to be represented as parameter inside the expression. I've tried the following:
declare #except varchar(max) = 'a'
select #data.value('sum(//*[not(*)])', 'float') - #data.value('sum(//*:local-name()=sql:variable("#except"))', 'float')
but no success.
While Marc's answer is what i would do mostly, I have a small hack for you, below
select #data.value('sum(//*[not(*)])', 'float')- #data.value('sum(//*:a)', 'float')
You could parse the XML into name and value in a CTE, and then select from that CTE - something like this:
DECLARE #Data XML = '...(your XML here).....';
;WITH XmlCte AS
(
SELECT
NodeName = xc.value('local-name(.)', 'varchar(25)'),
NodeValue = xc.value('(.)[1]', 'int')
FROM
#Data.nodes('/c/b1/*') AS XT(XC)
)
SELECT SUM(x.NodeValue)
FROM XmlCte x
WHERE x.NodeName <> 'a'
I need to define some cursor for spliting t-sql #xml variable on elements level into different #xml(s).
for example:
<root>
<element id=10/>
<element id=11/>
<element id=12/>
<element id=13/>
</root>
so that get the following values inside of tsql cursor:
<root><element id=10/><element id=11/></root>
then
<root><element id=12/><element id=13/></root>
and so on where n number of elements pro cursor loop.
Well, you can use the build-in functions for manipulating XML. For example, the following statement:
DECLARE #XML XML = N'<root><element id="10"/><element id="11"/><element id="12"/><element id="13"/></root>'
SELECT ROW_NUMBER() OVER (ORDER BY T.c)
,T.c.query('.')
FROM #XML.nodes('root/element') T(c)
will give you all elements preserving the order they have in the XML structure:
Then you can stored this result and build separate smaller XML variables.
For different elements you can use * like this:
DECLARE #XML XML = N'<root><element1 id="10"/><element2 id="11"/><element3 id="12"/><element4 id="13"/></root>'
SELECT ROW_NUMBER() OVER (ORDER BY T.c)
,T.c.query('.')
FROM #XML.nodes('root/*') T(c)
I have an XML variable, for ex.
DECLARE #xml XML =
'<A>
<AA>aa</AA>
<AB>
<ABA>aba</ABA>
</AB>
</A>
<B>b</B>
<C>
<CA>ca</CA>
</C>
I want to get a structure of this XML- table with one VARCHAR kolumn:
structure (VARCHAR)
--------------------
'A/AA'
'A/AB/ABA'
'B'
'C/CA'.
I don't need to get text in node- i need only structure.
XML variable can be different (i don't know number of nodes, name of nodes, etc.).
Variable #xml can be without ROOT element.
I tried many combinations of .value() or .nodes(), but it didn't works.
Best result give me an operation:
SELECT
grandparent.gname.value('fn:local-name(.)', 'VARCHAR(MAX)'),
parent.pname.value('fn:local-name(.)', 'VARCHAR(MAX)'),
child.cname.value('fn:local-name(.)', 'VARCHAR(MAX)')
FROM
#xml.nodes('*') AS grandparent(gname)
CROSS APPLY
grandparent.gname.nodes('*') AS parent(pname)
CROSS APPLY
parent.pname.nodes('*') AS child(cname)
It gaves me 'A/AB/ABA', but if i don't know number of nodes and nodes names, it is useless to me to continue.
Use a recursive CTE to extract the nodes one level at a time. The anchor part extract the root nodes and query('*') gets the child nodes for each node found. exist('*') is used to filter out the intermediate rows that is created during the recursion. The recursive part does the same as the anchor only it uses the XML provided in SubNodes instead.
declare #xml xml =
'<A>
<AA>aa</AA>
<AB>
<ABA>aba</ABA>
</AB>
</A>
<B>b</B>
<C>
<CA>ca</CA>
</C>';
with C as
(
select T.X.value('local-name(.)', 'nvarchar(max)') as Structure,
T.X.query('*') as SubNodes,
T.X.exist('*') as HasSubNodes
from #xml.nodes('*') as T(X)
union all
select C.structure + N'/' + T.X.value('local-name(.)', 'nvarchar(max)'),
T.X.query('*'),
T.X.exist('*')
from C
cross apply C.SubNodes.nodes('*') as T(X)
)
select C.Structure
from C
where C.HasSubNodes = 0;
Result:
Structure
---------
B
C/CA
A/AA
A/AB/ABA
I have a scalar xml variable:
DECLARED #XML xml =
'<rows>
<row>
<column1 attrib="" />
<column2 attrib="" />
</row>
<!-- ... -->
</rows>';
I would like to split the data so that each row's xml is assigned to a new record in a table:
Id | ... | XML
1 | | '<row><column1 attrib="" /><column2 attrib="" /></row>'
2 | | etc.
3 | | etc.
I haven't quite grasped xquery so I'm having trouble writing an insert statement that does what I want.
INSERT into MyTable( [XML])
SELECT #XML.query('row')
Now I know something is happening but it appears rather than doing what I intended and inserting multiple new records it is inserting a single record with an empty string into the [XML] column.
What am I not getting?
Clarification
I am not trying to get the inner text, subelements or attributes from each row using value(...). I am trying to capture the entire <row> element and save it to a column of type xml
I've experimented with nodes(...) and come up with:
INSERT into MyTable([XML])
SELECT C.query('*')
FROM #XML.nodes('rows/row') T(C)
Which is closer to what I want but the results don't include the outer <row> tag, just the <column*> elements it contains.
You need to use .nodes(...) to project each <row .../> as a row and the extract the attributes of interest using .value(...). Something like:
insert into MyTable(XML)
select x.value('text()', 'nvarchar(max)') as XML
from #XML.nodes(N'/rows/row') t(x);
text() will select the inner text of each <row .../>. You should use the appropriate expression (eg. node() or #attribute etc, see XPath examples), depending on what you want from the row (your example does not make it clear at all, with all those empty elements...).
T-SQL Script:
SET ANSI_WARNINGS ON;
DECLARE #x XML =
'<rows>
<row Atr1="11" />
<row Atr1="22" Atr2="B" />
<row Atr1="33" Atr2="C" />
</rows>';
DECLARE #Table TABLE(Id INT NOT NULL, [XMLColumn] XML NOT NULL);
INSERT #Table (Id, XMLColumn)
SELECT ROW_NUMBER() OVER(ORDER BY ##SPID) AS Id,
a.b.query('.') AS [XML]
FROM #x.nodes('//rows/row') AS a(b);
SELECT *
FROM #Table t;
Results:
Id XMLColumn
-- --------------------------
1 <row Atr1="11" />
2 <row Atr1="22" Atr2="B" />
3 <row Atr1="33" Atr2="C" />
I actually want to achieve the following description
This is the table argument I want to pass to the server
<items>
<item category="cats">1</item>
<item category="dogs">2</item>
</items>
SELECT * FROM Item
WHERE Item.Category = <one of the items in the XML list>
AND Item.ReferenceId = <the corresponding value of that item xml element>
--Or in other words:
SELECT FROM Items
WHERE Item IN XML according to the splecified columns.
Am I clear enought?
I don't mind to do it in a different way other than xml.
What I need is selecting values that mach an array of two of its columns' values.
You should be able to parse the XML thus, and join it like a table.
DECLARE #foo XML;
SET #foo = N'<items>
<item category="cats">1</item>
<item category="dogs">2</item>
</items>';
WITH xml2Table AS
(
SELECT
x.item.value('#category', 'varchar(100)') AS category,
x.item.value('(.)[1]', 'int') AS ReferenceId
FROM
#foo.nodes('//items/item') x(item)
)
SELECT
*
FROM
Item i
JOIN
xml2Table_xml x ON i.category = x.Category AND i.ReferenceId = x.ReferenceId
DECLARE #x XML;
SELECt #x = N'<items>
<item category="cats">1</item>
<item category="dogs">2</item>
</items>';
WITH shred_xml AS (
SELECT x.value('#category', 'varchar(100)') AS category,
x.value('text()', 'int') AS ReferenceId
FROM #x.nodes('//items/item') t(x) )
SELECT *
FROM Item i
JOIN shred_xml s ON i.category = s.category
AND i.ReferenceId = s.ReferenceId;
BTW Doing this from memory, might had got some syntax off, specially at text().