I shred XML by assigning an xml column value to a xml variable and I then do the following:
select s.property.value('#name[1]', 'varchar(100)') as name1
,s.property.value('#value[1]', 'varchar(100)') as value1
from #s1.nodes('//properties/property') as s(property)
where #s1 is the variable.
How can I use this syntax directly against an XML column in a table (2008 R2)?
You need to cross apply, then select out the values:
create table #xmltable (
x xml
)
insert #xmltable (x) values ('<xml><properties><property name="name1" value="value1" /><property name="name2" value="value2" /></properties></xml>')
select p.value('#name', 'varchar(100)') as name,
p.value('#value', 'varchar(100)') as value
from #xmltable xt
cross apply xt.x.nodes('//properties/property') t(p)
drop table #xmltable
Related
I have ugly XML that looks like this:
<?xml version="1.0"?>
<MainTag xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RecordID="201801026543210">
<Field Name="TheFieldName">
<FieldValue>Field Contents</FieldValue>
<ListTag>
<ListItem>
<Value>This List Value</Value>
<Source>source.txt</Source>
<ListType>text</ListType>
<ItemNumber>6912</ItemNumber>
<MoreData>some text here</MoreData>
<Address>address data</Address>
<Ranking>102</Ranking>
</ListItem>
<ListItem>
<Value>Another List Value</Value>
<Source>other.txt</Source>
<ListType>text</ListType>
<ItemNumber>7919</ItemNumber>
<MoreData>more text here</MoreData>
<Address>address data</Address>
<Ranking>41</Ranking>
</ListItem>
</ListTag>
</Field>
</MainTag>
What I want is query results that gives me a spreadsheet, essentially:
RecordID FieldName FieldValue ListValue ListSource ListType ListItemNumber …
201801026543210 TheFieldName Field Contents This List Value source.txt text 6912
201801026543210 TheFieldName Field Contents Another List Value other.txt text 7919
To aid in the fun, the XML is stored in a varchar field, not an XML field.
As example data and queries I've tried:
DECLARE #Tbl TABLE (
TblID varchar(15)
, Fld varchar(max)
)
INSERT INTO #Tbl SELECT '201801026543210', '<?xml version="1.0"?><MainTag xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RecordID="201801026543210"><Field Name="TheFieldName"><FieldValue>Field Contents</FieldValue><ListTag><ListItem><Value>This List Value</Value><Source>source.txt</Source><ListType>text</ListType><ItemNumber>6912</ItemNumber><MoreData>some text here</MoreData><Address>address data</Address><Ranking>102</Ranking></ListItem><ListItem><Value>Another List Value</Value><Source>other.txt</Source><ListType>text</ListType><ItemNumber>7919</ItemNumber><MoreData>more text here</MoreData><Address>address data</Address><Ranking>41</Ranking></ListItem></ListTag></Field></MainTag>'
-- this shows that i am reading the main tag
SELECT t.r.value('#RecordID','varchar(15)') AS RecordID
, t.r.query('.') as fullvalue
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag') AS t(r)
-- and this works to read the attribute from the first field
SELECT t.r.value('#RecordID', 'varchar(18)') AS RecordID
, f.r.value('#Name', 'varchar(100)') AS Field
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag') AS t(r)
CROSS APPLY Fld.nodes('/MainTag/Field') AS f(r)
-- this does NOT work to read the second field
SELECT t.r.value('#RecordID','varchar(18)') AS RecordID
, f.r.value('.', 'varchar(100)') AS ValueF
, p.r.query('.') AS QueryP
, p.r.value('.', 'varchar(100)') AS ValueP
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag') AS t(r)
CROSS APPLY Fld.nodes('/MainTag/Field') AS f(r)
CROSS APPLY Fld.nodes('/MainTag/FieldValue') AS p(r)
-- i honestly feel like this should be using nodes off of the parent nodes method
-- , but this is NOT working either
SELECT t.r.value('#RecordID','varchar(18)') AS RecordID
, p.r.query('.') AS FieldValue
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY s.Fld.nodes('/MainTag') AS t(r)
CROSS APPLY t.r.nodes('/MainTag/FieldValue') AS p(r)
I've seen some other questions/samples that grab just the first entry. I do not want that. I want a big blob of data. I want the unique fields to repeat for each "ListItem". To say that another way: I know this is akin to a one-to-many join where the fields in the "one" table will repeat for each row in the "many" table.
Technically, in my data there may be more than one "Field" as well.
There is only one FieldValue for that field.
There is one or more ListItems.
Try it with this query:
SELECT s.Fld.value('(/MainTag/#RecordID)[1]','bigint') AS RecordID
,A.f.value('#Name','nvarchar(max)') as FieldName
,A.f.value('(FieldValue/text())[1]','nvarchar(max)') as FieldValue
,B.li.value('(Value/text())[1]','nvarchar(max)') as ListValue
,B.li.value('(Source/text())[1]','nvarchar(max)') as ListSource
--and so on
FROM (
SELECT top 10 CONVERT(xml, Fld) AS Fld, TblID
FROM #Tbl
) AS s
CROSS APPLY Fld.nodes('/MainTag/Field') AS A(f)
CROSS APPLY A.f.nodes('ListTag/ListItem') AS B(li)
Hint
If there is any chance to change storage from VARCHAR to XML it's worth it...
I hate dealing with XML queries. I don't do it often enough to remember how to format everything, but I'm at my wits' end.
Let's say I have a table called Messages and a column inside it called Payload. Payload contains XML stored as varchar(max). The XML is formatted as such:
<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>
How do I query the table to retrieve a list of the values in the VID node?
Try this:
declare #w as xml = cast('
<NCOAPACP xmlns:i=''http://www.w3.org/2001/XMLSchema-instance''
xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>' as xml)
SELECT #w.value('declare namespace ns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM";
ns:NCOAPACP[1]/ns:VID[1]','varchar(10)') AS VID
If you have multiple VID node you can try this:
declare #w as xml = cast('
<NCOAPACP xmlns:i=''http://www.w3.org/2001/XMLSchema-instance''
xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
<VID>454545</VID>
</NCOAPACP>' as xml)
SELECT VID.value('.','varchar(10)')
from #w.nodes(
'declare namespace ns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM";
ns:NCOAPACP[1]/ns:VID'
)
AS C(VID)
If XML data are in a table column then the solution is a little more tricky. Especially if data type is varchar(max).
declare #tbl table(id int,payload varchar(max))--sample table
--sample data
insert #tbl values
(1,'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
<VID>3656184</VID>
</NCOAPACP>'),
(2,'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>123456</VID>
<VID>987654</VID>
</NCOAPACP>')
--it is convenient to define namespaces before the query
;with xmlnamespaces('uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM' as x)--we will use this x:
select id, t.vid.value('.[1]','varchar(20)') vid
-- . means self. [1] ensure single value
from
--convert varchar(max) to xml first
(select id,cast(payload as xml) payload from #tbl) tt
cross apply
--convert xml to a tabular form
tt.payload.nodes('//x:VID') t(vid)
Results:
id vid
1 3656183
1 3656184
2 123456
2 987654
U can try this
DECLARE #Messages TABLE(ID INT, Payload NVARCHAR(MAX))
INSERT INTO #Messages(ID, Payload) VALUES(1, '<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"> <VID>3656183</VID> </NCOAPACP>')
INSERT INTO #Messages(ID, Payload) VALUES(2, '<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"> <VID>3656183</VID> <VID>3656184</VID> </NCOAPACP>')
;WITH CTE(ID, Payload) AS
(
SELECT ID, CAST(Payload AS XML)
FROM #Messages
)
SELECT
ID,
x.items.value('.', 'NVARCHAR(10)')
FROM CTE
CROSS APPLY Payload.nodes('declare namespace xx="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"; xx:NCOAPACP/xx:VID') as x(items)
The most simple way (CTE test created just to get output, your xml value converted to varchar(max) as in your table, then SELECT *.value):
;WITH test AS (
SELECT CAST(
'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>' as varchar(max)) as Payload
)
SELECT CAST(Payload as xml).value('(/*/*)[1]', 'int')
FROM test
Output:
3656183
I'm trying to figure out how to get an XML attributes value when the element name changes. The attribute will be the same regardless of the element.
<obj1 id="1" name="sally" />
<obj2 id="15" date="yesterday" />
I've been trying various forms of this, but it's not working:
SELECT
[OriginalRecordXml].value('(/./id)[1]', 'varchar(MAX)')
FROM [AuditRecords]
Is this possible?
Something like this does the trick:
declare #t table (
Id int identity(1,1) primary key,
XMLData xml not null
);
insert into #t (XMLData)
values (N'<obj1 id="1" name="sally" />
<obj2 id="15" date="yesterday" />'),
(N'<objM />');
select t.Id, x.c.value('./#id', 'varchar(max)')
from #t t
cross apply t.XMLData.nodes('//*[#id]') x(c);
Or, you can save a little bit if you only need value from first / single node:
select t.Id, t.XMLData.value('/*[#id][1]/#id', 'varchar(max)')
from #t t;
This is my sample XML:
<root>
<element>
<subelement>
<value code="code1">value1</value>
<value code="code2">value2</value>
</subelement>
</element>
</root>
And this is my test query:
DECLARE #tempTable TABLE (
ValueCode nvarchar(MAX),
Value nvarchar(MAX)
)
DECLARE #xml XML
select #xml = cast(c1 as xml) from OPENROWSET (BULK 'C:\test.xml', SINGLE_BLOB) as T1(c1)
INSERT INTO #tempTable
SELECT
Tbl.Col.value('subelement[1]/#code', 'NVARCHAR(MAX)'),
Tbl.Col.value('subelement[1]', 'NVARCHAR(MAX)')
FROM #xml.nodes('//element') Tbl(Col)
SELECT * FROM #tempTable
The query, when executed, gives out a row with the ValueCode column containing NULL and the Value column containing 'value1value2'. What I would like to get would be the attributes and values concatenated with a separator. For example, I need ValueCode to contain 'code1; code2' and Value to contain 'value 1; value2'. How can I achieve that?
you can use xuery if you want concatenated strings:
SELECT
Tbl.Col.query('for $i in value return concat($i/text()[1], ";")').value('.', 'nvarchar(max)'),
Tbl.Col.query('for $i in value return concat($i/#code, ";")').value('.', 'nvarchar(max)')
FROM #xml.nodes('root/element/subelement') Tbl(Col);
if you want your values into rows:
SELECT
Tbl.Col.value('.', 'nvarchar(max)'),
Tbl.Col.value('#code', 'nvarchar(max)')
FROM #xml.nodes('root/element/subelement/value') Tbl(Col);
sql fiddle demo
I'm rather experienced with SQL server "select for XML path" queries but now i run into a strange problem.
The following query works fine:
select
(
select
'Keyfield1' as "#Name",
t1.Keyfield1 as "Value"
from MyTable t1
where
t1.KeyField1= t2.KeyField1 and
t1.KeyField2= t2.KeyField2
for xml path('Field'),type, elements
) as 'Key'
from MyTable t2
for XML path('Path') , elements XSINIL, root('Root')
This will result (for a dummy dataset) in this XML:
<Root>
<Path>
<Key Name="KeyField1">
<Value>DummyValue1</Value>
</Key>
</Path>
</Root>
In my result of this (part of a bigger) statement i need the 2nd keyfield too:
<Root>
<Path>
<Key Name="KeyField1">
<Value>DummyValue1</Value>
</Key>
<Key Name="KeyField2">
<Value>DummyValue2</Value>
</Key>
</Path>
</Root>
So i changed my (sub)query with a union-select to:
select
(
select
'Keyfield1' as "#Name",
t1.Keyfield1 as "Value"
union all
select
'Keyfield2' as "#Name",
t1.Keyfield2 as "Value"
from MyTable t1
where
t1.KeyField1= t2.KeyField1 and
t1.KeyField2= t2.KeyField2
for xml path('Field'),type, elements
) as 'Key'
from MyTable t2
for XML path('Path') , elements XSINIL, root('Root')
But now i get the error "Only one expression can be specified in the select list when the subquery is not introduced with EXISTS."
I know it is possible to have multiple records in a subquery with for XML path witch results in multiple elements. But i don't understand why this can't be done with a union.
Can someone put me in the right direction how to accomplisch the XML with the 2 keyfields in my (sub)query?
Thanx you very much.
The problem with your subselect is that the first part isn't referring to any table at all (no FROM-clause).
This listing gives me the output you requested:
declare #mytable table (
keyfield1 nvarchar(20),
keyfield2 nvarchar(20)
)
insert into #mytable values ('Dummyvalue1', 'Dummyvalue2')
select * from #mytable
select
(
select
'Keyfield1' as "#Name",
t1.Keyfield1 as "Value"
from #mytable t1
where
t1.KeyField1= t2.KeyField1 and
t1.KeyField2= t2.KeyField2
for xml path('Field'),type, elements
) as 'Key'
from #mytable t2
for XML path('Path') , elements XSINIL, root('Root')
select
(
select * from (
select
'Keyfield1' as "#Name",
t1.Keyfield1 as "Value"
from #MyTable t1
where
t1.KeyField1= t2.KeyField1
union all
select
'Keyfield2' as "#Name",
t3.Keyfield2 as "Value"
from #MyTable t3
where
t3.KeyField2= t2.KeyField2) a
for xml path('Field'),type, elements
) as 'Key'
from #MyTable t2
for XML path('Path') , elements XSINIL, root('Root')
Here is a simplified example, but does this get you what you need?
select
(
select
'Keyfield1' as "#Name",
'Blah' as "Value"
for xml path('Key'),type, elements
),
(
select
'Keyfield2' as "#Name",
'Blah' as "Value"
for xml path('Key'),type, elements
)
for XML path('Path') , elements XSINIL, root('Root')