SQL Server 2005 - searching for value in XML field - sql-server

I'm trying to query a particular value in an XML field. I've seen lots of examples, but they don't seem to be what I'm looking for
Supposing my xml field is called XMLAttributes and table TableName, and the complete xml value is like the below:
<Attribute name="First2Digits" value="12" />
<Attribute name="PurchaseXXXUniqueID" value="U4RV123456762MBE79" />
(although the xml field will frequently have other attributes, not just PurchaseXXXUniqueID)
If I'm looking for a specific value in the PurchaseXXXUniqueID attribute name - say U4RV123456762MBE79 - how would I write the query? I believe it would be something like:
select *
from TableName
where XMLAttributes.value('(/path/to/tag)[1]', 'varchar(100)') = '5FTZP2QT8Z3E2MAV2D'
... but it's the path/to/tag that I need to figure out.
Or probably there's other ways of getting the values I want.
To summarize - I need to get all the records in a table where the value of a particular attribute in the xml field matches a value I'll pass to the query.
thanks for the help!
Sylvia
edit: I was trying to make this simpler, but in case it makes a difference - ultimately I'll have a temporary table of 50 or so potential values for the PurchaseXXXUniqueID field. For these, I want to get all the matching records from the table with the XML field.

This ought to work:
SELECT
(fields from base table),
Nodes.Attr.value('(#name)[1]', 'varchar(100)'),
Nodes.Attr.value('(#value)[1]', 'varchar(100)')
FROM
dbo.TableName
CROSS APPLY
XMLAttributes.nodes('/Attribute') AS Nodes(Attr)
WHERE
Nodes.Attr.value('(#name)[1]', 'varchar(100)') = 'PurchaseXXXUniqueID'
AND Nodes.Attr.value('(#value)[1]', 'varchar(100)') = 'U4RV123456762MBE79'
You basically need to join the base table's row against one "pseudo-row" for each of the <Attribute> nodes inside the XML column, and the pick out the individual attribute values from the <Attribute> node to select what you're looking for.

Something like that?
declare #PurchaseXXXUniqueID varchar(max)
set #PurchaseXXXUniqueID = 'U4RV123456762MBE79';
select * from TableName t
where XMLAttributes.exist('//Attribute/#value = sql:variable("#PurchaseXXXUniqueID")') = 1

Related

Querying selection of values in XML in SQL Server, on table of mixed values

I have a table with people who have bought tickets for a charity evening event, and the table contains details of registration event, and the XML will show guests they are bringing with them, but also details of any dietary requirements, and the occasional person who might be disabled. This is supposed to be pushed to our CRM system but this is not currently working.
I'm trying to extract some values out of some XML which is in a column in our import table.
I've seen plenty of examples of querying ordinary chunks of XML, but not when the XML is inside a table with other normal INT and VARCHAR values.
We are using SQL Server 2014. I've spent hours googling but haven't the faintest idea on making a query that combined the two together. Or even if I'm supposed to push the XML stuff into a temp table which I could then do a join with.
Declare #xmlstring xml = '<field_import_admin_event_tickets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<und is_array="true">
<item>
<value>8463</value>
<revision_id>4763</revision_id>
</item>
</und>
</field_import_admin_event_tickets>'
select
MainDataCenter.Col.value('(value)[1]', 'varchar(max)') as Name,
MainDataCenter.Col.value('(revision_id)[1]', 'varchar(max)') as Value
from
#xmlstring.nodes('/field_import_admin_event_tickets/und/item') as MainDataCenter(Col)
^ this will work
but I need to query it along with this:-
SELECT *
FROM [importtickets].[bcc].[entityform]
WHERE type LIKE '%show%'
AND createdDATETIME > '2019-03-14'
AND LEN(CAST(field_import_admin_event_tickets AS VARCHAR(MAX)) ) >1
-- bodging a way of seeing if XML code exists or not, doesn't seem to work with IS NOT NULL
AND Jobstatus = 'completed'
The only way I can crudely get values out of the XML is CAST it to a VARCHAR and use lots of REPLACE commands to strip out the XML tags to get it down to the values. There may be 2 to 18 numeric values in each lump of XML
This is my first post on StackOverflow and I've spent days searching on this, so please be gentle with me. Thanks.
2019-07-10 Hey, so I didn't make this fully clear.
each column of XML (a few are nulls) contains 2 - 34 separate numbers in. I dd some crude manipulation of data by CASTing this into VARCHAR and running lots of replace commands to understand it better.
this is the largest example here of some XML, 34 integer values, 17 are 'value' and 17 are 'revision_id'
So I then pushed this all into a new table using lots of SUBSTRING. This is crude but effective, but assumes each value is five digits long (it is so far) my boss is not keen on this solution though.
crudely shredded XML using CAST to VARCHAR and tags manually stripped out
I just need each sets of values extracted in each row so I can then do a JOIN or subquery to them, with a row or something identifiable. The numbers will refer to a guest who is coming to some charity events which will have some attributes such as dietary requirements or disability.
I don't know, if this is the very best approach for your issue, but I hope that I got your question correctly, that you want to combine the working query against an isolated XML with the tabular query, where the XML is the content of a column:
First of all I create a mockup with two rows
DECLARE #mockupTable TABLE(ID INT IDENTITY,SomeOtherValue VARCHAR(100),YourXml XML);
INSERT INTO #mockupTable(SomeOtherValue,YourXml) VALUES
('This is some value in row 1'
,'<field_import_admin_event_tickets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<und is_array="true">
<item>
<value>8463</value>
<revision_id>4763</revision_id>
</item>
</und>
</field_import_admin_event_tickets>')
,('This is some value in row 2'
,'<field_import_admin_event_tickets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<und is_array="true">
<item>
<value>999</value>
<revision_id>888</revision_id>
</item>
</und>
</field_import_admin_event_tickets>');
--The query
SELECT t.ID
,t.SomeOtherValue
,MainDataCenter.Col.value('(value)[1]', 'varchar(max)') as Name
,MainDataCenter.Col.value('(revision_id)[1]', 'varchar(max)') as Value
FROM #mockupTable t
CROSS APPLY t.YourXml.nodes('/field_import_admin_event_tickets/und/item') as MainDataCenter(Col);
The result
ID SomeOtherValue Name Value
1 This is some value in row 1 8463 4763
2 This is some value in row 2 999 888
The idea in short:
APPLY allows to call a table-valued function row-wise. In this case we hand in the content of a column (in your case the XML) into the built-in function .nodes().
Similar to a JOIN we get a joined set, which adds columns (and rows) to the final set. We can use the .value() method to retrieve the actual values from the XML.
If this is the best approach? I don't know...
Your sample above shows just one single <item>. .nodes() would be needed to return several <item> elements in a derived set. With just one <item> this could be done more easily using .value() directly...

Find if a list contains several values - SQL Server Xquery

I am storing a XML data into a table called BikeTable. The XML data is coming from an object that is being serialized using .Net serializer.
BikeTable would look like this :
Id - UniqueIdentifier
XmlData - XML
The XML stored in the XmlData column looks like this :
Record 1 :
<Bike>
<Material>
<Cage>EIECH</Cage>
<Mpn>B258-C436-B001</
</Material>
<Roles>
<string>Race</string>
<string>Mountain</string>
<string>City</string>
</Roles>
</Bike>
Record 2 :
<Bike>
<Material>
<Cage>ABCDE</Cage>
<Mpn>B258-C436-B001</Mpn>
</Material>
<Roles>
<string>Race</string>
</Roles>
</Bike>
I want to be able to find the records in my table that will contain for example Race and Mountain.
Example if I want the Ids of the record that contains 'Road'and 'Mountain" the only way I found is like this :
select Id
from BikeTable
where XmlData.exist('/Bike/Roles/string[contains(., "Road")]') = 1
or XmlData.exist('/Bike/Roles/string[contains(., "Mountain")]') = 1
I don't like this option because it forces me to generate the query if I want to find records that would match one or several roles.
Roles can contains unlimited number of values and I need to be able to find the records that will one or more values.
Ex : records containing Race, records containing Race or Montain, records containing City, records containing City and Mountain etc.
Is there any way to know if a list contains several values?
Yes, you can. This is a bit of a guess though, as you say you want to do a SELECT *; something that is impossible to provide any data for without the DDL of the table. Thus, instead, I've returned the Cage and Mpn of the Bike:
CREATE TABLE BikeTable (xmlData xml);
--The Close tag for Mpn was missing in your sample data, I assume it wasn't mean to be
INSERT INTO BikeTable
VALUES('<Bike>
<Material>
<Cage>EIECH</Cage>
<Mpn>B258-C436-B001</Mpn>
</Material>
<Roles>
<string>Race</string>
<string>Mountain</string>
<string>City</string>
</Roles>
</Bike>')
GO
WITH Bikes AS (
SELECT B.Material.value('(Cage/text())[1]','varchar(15)') AS Cage, --Data Type guessed
B.Material.value('(Mpn/text())[1]','varchar(15)') AS Mpn, --Data Type guessed
BR.String.value('(./text())[1]','varchar(15)') AS String --Data Type guessed
FROM BikeTable BT
CROSS APPLY BT.xmlData.nodes('/Bike/Material') B(Material)
CROSS APPLY BT.xmlData.nodes('/Bike/Roles/string') BR(String))
SELECT Cage, Mpn
FROM Bikes
GROUP BY Cage, Mpn
HAVING COUNT(String) > 1;
GO
DROP TABLE BikeTable;

Parse XML for a column value in a SQL query

I've set up a SQL fiddle to mimic the tables that I currently have which can be found here: http://sqlfiddle.com/#!6/7675e/5
I have 2 tables that I would like to join (Things and ThingData) which is easy enough, but I would like 1 of the columns to be coming from a value that is pulled from parsed XML in one of the columns in ThingData.
Ideally the output would look something like this:
thingID | thingValue | xmlValue
1 | aaa | a
As you can see in the fiddle, I'm able to parse a single XML string at a time, but I'm unsure of how to go from here to using the parsing stuff as a column in a join. Any help would be greatly appreciated.
I updated your SQL Fiddle to demonstrate; the number one issue you're facing is that you're not using an XML type for your XML column. That's going to cause headaches down the road as people shove crap in that column :P
http://sqlfiddle.com/#!6/4c674/2
I have made a change to your query that gets executed .
DECLARE #xml xml
SET #xml = (select thingDataXML from ThingData where thingDataID = 3)
;WITH XMLNAMESPACES(DEFAULT 'http://www.testing.org/a/b/c/d123')
SELECT
t.[thingID]
, t.[thingValue]
, CONVERT(XML,td.[thingDataXML]).value('(/anItem/a1/b1/c1/text())[1]','Varchar(1)') as xmlValue
FROM Things t
join ThingData td
on td.[thingDataID] = t.[thingDataID]
This should join from you main table into the table containing the xml and would then retreive the one value in the xml for you, note that if you would like to return multiple values for the one row you will need to either concatinate the answer or perhaps decide on a crossjoin to then use the data with the other data set to perform some logic with.
EDIT:
The answer for your question would be that both have a use case, the cross join he showed above creates an accesible table to query which uses a tiny bit more memory while the query runs but can be accessed many times, where as the normal xquery value function just reads a node which would only work to read a single value from the xml.
Cross join would be the solution if you would like to construct tabular data out of your xml.

Update SQL server xml column using XQUERY?

I have a simple xml in a XML column
<Bands>
<Band>
<Name>beatles</Name>
<Num>4</Num>
<Score>5</Score>
</Band>
<Band>
<Name>doors</Name>
<Num>4</Num>
<Score>3</Score>
</Band>
</Bands>
I have managed to update the column with :
-----just update the name to the id)----
UPDATE tbl1
SET [myXml].modify('replace value of (/Bands/Band/Name/text())[1]
with sql:column("id")')
All fine.
Question #1
How can I use this query to udpate the value to id+"lalala":
UPDATE tbl1
SET [myXml].modify('replace value of (/Bands/Band/Name/text())[1]
with sql:column("id") + "lalala"')
Error = XQuery [tbl1.myXml.modify()]: The argument of '+' must be of a single numeric primitive type
Question #1
Let's say I Don't want to update first record ([1]) , But I want to udpate (the same update as above) only where score>4.
I can write ofcourse in the xpath :
replace value of (/Bands/Band[Score>4]/Name/text())[1]
But I dont want to do it in the Xpath. Isn't there a Normal way of doing this with a Where clause ?
something like :
UPDATE tbl1
SET [myXml].modify('replace value of (/Bands/Band/Name/text())[1]
with sql:column("id") where [...score>4...]')
here is the online sql
If you want to concatenate strings you should use concat and if id in your case is an integer you need to cast it to a string in the concat function.
In the where clause you can filter rows of the table to update, you can not specify what nodes to update in the XML. That has to be done in the xquery expression. You can however use exist in the where clause to filter out the rows that really needs the update.
update tbl1
set myXml.modify('replace value of (/Bands/Band[Score > 4]/Name/text())[1]
with concat(string(sql:column("id")), "lalalala")')
where myXml.exist('/Bands/Band[Score > 4]') = 1
Q1:
;with t as (select convert(varchar(10),id) + 'lalala' id2, * from #tbl1)
UPDATE t
SET [myXml].modify('replace value of (/Bands/Band/Name/text())[1]
with sql:column("id2")');
Note: Do you realise that this updates the name of only the first band's name, not all bands?
2nd Q1:
No you cannot. Especially since (/Bands/Band[Score<4]/Name/text())[1] (I changed to <) specifically targets the doors Band in your example xml in the question. A WHERE clause on the other hand will work across the XML, instead of a particular level in the path. e.g. a very wrong interpretation:
;with t as (
select a.*, n.m.value('.','int') Score
from #tbl1 a
cross apply myXml.nodes('/Bands/Band/Score') n(m) -- assume singular
)
UPDATE t
SET [myXml].modify('replace value of (/Bands/Band/Name/text())[1]
with sql:column("id")')
where Score < 4
Because there is at least one Band in the xml with a score < 4, the XML gets updated. HOWEVER, because xml.modify only works ONCE on the first match, the first band's name gets updated, not the one matching the score filter.

How do I build XQuerys to include/exclude rows based on the presence of certain tags and attributes?

I have an auditing/logging system that uses raw XML to represent actions taken out by an application. I'd like to improve on this system greatly by using an XML column in a table in the application's SQL Server database.
Each row in the table would contain one log entry and each entry should contain one or more tags that are used to describe the action in a semantic fashion that allows me to search in ways that match the auditing needs of the application, example:
<updateInvoice id="5" userId="7" /><fieldUpdate name="InvoiceDate" /><invoice /><update />
<deleteInvoice id="5" userId="6" /><invoice /><delete />
My intention is to return rowsets from this table by specifying combinations of tags and attributes to include or exclude rows by (e.g. "Include all rows with the tag invoice but exclude rows with the attribute userId='7'", or "Include all rows with the tag invoice but exclude rows with the tag delete)
I wish to do so programatically by using combinations of a simple filter structure to represent combinations of tags and attributes that I want to cause rows to be either included or excluded.
The structure I use looks like this:
enum FilterInclusion { Include, Exclude };
public struct Filter
{
FilterInclusion Inclusion;
string TagName;
string? AttributeName;
object? AttributeValue;
}
My goal is to accept a set of these and generate a query that returns any rows that match any single inclusion filter, without matching any single exclusion filter.
Should I and can I encode this boolean logic into the resulting XPath itself, or am I looking at having multiple SELECT statements in my outputted queries? I'm new to XQuery and any help is appreciated. Thanks!
I'm not sure if that's what you're looking for, but to filter nodes in XML methods you use the brackets [ and ]. For instance to select the elements foo but filter to only those that have the attribute bar you'd use an XPath like /foo[#bar]. If you want those that have the attribute #bar with value 5 you use /foo[#bar=5]. If you want to select the elements foo that have a child element bar you use /foo[bar].
declare #t table (x xml);
insert into #t (x) values
(N'<foo bar="abc"/>');
insert into #t (x) values
(N'<foo bar="5"/>');
insert into #t (x) values
(N'<foo id="1"><bar id="2"/></foo>');
select * from #t;
select c.value(N'#bar', N'varchar(max)')
from #t cross apply x.nodes(N'/foo[#bar]') t(c)
select c.value(N'#bar', N'varchar(max)')
from #t cross apply x.nodes(N'/foo[#bar=5]') t(c)
select c.value(N'#id', N'int')
from #t cross apply x.nodes(N'/foo[bar]') t(c)
I tried to show the examples on the XML snippets in your post, but there those are too structureless to make useful examples.

Resources