SQL Query Column with XML Data - sql

I have the following data in a column, how can I query the values inside.
<deliveryPart notificationId="12345">
<message address="email#email.com" content="multipart/alternative" domain="email.com" format="0" id="159436637" recipient-id="098765" targetCode="__MAIN__">
<custom>
<recipient Nickname="mynickname" id="54321" />
<targetData Incident_Id="1509403" reference_nb="0000-0000" />
</custom>
</message>
</deliveryPart>
I gave it a quick go but I've never done it before and I am in short of time.
select top 10 *
from [db].[db].[table]
where mData.value('(deliveryPart/message/recipient-id)[1]','varchar(max)') like '098765'
I get the following error
Msg 4121, Level 16, State 1, Line 1
Cannot find either column "mData" or the user-defined function or aggregate "mData.value", or the name is ambiguous.
UPDATE
I am using the following code to fetch the xml values and it works
SELECT TOP 1000 B1.[mData].value('(deliveryPart/message/#id)[1]', 'NVARCHAR(MAX)') AS 'MessageID', B1.[mData].value('(deliveryPart/message/#address)[1]', 'NVARCHAR(MAX)') AS 'Address'
FROM (SELECT CAST(mData as XML) as xmlData FROM [dbo].[db].[table]) B0
CROSS APPLY xmlData.nodes('/') B1 (mData)
WHERE B1.[mData].value('(deliveryPart/message/#address)[1]', 'NVARCHAR(MAX)') LIKE '%#%'
And it returns the xml values store in the ntext field just fine.
180646774 email1#email.com
159436627 test2#hotmail.com
159436637 test3#hotmail.com
However, I need to fetch values from outside the mData column and is not letting me do it.

You need to use the #recipient-id as a XML attribute - not XML element:
<message address="email#email.com" content="multipart/alternative"
domain="email.com" format="0" id="159436637"
recipient-id="098765" targetCode="__MAIN__">
************ this is an *attribute* - use the `#` to select it!
Code:
select top 10 *
from [db].[db].[table]
where mData.value('(deliveryPart/message/#recipient-id)[1]', 'varchar(max)') like '098765'

Related

Is there any way to find out specific tag values from XML?

I am setting up alert using extended event in which I am pulling out info in XML format so I got stuck in finding out the values - Object name from this XML.
SELECT CAST(data AS XML) AS [result]
FROM #temp
WHERE data LIKE '%<text>Abort</text>%'
Using this query, I have pulled out those records which gets time out in XML format and through this xml, we need to pull XYZ value as object name using T-SQL <value>XYZ</value></data>
Output of above select query:
<event name="rpc_completed" package="sqlserver" timestamp="2019-02-20T14:42:39.678Z"><data name="cpu_time"><value>15000</value></data><data name="duration"><value>29999325</value></data><data name="physical_reads"><value>0</value></data><data name="logical_reads"><value>363</value></data><data name="writes"><value>0</value></data><data name="result"><value>2</value><text>Abort</text></data><data name="row_count"><value>9</value></data><data name="connection_reset_option"><value>0</value><text>None</text></data><data name="object_name"><value>XYZ</value></data><data name="statement"><value>exec XYZ </value></data><data name="data_stream"><value /></data><data name="output_parameters"><value /></data><action name="transaction_id" package="sqlserver"><value>0</value></action><action name="session_id" package="sqlserver"><value>1381</value></action><action name="server_principal_name" package="sqlserver"><value>sq</value></action><action name="database_name" package="sqlserver"><value>PR</value></action><action name="database_id" package="sqlserver"><value>5</value></action><action name="client_pid" package="sqlserver"><value>32048</value></action><action name="client_hostname" package="sqlserver"><value>RuntimeHost</value></action><action name="client_app_name" package="sqlserver"><value>test</value></action><action name="event_sequence" package="package0"><value>133050</value></action></event>
NA
Output should be like this:
Object Name
XYZ
You can use nodes to filter the items inside your xml by attribute value and then value to extract the data you need:
;with x as(
SELECT CAST(data AS XML) AS [result]
FROM #temp
WHERE data LIKE '%<text>Abort</text>%'
)
select
t.s.value('.', 'nvarchar(max)') as object_name
from
x
cross apply
[result].nodes('//data[#name = "object_name"]/value') t(s)
Result:
Edit
One approach to retrieve database_name is adding another nodes filtering on action tags. To get the timestamp you can just add a value in the select clause specifying the correct xpath expression:
;with x as(
SELECT CAST(data AS XML) AS [result]
FROM #temp
WHERE data LIKE '%<text>Abort</text>%'
)
select
t.s.value('.', 'nvarchar(max)') as [object_name]
, u.s.value('.', 'nvarchar(max)') as [database_name]
, [result].value('(/event/#timestamp)[1]', 'nvarchar(max)') as [timestamp]
from
x
cross apply
[result].nodes('//data[#name = "object_name"]/value') t(s)
cross apply
[result].nodes('//action[#name = "database_name"]/value') u(s)
Results with database_name and timestamp:

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

Parsing nested XML into SQL table

What would be the right way to parse the following XML block into SQL Server table according to desired layout (below)? Is it possible to do it with a single SELECT statement, without UNION or a loop? Any takers? Thanks in advance.
Input XML:
<ObjectData>
<Parameter1>some value</Parameter1>
<Parameter2>other value</Parameter2>
<Dates>
<dateTime>2011-02-01T00:00:00</dateTime>
<dateTime>2011-03-01T00:00:00</dateTime>
<dateTime>2011-04-01T00:00:00</dateTime>
</Dates>
<Values>
<double>0.019974</double>
<double>0.005395</double>
<double>0.004854</double>
</Values>
<Description>
<string>this is row 1</string>
<string>this is row 2</string>
<string>this is row 3</string>
</Values>
</ObjectData>
Desired table output:
Parameter1 Parameter2 Dates Values Description
Some value Other value 2011-02-01 00:00:00.0 0.019974 this is row 1
Some value Other value 2011-03-01 00:00:00.0 0.005395 this is row 2
Some value Other value 2011-04-01 00:00:00.0 0.004854 this is row 3
I am after an SELECT SQL statement using OPENXML or xml.nodes() functionality. For example, the following SELECT statement results in production between Values and Dates (that is all permutations of Values and Dates), which is something I want to avoid.
SELECT
doc.col.value('Parameter1[1]', 'varchar(20)') Parameter1,
doc.col.value('Parameter2[1]', 'varchar(20)') Parameter2,
doc1.col.value('.', 'datetime') Dates ,
doc2.col.value('.', 'float') [Values]
FROM
#xml.nodes('/ObjectData') doc(col),
#xml.nodes('/ObjectData/Dates/dateTime') doc1(col),
#xml.nodes('/ObjectData/Values/double') doc2(col);
You can make use of a numbers table to pick the first, second, third etc row from the child elements. In this query I have limited the rows returned to the number if dates provided. If there are more values or descriptions than dates you have to modify the join to take that into account.
declare #XML xml = '
<ObjectData>
<Parameter1>some value</Parameter1>
<Parameter2>other value</Parameter2>
<Dates>
<dateTime>2011-02-01T00:00:00</dateTime>
<dateTime>2011-03-01T00:00:00</dateTime>
<dateTime>2011-04-01T00:00:00</dateTime>
</Dates>
<Values>
<double>0.019974</double>
<double>0.005395</double>
<double>0.004854</double>
</Values>
<Description>
<string>this is row 1</string>
<string>this is row 2</string>
<string>this is row 3</string>
</Description>
</ObjectData>'
;with Numbers as
(
select number
from master..spt_values
where type = 'P'
)
select T.N.value('Parameter1[1]', 'varchar(50)') as Parameter1,
T.N.value('Parameter2[1]', 'varchar(50)') as Parameter2,
T.N.value('(Dates/dateTime[position()=sql:column("N.Number")])[1]', 'datetime') as Dates,
T.N.value('(Values/double[position()=sql:column("N.Number")])[1]', 'float') as [Values],
T.N.value('(Description/string[position()=sql:column("N.Number")])[1]', 'varchar(max)') as [Description]
from #XML.nodes('/ObjectData') as T(N)
cross join Numbers as N
where N.number between 1 and (T.N.value('count(Dates/dateTime)', 'int'))
Use the OPENXML function. It is a rowset provider (it returns the set of rows parsed from the XML) and thus can be utilized in SELECT or INSERT like:
INSERT INTO table SELECT * FROM OPENXML(source, rowpattern, flags)
Please see the first example in the documentation link for clarity.
Typically, if you wanted to parse XML, you'd do it a programming language like Perl, Python, Java or C# that a) has an XML DOM, and b) can communicate with a relational database.
Here's a short article that shows you some of the basics of reading and writing XML in C# ... and even has an example of how to create an XML document from a SQL query (in one line!):
http://www.c-sharpcorner.com/uploadfile/mahesh/readwritexmltutmellli2111282005041517am/readwritexmltutmellli21.aspx

Updating XML attribute in SQL Server XML column

I am trying to update a node of in my XML which is stored in a SQL Server XML column, the line below works if my XML is in a XML element but now I somehow need to change it to XML attributes, apparently the line becomes invalid after the change.
Works for XMLElement:
UPDATE [Customers]
SET voucherXML.modify('replace value of (/ArrayOfCampaignVoucher/CampaignVoucher/Qty/text())[1] with "50"')
WHERE voucherXML.value('(/ArrayOfCampaignVoucher/CampaignVoucher/VouCode)[1]', 'nvarchar(50)') = #VoucherCode
I tried changing the statement like this but no luck, no errors but QTY values doesn't get change to the value of #NewQuantity:
UPDATE [Customers]
SET voucherXML='<ArrayOfCampaignVoucher xmlns:xsd="http://www.w3.org/2001/XMLSchema" Qty="' + CAST(#NewQuantity AS NVARCHAR(16)) + '" />'
WHERE voucherXML.value('(/CampaignVoucher/VouCode)[1]', 'nvarchar(50)') = #VoucherCode
This is how my XML looks in the SQL Server XML column:
<ArrayOfCampaignVoucher xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CampaignVoucher VouCode="Vouc001" Qty="16" />
<CampaignVoucher VouCode="Vouc002" Qty="18" />
<CampaignVoucher xsi:nil="true" />
</ArrayOfCampaignVoucher>
You should use the XQuery functions - not string together your XML like this....
Try this instead:
DECLARE #newquantity INT = 55
UPDATE dbo.Customers
SET voucherXML.modify('replace value of (/ArrayOfCampaignVoucher/CampaignVoucher[#VouCode="Vouc002"]/#Qty)[1] with sql:variable("#NewQuantity") ')
This works for me - the Qty attribute of the node with VouCode="Vouc002" gets updated to the value I defined before in a SQL variable
Update: to insert a new <CampaignVoucher> entry, use XQuery something like this:
UPDATE dbo.Customers
SET voucherXML.modify('insert <CampaignVoucher VouCode="Vouc003" Qty="42" />
as last into (/ArrayOfCampaignVoucher)[1]')

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