SQL WHERE clause with multiple XML attributes - sql

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...

Related

Importing XML to SQL Server

I am wondering how I can insert an XML file into a SQL Server DB. Below is the XML I have but I am unsure how to do this in a way that will scale. My thought is a Insert Into Select statement but I do not know if that is going to work as the data increases. Thank you in advance!
<Records>
<Record>
<ID SpecNum="5069580" IssueNum="001" SpecStatus="Pre-Approved">
<NutritionDetails>
<NutrientFacts>
<NutrientNameId>ENERC_KCAL</NutrientNameId>
<NutrientName>ENERC_KCAL</NutrientName>
<NutrientPer100gUnrounded>1.91</NutrientPer100gUnrounded>
<NutrientPer100gRounded>191</NutrientPer100gRounded>
</NutrientFacts>
</NutritionDetails>
</ID>
</Record>
</Records>
Once you've successfully created a proper, valid XML - you should be able to use this T-SQL code to grab the details:
SELECT
-- get the attributes from the <ID> node
IDSpecNum = XC.value('(ID/#SpecNum)[1]', 'int'),
IDIsseNum = XC.value('(ID/#IssueNum)[1]', 'int'),
IDSpecStatus = XC.value('(ID/#SpecStatus)[1]', 'varchar(100)'),
-- get the element values from the children of the <NutrientFacts> node
NutrientNameId = NUT.value('(NutrientNameId)[1]', 'varchar(100)'),
NutrientName = NUT.value('(NutrientName)[1]', 'varchar(100)'),
NutrientPer100gUnrounded = NUT.value('(NutrientPer100gUnrounded)[1]', 'decimal(20,4)'),
NutrientPer100gRounded = NUT.value('(NutrientPer100gRounded)[1]', 'decimal(20,4)')
FROM
dbo.YourTable
CROSS APPLY
-- get one XML fragment per <Record>
XmlData.nodes('/Records/Record') AS XT(XC)
CROSS APPLY
-- get one XML fragment per <NutrientFacts> inside
XC.nodes('ID/NutritionDetails/NutrientFacts') AS XT2(NUT)
The first CROSS APPLY basically get an "inline pseudo table" with one XML fragment for each <Record> node in your XML in the XmlData column of your table (this is just an assumption on my part - adapt to your reality!). These XML fragments are referenced as "pseudo-table" XT with a single column XC.
With that XC column's XML fragment, you can "reach in" and grab the attribute values from the <ID> node in the <Record> - that's the first three values.
Then, based on the XT pseudo table, I apply another CROSS APPLY to get all the <NutrientFacts> nodes inside <ID> / <NutritionDetails> - those are referenced as pseudo-table XT2 with column NUT, which again holds an XML fragment for each <NutrientFacts> node; I reach into that XML node and extract the values from the sub-elements of that node - those are the four additional values that are shown in the select.
Now that you have a SELECT that returns all the values - you can easily get those bits you need and use them in a INSERT INTO dbo.MyTable(list-of-columns) SELECT list-of-columns :...... scenario. Enjoy!
UPDATE: to import an XML file from disk (local disk on your SQL Server machine's file system) into your table - use something like this:
INSERT INTO dbo.YourTable(XmlData)
SELECT
CONVERT(XML, BulkColumn) AS BulkColumn
FROM
OPENROWSET(BULK 'C:\temp\records.xml', SINGLE_BLOB) AS x;
Again: adapt to your needs - I don't know if you want to insert additional information into dbo.YourTable - and I don't even know your table name; you can load one XML at a time from disk

Insert XML element with attribute into SQL XML column

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...

Flattening xml data in sql

I'm trying to flatten XML data in a SQL query but I always seem to get nulls.
I tried the cross/outer apply method described here.
The column with XML data is called Data.
I'm guessing that the xml data with these links need to be somehow also added?
Could you please help to get a proper SQL query?
Query I tried:
SELECT
v.name
,pref.value('(LocalId/text())[1]', 'nvarchar(10)') as localid
FROM [database].[requests] v
outer apply v.Data.nodes('/DataForm') x(pref)
GO
example of xml data in that column:
<Dataform xmlns="http://somelongasslink.org/hasalsosomestuffhere" xmlns:i="http://somexlmschemalink/">
<DeleteDate xmlns="http://somelongasslink.org/hasalsosomestuffhere" i:nil="true" />
<LocalId xmlns="http://somelongasslink.org/hasalsosomestuffhere">5325325</LocalId>
...
You can use this code to get the result you're looking for:
;WITH XMLNAMESPACES(DEFAULT 'http://somelongasslink.org/hasalsosomestuffhere')
SELECT
rq.Name,
LocalID = TC.value('(LocalId)[1]', 'nvarchar(10)')
FROM
[database].[requests] rq
CROSS APPLY
rq.Data.nodes('/Dataform') AS TX(TC)
GO
There were two problems with your code:
you're not respecting / including the XML namespace that's defined on the XML document
<Dataform xmlns="http://somelongasslink.org/hasalsosomestuffhere"
*******************************************************
you didn't pay attention to the case-sensitivity of XML in your call to .nodes() - you need to use .nodes('/Dataform') (not /DataForm - the F is not capitalized in your XML)

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

Extracting values from XML field with unusual XML using SQL

Hoping someone can help -
The XML format was put together with a very simple syntax, but for some reason I'm struggling to parse it using a standard 'value' type query.
I'm experienced with SQL, but only have limited experience in XML, and after 2 hours of frustration and much Googling, I thought I'd ask for my own sanity!
The data is stored as a text string, so converting it to XML before parsing:
<!-- Data config file -->
<Config>
<!-- keys-->
<foo value="bar"/>
<foo1 value="bar1"/>
<big_foo value="bar/bar.com"/>
<other value="f00"/>
The query I'm using is:
SELECT
col.value('foo1[0]', 'nvarchar(max)') as VALUE
from
(
select
CAST((SELECT TOP 1 xml_text FROM dbo.xml_lookup)AS XML)
as Col)x
but this returns NULL rather than the expected "bar1".
Any idea where I'm going wrong?
proper XPath would be
col.value('(Config/foo1)[1]/#value', 'nvarchar(max)')
sql fiddle demo