Query for xml values in sql server - sql-server

I have xml field in the below table in Data column
ID | website | Data
Following is the xml field
<Product>
<field name="IsCustomer" type="System.Boolean, mscorlib">
<boolean>false</boolean>
</field>
</product>
I need to retrieve all the IsCustomer values in my table.
Following is the code part that I tried so far.
SELECT EMP.ED.value() as EmployeeID
FROM [dbo].[Products]
CROSS APPLY Data.nodes('/Product/Field[#Name="IsCustomer"]/Boolean') as EMP(ED)
Can anyone please help me?

First of all: XML is strictly case sensitive! Your XML is not even valid... The leading <Product> is another element-name then the closing </product>. As you seem to use lower letters in all places, I changed it this way.
Your own query is close, but wrong with some capital letters and you did not use the .value()-function properly (missing paramters).
Try this:
DECLARE #mockup TABLE(ID INT IDENTITY,Descr VARCHAR(100),Data XML);
INSERT INTO #mockup VALUES
('Your Sample','<product>
<field name="IsCustomer" type="System.Boolean, mscorlib">
<boolean>false</boolean>
</field>
</product>')
,('Your sample plus another field','<product>
<field name="IsCustomer" type="System.Boolean, mscorlib">
<boolean>true</boolean>
</field>
<field name="SomeOther" type="System.Boolean, mscorlib">
<boolean>true</boolean>
</field>
</product>')
,('No "IsCustomer" at all','<product>
<field name="SomeOther" type="System.Boolean, mscorlib">
<boolean>true</boolean>
</field>
</product>')
,('Two of them','<product>
<field name="IsCustomer" type="System.Boolean, mscorlib">
<boolean>true</boolean>
</field>
<field name="IsCustomer" type="System.Boolean, mscorlib">
<boolean>false</boolean>
</field>
</product>');
SELECT * FROM #mockup;
--Your query returning various variants, one of them should be okay for you:
SELECT m.ID
,m.Descr
,fld.value('(boolean/text())[1]','bit')
FROM #mockup AS m
OUTER APPLY m.Data.nodes('/product/field[#name="IsCustomer"]') AS A(fld);

I already wrote a post about XQuerying which might be helpful to you.
Note: XQuerying is case sensitive and you need to position yourself properly. Take a look at the post i linked, and if it does not help i will update this comment with a solution regarding your query
Update
To answer your question
First and foremost you need to have properly formatted XML, which means tags need to match their case sensitivity, and same rule applies when you are referencing a tag in a XQuery.
So one of the solutions would be :
DECLARE #XML as XML
SET #XML = '<Product>
<field name="IsCustomer" type="System.Boolean, mscorlib">
<boolean>false</boolean>
</field>
</Product>'
SELECT EMP.t.value('boolean[1]','varchar(20)') as EmployeeID
FROM #XML.nodes('/Product/field') as EMP(t)

Related

SQL Server : parse XML in field with unknown namespace

I'm trying to parse XML from a column to add it to a view for reports but I don't know the namespace. It may be more complicated because the fields within the XML are only created once the field is used in the base application.
The pfm.Entity table that I'm trying to parse XML from the Custom# column from is structured with the following columns:
[RootId#] ,[Id#] ,[LastId#] ,[Guid#] ,[Custom#] ,[Type#]
My goal is to parse the XML contained within Custom# into columns so that I can join the table with others on the RootId# field. Below is the select statement I'm testing with:
WITH XMLNAMESPACES ('http://www.w3.org/2001/XMLSchema' as ns)
SELECT TOP (1000)
Custom#.value('(/ns:Fields/ns:Field)[1]', 'VARCHAR(50)') AS xmlfield
FROM
[SelectDb].[pfm].[Entity]
My guess is that I'll need to be selecting with something like below but that's throwing an error on the # character.
SELECT
Custom#.value('(/ns:Fields/ns:MarketingSrcPercentage_901419#)[1]', 'VARCHAR(50)') AS mktsrcp
Below is two examples of what the XML in the field can look like. Keep in mind that there are rows where this is NULL as well.
<Fields>
<Field name="PurchaseDate_NJ#">6/11/2018</Field>
<Field name="AddOrImpDate_NJ#">8/1/2018</Field>
<Field name="AffidavitAddOrExcep_NJ#">nfui fevtt[th40thijfkrkl grwgr ijg rgmrk gmkmr pkgi</Field>
<Field name="DateOfPropertyAcquisition_DC#">8/7/2018</Field>
<Field name="AuthorizedAppointedAgent_PA#">NAME OF PERSON AUTHORIZED/APPONTED AGENT PROMPT BOX</Field>
<Field name="SpouseWaivingMaritalRights_PA#">NAME OF SPOUSE WAIVING MARTIAL RIGHTS PROMPT BOX</Field>
<Field name="PersonalFunds_TXFA#">true</Field>
<Field name="Currency_TXFA#">true</Field>
<Field name="CashiersCheck_TXFA#">true</Field>
<Field name="TravelersCheck_TXFA#">true</Field>
<Field name="MoneyOrder_TXFA#">true</Field>
<Field name="BusinessCheck_TXFA#">true</Field>
<Field name="PersonalCheck_TXFA#">true</Field>
<Field name="BankruptcyFilings_NY#">true</Field>
</Fields>
and
<Fields>
<Field name="">1</Field>
<Field name="MarketingSrcPercentage_901419#">1</Field>
<Field name="MarketingRep1Name_901419#">Brian</Field>
</Fields>
In case it's helpful, this is a database for SoftPro Select. I've looked through their documentation but I haven't found anything of help.
EDIT: Added in a clearer definition of the source table.
The XML you provide does not include any namespace? I don't know, why you think you need WITH XMLNAMESPACES at all...
Try this:
DECLARE #xml XML=
N'<Fields>
<Field name="PurchaseDate_NJ#">6/11/2018</Field>
<Field name="AddOrImpDate_NJ#">8/1/2018</Field>
<Field name="AffidavitAddOrExcep_NJ#">nfui fevtt[th40thijfkrkl grwgr ijg rgmrk gmkmr pkgi</Field>
<Field name="DateOfPropertyAcquisition_DC#">8/7/2018</Field>
<Field name="AuthorizedAppointedAgent_PA#">NAME OF PERSON AUTHORIZED/APPONTED AGENT PROMPT BOX</Field>
<Field name="SpouseWaivingMaritalRights_PA#">NAME OF SPOUSE WAIVING MARTIAL RIGHTS PROMPT BOX</Field>
<Field name="PersonalFunds_TXFA#">true</Field>
<Field name="Currency_TXFA#">true</Field>
<Field name="CashiersCheck_TXFA#">true</Field>
<Field name="TravelersCheck_TXFA#">true</Field>
<Field name="MoneyOrder_TXFA#">true</Field>
<Field name="BusinessCheck_TXFA#">true</Field>
<Field name="PersonalCheck_TXFA#">true</Field>
<Field name="BankruptcyFilings_NY#">true</Field>
</Fields>';
--Just some examples to get your named values in a type-safe way.
--The rest works the same...
SELECT #xml.value('(/Fields/Field[#name="PurchaseDate_NJ#"])[1]','date') AS [PurchaseDate_NJ#]
,#xml.value('(/Fields/Field[#name="AuthorizedAppointedAgent_PA#"])[1]','nvarchar(max)') AS [AuthorizedAppointedAgent_PA#]
,#xml.value('(/Fields/Field[#name="Currency_TXFA#"])[1]','bit') AS [Currency_TXFA#]
I found a solution for the problem but not the ideal solution which would pull all fields and sort them into columns. I'm still looking for the namespace but the below is working for now.
SELECT TOP (1000)
entity.Custom#.value('data(//Field[#name="MarketingRep1Name_901419#"])[1]','VARCHAR(100)') 'MarketingRep1'
FROM
[SelectDb].[pfm].[Entity] entity

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

SQL Server 2012 create Xml with default values from xsd

I have imported an xsd, containing 258 elements, into my SQL Server 2012 instance. It is mandatory that all 258 elements are present in the final xml. The issue I am having is that 246 of them will contain default values that are identified in the xsd and I do not how to construct my SQL to populate the xml with the default values.
The following is an example I created that illustrates my issue using a much smaller xsd:
DROP XML SCHEMA COLLECTION TestSchema
GO
CREATE XML SCHEMA COLLECTION TestSchema AS
'<schema xmlns="http://www.w3.org/2001/XMLSchema">
<element name="document">
<complexType>
<sequence>
<element minOccurs="0" name="field1" type="string" default="1" />
<element name="field2" type="int" />
</sequence>
</complexType>
</element>
</schema>'
GO
declare #xml xml(TestSchema) = null
declare #reccount table(recordcount int not null)
insert into #reccount select 32
set #xml =
(
select
recordcount as field2
from
#reccount
for xml
PATH('document')
)
select #xml
The value of #xml is:
<document>
<field2>32</field2>
</document>
Whereas i was expecting
<document>
<field1>1</field1>
<field2>32</field2>
</document>
Any ideas how I can generate the default value of field1?
Thanks in advance.
Lucky for me I came across the answer fooling around with the select statement above. I just needed to add the field 'field1' with a '' for the data.
set #xml =
(
select
'' as field1,
recordcount as field2
from
#reccount
for xml
PATH('document')
)
That did it.

Way to generically iterate through entities in XML?

Is there a generic way, in T-SQL, to iterate through an xml variable and get the xml for each entity in turn?
DECLARE #xml xml =
'<Entities>
<Entity key="4" attrib1="abc" attrib2="def" />
<Entity key="18" attrib1="ghi" attrib2="jkl" />
<Entity key="938" attrib1="mno" />
</Entities>'
For example, in the above XML, there are three instances of Entity. I want the XML for each one in turn. I can do this in a hard-coded way, e.g.
SELECT #entityxml = #xml.query('/Entities/Entity[1]')
which will return
<Entity key="4" attrib1="abc" attrib2="def" />
I can then change it to a [2] to get the next one, and so forth.
But, according to both BOL and experimentation, the parameter to #xml.query must be a string literal. In other words, a variable can't be used as the parameter, building it with [1], [2], etc., and getting however many entities there are. It appears we have hard-code the queries, which makes for some ugly code.
I know other methods can be used to get the data in the xml into a table, and then iterate through the rows in the table. I'm specifically asking about getting the XML itself, i.e. getting the equivalent of the above #xml.query, but being able to do so without having to hard-code the array value.
Thanks!
You can use position() to select elements based on index:
DECLARE #xml xml =
'<Entities>
<Entity key="4" attrib1="abc" attrib2="def" />
<Entity key="18" attrib1="ghi" attrib2="jkl" />
<Entity key="938" attrib1="mno" />
</Entities>'
declare #i int;
set #i = 2;
select #xml.query('Entities/Entity[position()=sql:variable("#i")]');
Something like this will do the trick:
DECLARE #xml xml =
'<Entities>
<Entity key="4" attrib1="abc" attrib2="def" />
<Entity key="18" attrib1="ghi" attrib2="jkl" />
<Entity key="938" attrib1="mno" />
</Entities>';
SELECT #xml.query('
for $i in //Entity
return
<Entity>
{ $i/#key }
{ $i/#attrib1 }
{ $i/#attrib2 }
</Entity>
');

SQLXML - Search and Query node element?

I have an XML like this stored in an XML datatype column (will have multiple such rows in table)-
<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Elem1 type="T1">
<Name type="string" display="First name">John</Name>
<TimeZone display="Time zone">
<DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
</TimeZone>
</Elem1>
</Root>
How can I filter based on a node element say (using SQL SERVER 2008 R2) - get all 'Elem1' nodes or get all 'Name' nodes or get all TimeZone nodes ? Something like using local-name() function ?
EDIT - Part Solution -
I got the solution partly (see John's reply below and then run this) -
SELECT C1.query('fn:local-name(.)') AS Nodes FROM [dbo].[MyXmlTable] AS MyXML CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T ( C1 )
The query above returns all the node elements across the TABLE. Now, I want to say filter upon specific elements and return the element and its value or its attribute value. How to achieve this (by using WHERE clause or any other filter mechanism)?
I'm not sure what result you are looking for but something like this perhaps.
declare #T table(XMLCol xml)
insert into #T values
('<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Elem1 type="T1">
<Name type="string" display="First name">John</Name>
<TimeZone display="Time zone">
<DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
</TimeZone>
</Elem1>
</Root>')
declare #Node varchar(50)
set #Node = 'Elem1'
select N.query('.') as Value
from #T as T
cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("#Node")]') as X(N)
Result:
<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1">
<p1:Name type="string" display="First name">John</p1:Name>
<p1:TimeZone display="Time zone">
<p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName>
</p1:TimeZone>
</p1:Elem1>
Edit
If you want the actual value instead of the entire XML you can do like this instead.
declare #Node varchar(50)
set #Node = 'TimeZone'
select N.value('.', 'varchar(100)') as Value
from #T as T
cross apply T.XMLCol.nodes('//*[local-name()=sql:variable("#Node")]') as X(N)
Result:
Value
------------------
GMT Standard Time
You can transform XML into table like here:
declare #XML xml='<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Elem1 type="T1">
<Name type="string" display="First name">John</Name>
<TimeZone display="Time zone">
<DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
</TimeZone>
</Elem1>
</Root> '
;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org'),
numbers as(
SELECT ROW_NUMBER() OVER(ORDER BY o1.object_id,o2.object_id) Num
FROM sys.objects o1 CROSS JOIN sys.objects o2),
c as(
SELECT
b.value('local-name(.)','nvarchar(1000)') Node_Name,
b.value('./text()[1]','nvarchar(1000)') Node_Value,
b.value('count(#*)','nvarchar(MAX)') AttributeCount,
Num Attribute_Number
FROM
#xml.nodes('Root//*') a(b)
CROSS APPLY Numbers
WHERE Num<=b.value('count(#*)','nvarchar(MAX)')
)
SELECT c.Node_Name,c.node_Value,Attribute_Number,
#XML.query('for $Attr in //*/.[local-name(.)=sql:column("Node_Name")]/#*[sql:column("Attribute_Number")] return local-name($Attr)').value('.','nvarchar(MAX)') Attribute_Name,
#XML.value('data(//*/.[local-name(.)=sql:column("Node_Name")]/#*[sql:column("Attribute_Number")])[1]','nvarchar(1000)') Attribute_Value
FROM c
Result:
Node_Name node_Value Attribute_Number Attribute_Name Attribute_Value
Elem1 NULL 1 type T1
Name John 1 type string
Name John 2 display First name
TimeZone NULL 1 display Time zone
DisplayName GMT Standard Time 1 type string
DisplayName GMT Standard Time 2 display Display name
Later you can query this result to get node/attribute value which do you need.
But it works only in your example, when you have only one node and all names are unique. In multinode XML you should use hierarchical numbering like '1-1-2' or something like this. It is much more complicated and i do not suggest to going this way.
It's not clear to me exactly what your output should look like. However, this should get you started:
create table MyXmlTable (MyXmlCol xml)
insert into MyXmlTable (MyXmlCol) values
(
'
<Root xmlns="http://tempuri.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Elem1 type="T1">
<Name type="string" display="First name">John</Name>
<TimeZone display="Time zone">
<DisplayName type="string" display="Display name">GMT Standard Time</DisplayName>
</TimeZone>
</Elem1>
<Elem1 type="T2">
<Name type="string" display="First name">Fred</Name>
<TimeZone display="Time zone">
<DisplayName type="string" display="Display name">EST Standard Time</DisplayName>
</TimeZone>
</Elem1>
</Root>
');
;WITH XMLNAMESPACES(DEFAULT 'http://tempuri.org')
select MyXmlCol.query('/Root/Elem1/Name')
from MyXmlTable
This queries the XML for the "Name" elements -- you can modify the query depending on what kind of output you want exactly. It's a bit long, but the MSDN article on SQLXML is pretty informative:
http://msdn.microsoft.com/en-us/library/ms345117(v=sql.90).aspx
Hope this helps!
John
Update: you can add a where clause something like this. I'm still not clear on what you want the output to look like, but this will filter out the "Elem1" values:
SELECT C1.query('fn:local-name(.)') AS Nodes
FROM [dbo].[MyXmlTable] AS MyXML
CROSS APPLY MyXML.MyXmlCol.nodes('//*') AS T ( C1 )
WHERE CAST(C1.query('fn:local-name(.)') AS NVARCHAR(32)) <> 'Elem1'
One more update; hopefully this is the answer you are looking for!
Try using a wildcard in the query. I had to use dynamic SQL because the XML query() function will only take string literals for paths (you can use sql:variable("#filter") for values, but I wasn't able to get that working for a path.)
DECLARE #filter nvarchar(20)
SET #filter = '*/Elem1'
DECLARE #sqlCommand nvarchar(1000)
SET #sqlCommand =
';WITH XMLNAMESPACES(DEFAULT ''http://tempuri.org'')
select MyXmlCol.query(''' + #filter + ''')
from MyXmlTable'
print #sqlCommand
EXECUTE sp_executesql #sqlCommand, N'#filter nvarchar(20)', #filter = #filter
This will return the Elem1 XML (and all sub-nodes):
<p1:Elem1 xmlns:p1="http://tempuri.org" type="T1">
<p1:Name type="string" display="First name">John</p1:Name>
<p1:TimeZone display="Time zone">
<p1:DisplayName type="string" display="Display name">GMT Standard Time</p1:DisplayName>
</p1:TimeZone>
</p1:Elem1>
<p2:Elem1 xmlns:p2="http://tempuri.org" type="T2">
<p2:Name type="string" display="First name">Fred</p2:Name>
<p2:TimeZone display="Time zone">
<p2:DisplayName type="string" display="Display name">EST Standard Time</p2:DisplayName>
</p2:TimeZone>
</p2:Elem1>
And if you want to pick out "TimeZone" you would do this:
SET #filter = '*/*/TimeZone'

Resources