Query ELMAH's XML field - sql-server

The stock ELMAH_Error table uses an nText field to store an Error entry. I found that by adding a field of type XML; then adding this new field to the INSERT statement of the SPROC that populates the field; i could make better use of ELMAH's output.
Now I'd like to learn how to query specific element values within that XML field. The document is structured as:
<error [...]>
<serverVariables>
<item name="ALL_HTTP">
<value string="..." />
</item>
<item name="ALL_RAW">
<value string="..." />
</item>
.
.
.
</serverVariables>
</error>
I need to be able to query the value of specific items beneath .
So I'm looking at an example from the 15seconds.com article:
SELECT MyXml.value('(/root/product[#id="304"]/name)[1]', 'nvarchar(30)')
and am trying to maps those values to my field's structure - but can't. E.g.
select top 10 RealXML.value('(/error/serverVariables[#id="REMOTE_HOST"]/name)[0]', 'nvarchar(30)')
where REMOTE_HOST is formatted:
<item name="REMOTE_HOST">
<value string="55.55.55.55" />
</item>
much appreciated

This should work:
select top 10 RealXML.value('(/error/serverVariables/item[#name="REMOTE_HOST"]/value/#string)[1]', 'nvarchar(30)')
Tested using the following:
DECLARE #xml XML = '
<error>
<serverVariables>
<item name="ALL_HTTP">
<value string="..." />
</item>
<item name="ALL_RAW">
<value string="..." />
</item>
<item name="REMOTE_HOST">
<value string="55.55.55.55" />
</item>
</serverVariables>
</error>
'
SELECT #xml.value('(/error/serverVariables/item[#name="REMOTE_HOST"]/value/#string)[1]','nvarchar(30)')

Related

Typed XML Update in SQL Server table

I have 3rd party table with XML column and I need to change the recipient value in the value if the key\string = 'recipient'. Here is a slice of the original XML with 2 items:
DECLARE #myDoc XML
SET #myDoc = '<Root>
<item>
<key>
<string>subject</string>
</key>
<value>
<anyType xmlns:q1="http://www.w3.org/2001/XMLSchema" xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="q1:string">test subject</anyType>
</value>
</item>
<item>
<key>
<string>recipient</string>
</key>
<value>
<anyType xmlns:q1="http://www.w3.org/2001/XMLSchema" xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="q1:string">prodops#company.com</anyType>
</value>
</item>
</Root>'
SET #myDoc.modify('
declare namespace q1="http://www.w3.org/2001/XMLSchema"; declare namespace p5="http://www.w3.org/2001/XMLSchema-instance";
replace value of (/q1:Root/q1:item/q1:value/p5:type="q1:string")[1]
with ("devops#company.com")
')
SELECT #myDoc
I have kind of hacked my way to this point - XML is not my strong suit. Based on BOL, I need to do something with namespace in the modify()? Right? I'm missing an if statement I am sure but just a mystery as to how to de-reference that value for recipient. Someone has done this before - I am sure. The goal is to incorporate into a post-restore script after refreshing production data to test.
Note that this fails as-is with
Msg 2337, Level 16, State 1, Line 125
XQuery [modify()]: The target of 'replace' must be at most one node, found 'xs:boolean ?'"
You need to specify text() to replace the inner text of a node. You also need to reference the anyType node, which you have not.
By the looks of things, none of the relevant nodes actually use the namespaces, so we don't need them at all.
SET #myDoc.modify('
replace value of (/Root/item[key[string[text()="recipient"]]]/value/anyType/text())[1]
with ("devops#company.com")
');
Explanation:
Descend to /Root, no namespace needed
Descend to /item[ with a predicate...
... must have key[ node ...
... which has string[ node ...
... which has inner text()="recipient"]]]
Back to the original item we descend /value then /anyType then inner /text()
All bracketed to [1] to ensure a single node
The title of the question specifies "Typed XML Update in SQL Server table".
The provided XML is not bound by any XSD schema. That's why that XML is not typed. A real typed XML is updated differently.
Please try the following solution. It has the following:
Somewhat simpler XPath predicate expression.
Using SQL variable for a new value.
All credit goes to #Charlieface.
SQL
DECLARE #myDoc XML =
N'<Root>
<item>
<key>
<string>subject</string>
</key>
<value>
<anyType xmlns:q1="http://www.w3.org/2001/XMLSchema"
xmlns:p5="http://www.w3.org/2001/XMLSchema-instance"
p5:type="q1:string">test subject</anyType>
</value>
</item>
<item>
<key>
<string>recipient</string>
</key>
<value>
<anyType xmlns:q1="http://www.w3.org/2001/XMLSchema"
xmlns:p5="http://www.w3.org/2001/XMLSchema-instance"
p5:type="q1:string">prodops#company.com</anyType>
</value>
</item>
</Root>';
DECLARE #anyType VARCHAR(30) = 'devops#company.com';
SET #myDoc.modify('
replace value of (/Root/item[key/string/text()="recipient"]/value/anyType/text())[1]
with (sql:variable("#anyType"))
');
-- test
SELECT #myDoc;

Query XML value in sql

I need to get some information from XML in SQL Server 2008, but I cannot even get basic attribute from it. All samples that I tried failed. Table name is Item, xml column name is Data.
Simplified xml looks like this:
<AnchoredXml xmlns="urn:schema:Microsoft.Rtc.Management.ScopeFramework.2008" SchemaWriteVersion="2">
<Key ScopeClass="Global">
<SchemaId Namespace="urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008" ElementName="Topology" />
<AuthorityId Class="Host" InstanceId="00000000-0000-0000-0000-000000000000" />
</Key>
<Dictionary Count="1">
<Item>
<Key />
<Value Signature="a3502dd0-8c16-4023-9eea-30ea1c7a3a2b">
<Topology xmlns="urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008">
<Services>
<Service RoleVersion="1" ServiceVersion="6" Type="Microsoft.Rtc.Management.Deploy.Internal.ServiceRoles.FileStoreService">
<ServiceId SiteId="1" RoleName="FileStore" Instance="1" />
<DependsOn />
<InstalledOn>
<ClusterId SiteId="1" Number="1" />
</InstalledOn>
<Ports xmlns="urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008" />
<FileStoreService xmlns="urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008" ShareName="lyncShare" />
</Service>
</Services>
</Topology>
</Value>
</Item>
</Dictionary>
</AnchoredXml>
I need to read information in AnchoredXml/Key/SchemaId/#NameSpace to select the right xml (there are more rows). Sample xml above is the right one. And after that I need to find the right service with
Type="Microsoft.Rtc.Management.Deploy.Internal.ServiceRoles.FileStoreService"
where is FileStoreService/#ShareName that I need.
I've tried to print the Namespace attributte for the start, but no sample code is working.
A few tries:
SELECT c.p.value('(#Namespace)[1]', 'varchar(50)') as 'Nmspace'
FROM Item
CROSS APPLY Data.nodes('/AnchoredXml/Key/SchemaId') c(p)
returns empty result set
SELECT Data.value('(/AnchoredXml/Key/SchemaId/#Namespace)[1]', 'varchar(50)')
FROM Item
returns NULL for all rows
SELECT
It.Data.exist('/AnchoredXml/Key/SchemaId[#Namespace="Microsoft.Rtc.Management.Deploy.Topology.2008"]')
FROM [xds].[dbo].[Item] AS It
returns 0's for all rows also without quotes ("")
A working sample code to get at least attribute test would be maybe sufficient and I would figure out the rest.
Could you please help me find errors in my queries or maybe identify some other problem?
Thanks
You're ignoring all the XML namespaces in your XML document! You need to pay attention to those and respect them!
There are XML namespaces on:
the root node <AnchoredXml>
(XML namespace: urn:schema:Microsoft.Rtc.Management.ScopeFramework.2008)
the subnode <Topology>
(XML ns: urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008)
the subnode <FileStoreService>
(XML ns: urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008)
Try this:
-- respect the XML namespaces!!
;WITH XMLNAMESPACES(DEFAULT 'urn:schema:Microsoft.Rtc.Management.ScopeFramework.2008',
'urn:schema:Microsoft.Rtc.Management.Deploy.Topology.2008' AS t,
'urn:schema:Microsoft.Rtc.Management.Deploy.ServiceRoles.2008' AS fss)
SELECT
ShareName = Data.value('(/AnchoredXml/Dictionary/Item/Value/t:Topology/t:Services/t:Service/fss:FileStoreService/#ShareName)[1]', 'varchar(50)')
FROM
dbo.Item
In my case, this returns:
ShareName
-----------
lyncShare

How to eliminate superfluous namespace declarations in SQL generated XML?

I'm fine tuning a web app that calls SOAP services backed by SQL stored procedure calls. Typically the stored procs generate XML that becomes part of the SOAP response, and that XML has many superfluous xmlns namespace declarations. In pathological cases this can be 30% or more of the char encoded XML measured in bytes, e.g.:
<GetFooResponse xmlns="http://service.url/">
<GetFooResult>
<FooItems xmlns="http://www.thisisalongishurl.com/schema12345/version12345">
<Item xmlns="http://www.thisisalongishurl.com/schema12345/version12345" data="foo" />
<Item xmlns="http://www.thisisalongishurl.com/schema12345/version12345" data="foo" />
<Item xmlns="http://www.thisisalongishurl.com/schema12345/version12345" data="foo" />
<Item xmlns="http://www.thisisalongishurl.com/schema12345/version12345" data="foo" />
<Item xmlns="http://www.thisisalongishurl.com/schema12345/version12345" data="foo" />
</FooItems >
</GetFooResult>
</GetFooResponse>
The SQL I'm using to generate XML typically follows this pattern:
WITH XMLNAMESPACES(DEFAULT 'http://www.thisisalongishurl.com/schema12345/version12345')
SELECT
[Name],
[Value]
FROM
[Foo]
FOR XML PATH('Item'),
TYPE,
ROOT('FooItems');
Is there any way to avoid the generation of superfluous xmlns namespace declations on each Item XML element?
thanks.
WITH XMLNAMESPACES(
'http://www.thisisalongishurl.com/schema12345/version12345' AS short,
DEFAULT 'http://service.url/'
),
[Foo] ([short:Name], [short:Value]) AS
(
SELECT 'testName', 'testValue'
)
SELECT *
FROM [Foo]
FOR XML PATH('Item'),
TYPE,
ROOT('FooItems')
returns
<FooItems xmlns="http://service.url/" xmlns:short="http://www.thisisalongishurl.com/schema12345/version12345">
<Item>
<short:Name>testName</short:Name>
<short:Value>testValue</short:Value>
</Item>
</FooItems>

XPath to fetch SQL XML value

Here is my problem: from the following XML that is within a column, I want to know if the value of a variable with the name 'Enabled' is equal to 'Yes' given a step Id and a component Id.
'<xml>
<box stepId="1">
<components>
<component id="2">
<variables>
<variable id="3" nom="Server" valeur="DEV1" />
<variable id="4" nom="Enabled" valeur="Yes" />
</variables>
</component>
<component id="3">
<variables>
<variable id="3" nom="Server" valeur="DEV1" />
<variable id="4" nom="Enabled" valeur="No" />
</variables>
</component>
</components>
</box>
<box stepId="2">
<components>
<component id="2">
<variables>
<variable id="3" nom="Server" valeur="DEV2" />
<variable id="4" nom="Enabled" valeur="Yes" />
</variables>
</component>
<component id="3">
<variables>
<variable id="3" nom="Server" valeur="DEV2" />
<variable id="4" nom="Enabled" valeur="No" />
</variables>
</component>
</components>
</box>
</xml>'
XQuery Against the xml Data Type
General XQuery Use Cases
XQueries Involving Hierarchy
XQueries Involving Order
Anything in Michael Rys blog
Update
My recomendation would be to shred the XML into relations and do searches and joins on the resulted relation, in a set oriented fashion, rather than the procedural fashion of searching specific nodes in the XML. Here is a simple XML query that shreds out the nodes and attributes of interest:
select x.value(N'../../../../#stepId', N'int') as StepID
, x.value(N'../../#id', N'int') as ComponentID
, x.value(N'#nom',N'nvarchar(100)') as Nom
, x.value(N'#valeur', N'nvarchar(100)') as Valeur
from #x.nodes(N'/xml/box/components/component/variables/variable') t(x)
However, if you must use an XPath that retrieves exactly the value of interest:
select x.value(N'#valeur', N'nvarchar(100)') as Valeur
from #x.nodes(N'/xml/box[#stepId=sql:variable("#stepID")]/
components/component[#id = sql:variable("#componentID")]/
variables/variable[#nom="Enabled"]') t(x)
If the stepID and component ID are columns, not variables, the you should use sql:column() instead of sql:variable in the XPath filters. See Binding Relational Data Inside XML Data.
And finaly if all you need is to check for existance you can use the exist() XML method:
select #x.exist(
N'/xml/box[#stepId=sql:variable("#stepID")]/
components/component[#id = sql:variable("#componentID")]/
variables/variable[#nom="Enabled" and #valeur="Yes"]')
I always go back to this article SQL Server 2005 XQuery and XML-DML - Part 1 to know how to use the XML features in SQL Server 2005.
For basic XPath know-how, I'd recommend the W3Schools tutorial.
I think the xpath query you want goes something like this:
/xml/box[#stepId="$stepId"]/components/component[#id="$componentId"]/variables/variable[#nom="Enabled" and #valeur="Yes"]
This should get you the variables that are named "Enabled" with a value of "Yes" for the specified $stepId and $componentId. This is assuming that your xml starts with an tag like you show, and not
If the SQL Server 2005 XPath stuff is pretty straightforward (I've never used it), then the above query should work. Otherwise, someone else may have to help you with that.

XML question in SQL Server

In one of my sql scripts, I need to execute a stored procedure with the following xml string
<Collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Field>
<Attributes>
<Attribute Name="CODE1" IsRequired="true" Order="1" IsVisible="true"/>
<Attribute Name="CODE2" IsRequired="true" Order="2" IsVisible="true"/>
</Attributes>
<Rows>
<Row ProductState="5">
<Items>
<Item Name="PROD1" SendCustomer="false"/>
<Item Name="PROD2" SendCustomer="false"/>
</Items>
</Row>
</Rows>
</Field>
</Collection>
I get the Attribute and the Item information from different tables. I am writing a generic function in which you pass an ID and returns this XML string that is used by the SQL script to execute the stored procedure
Sometimes, I need to override the attribute values of some elements like SendCustomer. My initial thought was to deserialize this to a temp table, update the temp table with the override value and then serialize it back to XML.
So, essentially, the entire process boils down to:
Query tables, serialize to XML in the function
Deserialze XML, store in temp table
Override values if necessary
Serialze from table to XML again
Is there a more elegant way in sql server 2005 to do this entire process?
The XML datatype actually can be modified using XQuery. See the modify() method.
declare #x XML;
select #x = N'<Collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Field>
<Attributes>
<Attribute Name="CODE1" IsRequired="true" Order="1" IsVisible="true"/>
<Attribute Name="CODE2" IsRequired="true" Order="2" IsVisible="true"/>
</Attributes>
<Rows>
<Row ProductState="5">
<Items>
<Item Name="PROD1" SendCustomer="false"/>
<Item Name="PROD2" SendCustomer="false"/>
</Items>
</Row>
</Rows>
</Field>
</Collection>';
set #x.modify(N'replace value of
(/Collection/Field/Rows/Row/Items/Item[#Name="PROD2"]/#SendCustomer)[1]
with "true"');
select #x;

Resources