Insert XML element with attribute into SQL XML column - sql

I have the following XML data stored in [MyTable].Column1 which is an XML type column.
I want to insert a new element to it:
I'm using the following SQL, it executed successfully but when I query the same column value again, I do not see my new XML element. Do I miss certain syntax for inserting an element with attributes?
UPDATE [MyTable]
SET Column1.modify('insert <Setting Name="H" Value="0"/> as last into (/SettingValues)[1]')
WHERE ID = 'xxxxx'

First of all - for your next question: Please do not post pictures! Try to set up a test-scenario. Please read How to ask a good SQL question and How to create a MCVE.
About your question
Your code - on the first sight - should work. But you obviously modified it to fit to this forum.
DECLARE #myTable TABLE(ID VARCHAR(100),Column1 XML)
INSERT INTO #myTable VALUES
('111'
,'<SettingValues>
<Setting Name="A-name" Value="A-value"/>
</SettingValues>')
, ('222'
,'<SettingValues>
<Setting Name="A-name" Value="A-value"/>
</SettingValues>');
UPDATE #MyTable
SET Column1.modify('insert <Setting Name="H" Value="0"/> as last into (/SettingValues)[1]')
WHERE ID = '222';
SELECT *
FROM #myTable
This works as expected.
ID Column1
111 <SettingValues><Setting Name="A-name" Value="A-value" /></SettingValues>
222 <SettingValues><Setting Name="A-name" Value="A-value" /><Setting Name="H" Value="0" /></SettingValues>
After execution you see "1 row affected".
Some ideas:
The filter is not fullfilled
The given XML declares namespaces but never uses them... This is a bit odd. Have you reduced the namespaces and your original XML includes a default namespace (or the elements are prefixed somehow)?
You are checking against the wrong target (other server, other schema, other table...
But: The code above should work...

Related

Fetch a value from a column which contains XML

How to fetch a value from a column which contains XML in SQL Server?
below is my sample XML column value and the id's can be swap anytime(101,100) or (201,100,101,321).
<Questions>
<item id="101">Yes</item>
<item id="100">No</item>
</Questions>
I want to fetch a value based on Id. Like fetching Yes from id=101.
Sample code much appreciated.
I tried with below sample, but Unable to retrieve value "Yes"
select Y.value('#item[1]','varchar[3]') as valT from tbl_storeXML s cross apply s.Questions.nodes('Questions/item') as X(Y) where e.empId=256 and Y.value('#id','int')=101
Please help on this.
Ps. It's not a home work, I am learning handling xml in sql server.
Use of the value is not done correct, you do:
Y.value('#id','int')
This should be: Y.value('(#id)[1]','int')
round braces around #id, see: docs: value() Method
and Y.value('item[1]','varchar[3]').
This should be: Y.value('(#item)[1]','varchar(3)').
The # is removed because item is not an attribute
varchar should have round braces, not square braces.
Your try, after changes will become:
select
Y.value('(item)[1]','varchar(3)') as valT
from tbl_storeXML s
cross apply s.Questions.nodes('Questions/item') as X(Y)
where e.empId=256 and Y.value('(#id)','int')=101
This is not tested, because I do not have those tables. (I do think Y.value('(item)[1]','varchar(3)') might need to be written as Y.value('(.)[1]','varchar(3)') )
But the same approach can be seen in this DBFIDDLE
DECLARE #xml XML = '<Questions>
<item id="101">Yes</item>
<item id="100">No</item>
</Questions>';
select
X.y.value('(#id)[1]','VARCHAR(20)') id,
X.y.value('(.)[1]','VARCHAR(20)') value
from #xml.nodes('Questions/item') as X(y);
output:
id
value
101
Yes
100
No

SQL WHERE clause with multiple XML attributes

I have table in database with XML column. Now I need to select some rows by two attributes from XML.
So far I've come up with this:
SELECT o.Id
FROM Objects o
WHERE o.SerializedObject.value('(/object/param[#id="111"]/#value)[1]', 'varchar(8)') = '-1'
AND o.SerializedObject.value('(/object/param[#id="222"]/#value)[1]', 'varchar(8)') = '8'
EDIT:
XML is like:
<object>
<param id="1" value="111"/>
<param id="2" value="222"/>
...
<param id="200" value="4545"/>
<object>
Each object has ~2k params.
I'm wondering if there is a better way to do that with single XML query.
This depends on your XML (you did not show an example, but I assume this is kind of EAV).
You can try using XML's method .exist():
DECLARE #mockup TABLE(ID INT IDENTITY,Comment VARCHAR(100),SerializedObject XML);
INSERT INTO #mockup VALUES
('just one of them','<object><param id="111" value="-1"/></object>')
,('both, but wrong values','<object><param id="111" value="-1"/><param id="222" value="-1"/></object>')
,('both, should fit','<object><param id="111" value="-1"/><param id="222" value="8"/></object>')
SELECT o.Id,o.Comment,o.SerializedObject
FROM #mockup o
WHERE o.SerializedObject.exist('/object[param[#id="111" and #value="-1"] and param[#id="222" and #value="8"]]')=1;
.exist() is the fastest here, because it does not return any value. It will just return 1 on the first occurance found. This is especially fast, when there are many occurances of a <param id="111" value="???"> Otherwise you'd have to shred the whole lot and place the filter on the whole resultset.
And - of course! - the necessary hint: As told in a comment by Jeroen Mostert dealing with bigger XMLs might turn out as a bottle neck. If you need this more often, you might think about a relational design instead of big XMLs...

extracting all tags(duplicates also) with specified name from xmltype column in sql

i want to extract a tag from an xml and insert into another table.
this XML is having different name spaces hence i use local-name() to fetch the tag which i want.
but some times there are multiple tags with same name. hence its failing with EXACTFETCH RETURNS MULTIPLE NODES. when multiple tags are existed i want to consider both instead of ignoring the second occurence.
source_table(id, payload):
id : 10
payload :
<root>
<name>john</name>
<status>
<statuscode>1</statuscode>
<statusmessage>one</statusmessage>
<statuscode>2</statuscode>
<statusmessage>two</statusmessage>
</status>
</root>
i want to extract stauscode and message and insert into another table
destination_table(id,name,statuscode,message)
output
10,john,1,one
10,john,2,two
below is the query i used
select id,
extract(payload, '//*[local-name()="name"]'),
extract(payload, '//*[local-name()="statuscode"]'),
extract(payload, '//*[local-name()="statusmessage"]')
from source_table;
i can get first occurence or second occurence by specifying [1] or [2] but i need both the stauscodes to be displayed like below
10,john,1,one
10,john,2,two
any help here
Hope this is what you need: Just past this into an empty query window and execute. Adapt it for your needs:
This solution assumes, that the status codes are sorted (as in your example 1,2,...). If this could occur in random order, just ask again...
Short explanation: The CTE "NameAndCode" brings up the name and the statuscodes. The ROW_NUMBER-function give us the node's index. This index I use to fetch the right message.
One more hint: If you can change the XML's format, it would be better to make the message an attribut of statuscode or to have it as subnode...
DECLARE #xmlColumn XML='<root>
<name>john</name>
<status>
<statuscode>1</statuscode>
<statusmessage>one</statusmessage>
<statuscode>2</statuscode>
<statusmessage>two</statusmessage>
</status>
</root>';
WITH NameAndCode AS
(
SELECT #xmlColumn.value('(/root/name)[1]','varchar(max)') AS Name
,x.y.value('.','int') AS Statuscode
,x.y.query('..') XMLNode
,ROW_NUMBER() OVER(ORDER BY x.y.value('.','int')) AS StatusIndex
FROM #xmlColumn.nodes('/root/status/statuscode') AS x(y)
)
SELECT *
,XMLNode.value('(/status[1]/statusmessage[sql:column("StatusIndex")])[1]','varchar(max)')
FROM NameAndCode

Change XML attribute values in SQL Server XML column

I have a SQL Server database table like the following:-
Id (int) Info(xml)
------------------------
1 <xml....
2 <xml ...
3 <xml ...
The XML in each record in the Info field is something like:-
<CodesParameter xmlns="http://mynamespace.com/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Data Code="A1" HasMoreData="true">
.
.
</Data>
</CodesParameter>
I would like to be able to change the value of the <Data Code="..."> within the XML and have tried the following query:-
UPDATE MyTable
SET Info.modify('replace value of (Data[1]/#Code)with ("B1")')
WHERE Id = 2
Go
The message backs says that the query has executed successfully, but the value does not change.
I have tried declaring the namespace:-
UPDATE MyTable
SET Info.modify('declare namespace myns="http://mynamespace.com/";
replace value of (/myns:WebTableParameter[1]/myns:DataTable[1]/#Code)with ("B1")')
WHERE Id = 2
Go
But the same result - the message says the query has executed successfully, but nothing has changed.
Any help appreciated.
You have several problems:
you're totally ignoring the XML namespace on the XML document in question (in your original UPDATE)
you're using an incomplete XPath expression for your modify
Try this statement:
;WITH XMLNamespaces(DEFAULT 'http://mynamespace.com/')
UPDATE dbo.MyTable
SET Info.modify('replace value of (/CodesParameter/Data[1]/#Code)[1] with ("B1")')
WHERE Id = 1
Basically, you need to start at the root of the XML document - you've missed (or left out) the <CodesParameter> node entirely. Also: you need to select a single attribute - thus you need another [1] after your parenthesis.
With this statement, I was able to update the XML as desired.

How do I select a top-level attribute of an XML column in SQL Server?

I have an XML column in SQL Server that is the equivalent of:
<Test foo="bar">
<Otherstuff baz="belch" />
</Test>
I want to get the value of the foo attribute of Test (the root element) as a varchar. My goal would be something along the lines of:
SELECT CAST('<Test foo="bar"><Otherstuff baz="belch" /></Test>' AS xml).value('#foo', 'varchar(20)') AS Foo
But when I run the above query, I get the following error:
Msg 2390, Level 16, State 1, Line 1
XQuery [value()]: Top-level attribute
nodes are not supported
John Saunders has it almost right :-)
declare #Data XML
set #Data = '<Test foo="bar"><Otherstuff baz="belch" /></Test>'
select #Data.value('(/Test/#foo)[1]','varchar(20)') as Foo
This works for me (SQL Server 2005 and 2008)
Marc
If you dont know the root element:
select #Data.value('(/*/#foo)[1]','varchar(20)') as Foo
Why does .value('#foo', 'varchar(20)') generate the error “Top-level attribute nodes are not supported”?
When you query the xml data type, the context is the document node, which is an implicit node that contains the root element(s) of your XML document. The document node has no name and no attributes.
How can I get the value of an attribute on the root element?
In your XQuery expression, include the path to the first root element:
DECLARE #Data xml = '<Customer ID="123"><Order ID="ABC" /></Customer>'
SELECT #Data.value('Customer[1]/#ID', 'varchar(20)')
-- Result: 123
If you don’t know (or don’t want to specify) the name of the root element, then just use * to match any element:
SELECT #Data.value('*[1]/#ID', 'varchar(20)')
-- Result: 123
Because the query context is the document node, you don’t need to prefix the XQuery expression with a forward slash (as the other answers unnecessarily do).
Why do I have to include [1]?
The XQuery expression you pass to value() must be guaranteed to return a singleton. The expression Customer/#ID doesn’t satisfy this requirement because it matches both ID="123" and ID="456" in the following example:
DECLARE #Data xml = '<Customer ID="123" /><Customer ID="456" />'
Remember that the xml data type represents an XML document fragment, not an XML document, so it can contain multiple root elements.
What’s the difference between Customer[1]/#ID and (Customer/#ID)[1]?
The expression Customer[1]/#ID retrieves the ID attribute of the first <Customer> element.
The expression (Customer/#ID)[1] retrieves the ID attribute of all <Customer> elements, and from this list of attributes, picks the first.
The following example demonstrates the difference:
DECLARE #Data xml = '<Customer /><Customer ID="123" /><Customer ID="456" />'
SELECT #Data.value('Customer[1]/#ID', 'varchar(20)')
-- Result: NULL (because the first Customer element doesn't have an ID attribute)
SELECT #Data.value('(Customer/#ID)[1]', 'varchar(20)')
-- Result: 123