How to get value from node? SQL Server XML dml - sql-server

#x xml:'
<a number="1">
<b>1</b>
</a>'
I want use query() to get value (can't use value())
#x.query('string(/a[1]/b[1])')
is ok.
#x.query('string(/a[#number="1"]/b)')
throws an error.
Do you have any solution? I want use [#number="1"] to get value.

You can try this.
#x.query('string( (/a[#number="1"]/b)[1])')

In most cases one wants to read more than one value from a given node. You can use a combination of .nodes() (very related to .query(), but returning a derived table) and .query() or .value() like here:
DECLARE #x xml=
N'<a number="1">
<b>1</b>
</a>';
SELECT MyA.value(N'(b/text())[1]','int') AS ReadTheValue
,MyA.query(N'b') QueryTheValue
FROM #x.nodes(N'/a[#number="1"]') AS A(MyA);
You can pass in the search value as a variable like here:
DECLARE #SearchNumber INT=1;
SELECT MyA.value(N'(b/text())[1]','int') AS ReadTheValue
,MyA.query(N'b') QueryTheValue
FROM #x.nodes(N'/a[#number=sql:variable("#SearchNumber")]') AS A(MyA);
If you need nothing else then the value through .query() go with the solution provided by Serkan Aslan. You were just missing to ensure the inner experession to be singleton.
But I must admit, that I have no idea, why one should need this...

Related

SQL: Using XML as input to do an inner join

I have XML coming in as the input, but I'm unclear on how I need to setup the data and statement to get the values from it. My XML is as follows:
<Keys>
<key>246</key>
<key>247</key>
<key>248</key>
</Keys>
And I want to do the following (is simplified to get my point across)
Select *
From Transaction as t
Inner Join #InputXml.nodes('Keys') as K(X)
on K.X.value('#Key', 'INT') = t.financial_transaction_grp_key
Can anyone provide how I would do that? What would my 3rd/4th line in the SQL look like?
Thanks!
From your code I assume this is SQL-Server but you added the tag [mysql]...
For your next question please keep in mind, that it is very important to know your tools (vendor and version).
Assuming T-SQL and [sql-server] (according to the provided sample code) you were close:
DECLARE #InputXml XML=
N'<Keys>
<key>246</key>
<key>247</key>
<key>248</key>
</Keys>';
DECLARE #YourTransactionTable TABLE(ID INT IDENTITY,financial_transaction_grp_key INT);
INSERT INTO #YourTransactionTable VALUES (200),(246),(247),(300);
Select t.*
From #YourTransactionTable as t
Inner Join #InputXml.nodes('/Keys/key') as K(X)
on K.X.value('text()[1]', 'INT') = t.financial_transaction_grp_key;
What was wrong:
.nodes() must go down to the repeating element, which is <key>
In .value() you are using the path #Key, which is wrong on two sides: 1) <key> is an element and not an attribute and 2) XML is strictly case-sensitive, so Key!=key.
An alternative might be this:
WHERE #InputXml.exist('/Keys/key[. cast as xs:int? = sql:column("financial_transaction_grp_key")]')=1;
Which one is faster depends on the count of rows in your source table as well as the count of keys in your XML. Just try it out.
You probably need to parse the XML to a readable format with regex.
I wrote a similar event to parse the active DB from an xmlpayload that was saved on a table. This may or may not work for you, but you should be able to at least get started.
SELECT SUBSTRING(column FROM IF(locate('<key>',column)=0,0,0+LOCATE('<key>',column))) as KEY FROM table LIMIT 1\G

Change value of a xml root node attribute

I am trying to modify the attribute of an XML root attribute in XQuery with T-SQL but I don't manage to do that. My XML has a namespace in it and I can't exactly bypass this. When I query the value of the xml I successfully retrieve the value because I use: ;WITH XMLNAMESPACES (DEFAULT 'some:namespace:here:v1'). I also tried to use: 'declare default element namespace "some:namespace:here:v1";' in the XQuery but does not seem to work.
Any ideas of how can I achieve this ?
This is an example of the XML I am trying to modify.
DECLARE #XML_TO_READ XML = N'
<F2101 xmlns="some:namespace:here:v1" propertyToModify="valueToModify">
<person xmlns="some:namespace:here:v1" anotherPropertyToModify="anotherValueToModify" />
</F2101>'
I retrieve the value like this:
;WITH XMLNAMESPACES (DEFAULT 'some:namespace:here:v1')
SELECT propertyToModify =
#XML_TO_READ.value('(/F2101/#propertyToModify)[1]', 'nvarchar(50)')
And I tried to modify (update) the value like this:
SET #XML_TO_READ.modify('
declare default element namespace "some:namespace:here:v1";
replace value of (/F2101/propertyToModify/text())[1] with ("modifiedValue")')
I tried multiple solutions but I did not find anything that would work for my special case here.
Thanks in advance.
Your statement should be:
SET #XML_TO_READ.modify('
declare default element namespace "some:namespace:here:v1";
replace value of (/F2101/#propertyToModify)[1] with ("modifiedValue")')
Note the "#propertyToModify" rather than "propertyToModify/text()".
Also, documentation link: replace value of (XML DML).

Supply xml element value in modify() method

I know how to replace element value for the xml element in the modify() method. Here's the example
TSQL Replace value in XML String
My problem is a bit different. Taking example from above link...
UPDATE dbo.TFS_Feedback_New
SET Details.modify('
replace value of (/optional/educational/text())[1]
with sql:variable("#updatedEducation")')
WHERE feedbackID = #FBID
What I want to do is provide value for 'educational'. In other words I want to do something like this
UPDATE dbo.TFS_Feedback_New
SET Details.modify('
replace value of (/optional/sql:variable("#name")/text())[1]
with sql:variable("#value")')
WHERE feedbackID = #FBID
I'm getting the following error because of sql:variable("#name")
The XQuery syntax '/function()' is not supported.
How can I pass both the name of the element to be updated and its value to my
stored procedure and have it update the XML column?
You are not allowed to use variables as part of the XPath, but you can use a predicate:
DECLARE #xml XML=
N'<root>
<optional>
<educational>SomeText</educational>
<someOther>blah</someOther>
</optional>
</root>';
--The straight approach as you know it:
SET #xml.modify('replace value of (/root/optional/educational/text())[1] with "yeah!"');
SELECT #xml;
--Now we use a variable to find the first node below <optional>, which name is as given:
DECLARE #ElementName VARCHAR(100)='educational';
SET #xml.modify('replace value of (/root/optional/*[local-name()=sql:variable("#ElementName")]/text())[1] with "yeah again!"');
SELECT #xml;
Try it out...

Trouble with XQuery SQL statement to get correct parent/child value

XML Schema: (Assumes that the XMLNAMESPACES are already set AS a and b)
<Layer1 xmlns="a">
<Layer2 xmlns="b">
<Layer3>
<id>val1</id>
<data>False</data>
</Layer3>
<Layer3>
<id>val2</id>
<data>True</data>
</Layer3>
</Layer2>
</Layer1>
I am using this bit of sql to attempt to accomplinsh my task.
ITEM.value('(/a:Layer1/b:Layer2/b:Layer3)[1]', 'varchar(max)') AS ReturnValue
What I am trying to do is get only the true values where id='val2' AND data='True'. Something like this:
ITEM.value('(/a:Layer1/b:Layer2/b:Layer3[id="val2" and data="True"]/b:data)[0]', 'varchar(max)') AS ReturnValue
For some reason the above returns null. I'd assume it is a syntax error.
In this query I would like the value to be returned as True from any Layer3 Parent where the id and data are following the conditions. I'd appreciate any help and thank you in advance.
From your code I take, that this is SQL-Server...
Your XML defines a default namespace for the first node <Layer1> and again a default namespace for the <Layer2>.
That means, that all elements below <Layer2> are in this new default namespace.
Your code misses the namespaces here [id="val2" and data="True"], but I do not really understand what you are trying to achieve actually...
DECLARE #ITEM XML=
N'<Layer1 xmlns="a">
<Layer2 xmlns="b">
<Layer3>
<id>val1</id>
<data>False</data>
</Layer3>
<Layer3>
<id>val2</id>
<data>True</data>
</Layer3>
</Layer2>
</Layer1>';
--This query will find the value "True" within <data> below a <Layer3>, where the <id> is "val2":
WITH XMLNAMESPACES('a' AS a,'b' AS b)
SELECT #ITEM.value('(/a:Layer1/b:Layer2/b:Layer3[b:id="val2"]/b:data/text())[1]', 'varchar(max)');
You might use a *: in front of each and any name to use a namespace wildcard:
SELECT #ITEM.value('(/*:Layer1/*:Layer2/*:Layer3[*:id="val2"]/*:data/text())[1]', 'varchar(max)');
One more approach was to define your inner default ns like this, which saves some typing:
WITH XMLNAMESPACES('a' AS a, DEFAULT 'b')
SELECT #ITEM.value('(/a:Layer1/Layer2/Layer3[id="val2"]/data/text())[1]', 'varchar(max)');

Using SQL Server 2005's XQuery select all nodes with a specific attribute value, or with that attribute missing

Update: giving a much more thorough example.
The first two solutions offered were right along the lines of what I was trying to say not to do. I can't know location, it needs to be able to look at the whole document tree. So a solution along these lines, with /Books/ specified as the context will not work:
SELECT x.query('.') FROM #xml.nodes('/Books/*[not(#ID) or #ID = 5]') x1(x)
Original question with better example:
Using SQL Server 2005's XQuery implementation I need to select all nodes in an XML document, just once each and keeping their original structure, but only if they are missing a particular attribute, or that attribute has a specific value (passed in by parameter). The query also has to work on the whole XML document (descendant-or-self axis) rather than selecting at a predefined depth.
That is to say, each individual node will appear in the resultant document only if it and every one of its ancestors are missing the attribute, or have the attribute with a single specific value.
For example:
If this were the XML:
DECLARE #Xml XML
SET #Xml =
N'
<Library>
<Novels>
<Novel category="1">Novel1</Novel>
<Novel category="2">Novel2</Novel>
<Novel>Novel3</Novel>
<Novel category="4">Novel4</Novel>
</Novels>
<Encyclopedias>
<Encyclopedia>
<Volume>A-F</Volume>
<Volume category="2">G-L</Volume>
<Volume category="3">M-S</Volume>
<Volume category="4">T-Z</Volume>
</Encyclopedia>
</Encyclopedias>
<Dictionaries category="1">
<Dictionary>Webster</Dictionary>
<Dictionary>Oxford</Dictionary>
</Dictionaries>
</Library>
'
A parameter of 1 for category would result in this:
<Library>
<Novels>
<Novel category="1">Novel1</Novel>
<Novel>Novel3</Novel>
</Novels>
<Encyclopedias>
<Encyclopedia>
<Volume>A-F</Volume>
</Encyclopedia>
</Encyclopedias>
<Dictionaries category="1">
<Dictionary>Webster</Dictionary>
<Dictionary>Oxford</Dictionary>
</Dictionaries>
</Library>
A parameter of 2 for category would result in this:
<Library>
<Novels>
<Novel category="2">Novel2</Novel>
<Novel>Novel3</Novel>
</Novels>
<Encyclopedias>
<Encyclopedia>
<Volume>A-F</Volume>
<Volume category="2">G-L</Volume>
</Encyclopedia>
</Encyclopedias>
</Library>
I know XSLT is perfectly suited for this job, but it's not an option. We have to accomplish this entirely in SQL Server 2005. Any implementations not using XQuery are fine too, as long as it can be done entirely in T-SQL.
It's not clear for me from your example what you're actually trying to achieve. Do you want to return a new XML with all the nodes stripped out except those that fulfill the condition? If yes, then this looks like the job for an XSLT transform which I don't think it's built-in in MSSQL 2005 (can be added as a UDF: http://www.topxml.com/rbnews/SQLXML/re-23872_Performing-XSLT-Transforms-on-XML-Data-Stored-in-SQL-Server-2005.aspx).
If you just need to return the list of nodes then you can use this expression:
//Book[not(#ID) or #ID = 5]
but I get the impression that it's not what you need. It would help if you can provide a clearer example.
Edit: This example is indeed more clear. The best that I could find is this:
SET #Xml.modify('delete(//*[#category!=1])')
SELECT #Xml
The idea is to delete from the XML all the nodes that you don't need, so you remain with the original structure and the needed nodes. I tested with your two examples and it produced the wanted result.
However modify has some restrictions - it seems you can't use it in a select statement, it has to modify data in place. If you need to return such data with a select you could use a temporary table in which to copy the original data and then update that table. Something like this:
INSERT INTO #temp VALUES(#Xml)
UPDATE #temp SET data.modify('delete(//*[#category!=2])')
Hope that helps.
The question is not really clear, but is this what you're looking for?
DECLARE #Xml AS XML
SET #Xml =
N'
<Books>
<Book ID="1">Book1</Book>
<Book ID="2">Book2</Book>
<Book ID="3">Book3</Book>
<Book>Book4</Book>
<Book ID="5">Book5</Book>
<Book ID="6">Book6</Book>
<Book>Book7</Book>
<Book ID="8">Book8</Book>
</Books>
'
DECLARE #BookID AS INT
SET #BookID = 5
DECLARE #Result AS XML
SET #result = (SELECT #xml.query('//Book[not(#ID) or #ID = sql:variable("#BookID")]'))
SELECT #result

Resources