SQL Script to Break up XML into Columns - sql

I have a table that stores audit history in an XML format. Each document has its own history. How can I parse out he XML data per document whereby each column in the XML represents and an actual column and action that took place in that column.
Example:
<auditElement><field id="2881159" type="5" name="Responsiveness" formatstring=""><unSetChoice>2881167</unSetChoice><setChoice>2881166</setChoice></field></auditElement>
UnsetChoice and Set Choice are the columns.
Name=represents the action.

Xml can be parsed using features such as XQuery, or OpenXml.
Here's an example of xquery:
declare #xml as xml = '<auditElement><field id="2881159" type="5" name="Responsiveness"' +
' formatstring=""><unSetChoice>2881167</unSetChoice>' +
'<setChoice>2881166</setChoice></field></auditElement>';
SELECT
Nodes.node.value('(field/#id)[1]', 'INT') AS FieldId,
Nodes.node.value('(field/#name)[1]', 'varchar(50)') AS FieldName,
Nodes.node.value('(field/unSetChoice/text())[1]', 'INT') AS OldValue,
Nodes.node.value('(field/setChoice/text())[1]', 'INT') AS NewValue
FROM
#xml.nodes('//auditElement') AS Nodes(node);
Result:
FieldId FieldName OldValue NewValue
----------- -------------------------------------------------- ----------- -----------
2881159 Responsiveness 2881167 2881166

You can use OUTER APPLY and then break down the parts of the XML field.
The value() method takes XQuery expressions.
For example:
DECLARE #T TABLE (Id int identity(1,1) primary key, XmlCol1 xml);
insert into #T (XmlCol1) values
('<auditElement><field id="2881159" type="5" name="Responsiveness" formatstring=""><unSetChoice>2881167</unSetChoice><setChoice>2881166</setChoice></field></auditElement>'),
('<auditElement><field id="2881160" type="6" name="Responsiveness" ><unSetChoice>2881187</unSetChoice><setChoice>2881188</setChoice></field></auditElement>');
select *
from (
select
Id,
a.field.value('#id', 'int') as xmlid,
a.field.value('(unSetChoice)[1]', 'int') as unSetChoice,
a.field.value('(setChoice)[1]', 'int') as setChoice,
a.field.value('#type', 'int') as type,
a.field.value('#name', 'varchar(max)') as name,
a.field.value('#formatstring', 'varchar(max)') as formatstring
from #T t
outer apply t.XmlCol1.nodes('/auditElement/field') as a(field)
where a.field.value('#id', 'int') > 0 and Id > 0
) q
where name = 'Responsiveness';
Result:
Id xmlid unSetChoice setChoice type name formatstring
1 2881159 2881167 2881166 5 Responsiveness
2 2881160 2881187 2881188 6 Responsiveness NULL

Related

Insert repeating tag xml data to multiple sql table columns

I'm having following XML and query.
declare #xml as xml
set #xml = '<root>
<header1></hedaer1>
<header2></hedaer2>
<ItemNo></ItemNo>
<ItemQty></ItemNo>
<ItemNo></ItemNo>
<ItemQty></ItemNo>
<ItemNo></ItemNo>
<ItemQty></ItemNo>
</root>'
INSERT INTO #ItemDetails
([header1]
,[header2]
,[ItemNo1]
,[ItemQty1]
,[ItemNo2]
,[ItemQty2]
--upto ItemNo10 is possible
,[ItemNo10]
,[ItemQty210]
)
SELECT
X1.value('(header1/text())[1]','int') AS header1,
X1.value('(header2/text())[1]','nvarchar(36)') AS header2,
X1.value('(ItemNo/text())[1]','nvarchar(10)') AS ItemNo1,
X1.value('(ItemQty/text())[1]','nvarchar(10)') AS ItemQty1,
X1.value('(ItemNo/text())[2]','nvarchar(10)') AS ItemNo2,
X1.value('(ItemQty/text())[2]','nvarchar(10)') AS ItemQty2,
----insert upto ItemNo10
X1.value('(ItemNo/text())[10]','nvarchar(10)') AS ItemNo10,
X1.value('(ItemQty/text())[10]','nvarchar(10)') AS ItemQty10
FROM
#xml.nodes('/root') AS TEMPTABLE(X1)
header tags are there and Item tag can be repeated up to 10 tags. I want to insert those item no and qty in separate columns. Here I mentioned only 2 tags for Item but in real situation there are 5 tags in XML. So in worst case scenario it will be 50 columns.
Is there any easy way to loop and insert those item data rather than inserting one by one to columns?
Thank you
…
declare #xml as xml
set #xml = '<root>
<header1>h1</header1>
<header2>h2</header2>
<ItemNo>A</ItemNo>
<ItemQty>1</ItemQty>
<ItemtagA>A1</ItemtagA>
<ItemNo>B</ItemNo>
<ItemQty>2</ItemQty>
<ItemtagB>B2</ItemtagB>
<ItemNo>C</ItemNo>
<ItemQty>3</ItemQty>
<ItemtagA>A3</ItemtagA>
<ItemNo>D</ItemNo>
<ItemQty>4</ItemQty>
<ItemtagC>C4</ItemtagC>
<ItemNo>E</ItemNo>
<ItemQty>5</ItemQty>
<ItemtagA>A5</ItemtagA>
<ItemtagB>B5</ItemtagB>
<ItemtagC>C5</ItemtagC>
<!-- ..... -->
</root>';
select *
from
(
select
e.value('text()[1]', 'varchar(20)') as val,
-- if a "tag" appears for all items or is missing for all items.. then...
--concat(e.value('local-name(.)', 'varchar(20)'), row_number() over (partition by e.value('local-name(.)', 'varchar(20)') order by n.e)) as colname
-- if a tag is missing from a few items but exists in others then ...assume ItemNo denotes the start of an item
concat(e.value('local-name(.)', 'varchar(20)'), nullif(count(case when e.value('local-name(.)', 'varchar(20)') = 'ItemNo' then 1 end) over(order by n.e), 0)) as colname
from #xml.nodes('root/*') as n(e)
) as t
pivot
(
max(val) for colname in
(
header1, header2, --header11,header21 ...when a tag exists for all or is missing for all items
ItemNo1, ItemQty1, ItemtagA1, ItemtagB1, ItemtagC1,
ItemNo2, ItemQty2, ItemtagA2, ItemtagB2, ItemtagC2,
ItemNo3, ItemQty3, ItemtagA3, ItemtagB3, ItemtagC3,
ItemNo4, ItemQty4, ItemtagA4, ItemtagB4, ItemtagC4,
ItemNo5, ItemQty5, ItemtagA5, ItemtagB5, ItemtagC5,
ItemNo6, ItemQty6, ItemtagA6, ItemtagB6, ItemtagC6,
/*....ItemNo7, ...., ItemtagC9*/
ItemNo10, ItemQty10, ItemtagA10, ItemtagB10, ItemtagC10
)
) as pvt;

Can I select all XML attributes in SQL without knowing the attributes name?

Say I have a column containing an XML string that looks something like:
<column type="someValue" action="anotherValue" />
Is there a way I can extract data from this column XML to where the results will look something like:
key
value
type
someValue
action
anothervalue
I've Googled around for the better part of an hour now with no luck. The other solutions I've come across seem to all assume the user know the attribute name beforehand. Is this possible?
Assuming your your XMLCol is a string and not XML.
Example
Declare #YourTable table (id int,XMLCol varchar(max))
Insert Into #YourTable values
(1,'<column type="someValue" action="anotherValue" />')
Select A.ID
,C.*
From #YourTable A
Cross Apply ( values ( try_convert(xml,XMLCol) ))B(XMLData)
Cross Apply (
Select [Key] = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From XMLData.nodes('//#*') xNode(xAttr)
) C
Returns
ID Key Value
1 type someValue
1 action anotherValue
IF your Column is XML
Declare #YourTable table (id int,XMLCol xml)
Insert Into #YourTable values
(1,'<column type="someValue" action="anotherValue" />')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select [Key] = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From XMLCol.nodes('//#*') xNode(xAttr)
) B
declare #x xml = N'
<column type="someValue" action="anotherValue" />
<column typeX="someValueX" actionY="anotherValueX" att3="X123"/>
<column typeY="someValueY" actionY="anotherValueY" att3="Y123"/>
';
select
n.a.value('local-name(.)', 'nvarchar(100)') as attributename,
n.a.value('data(.)', 'nvarchar(100)') as attributevalue
from #x.nodes('//#*') as n(a);

Get values from XML field with SQL Server

I would like to get the value from a xml column with T-SQL.
My xml column named XML contains :
<SIMBATCHJOB ONFAIL="2" ID="4" ONPASS="0"/>
How can I get the second parameter? For example here ID.
I have tried a request like this:
DECLARE #xml XML
SELECT #xml = N'<SIMBATCHJOB ONFAIL="2" ID="4" ONPASS="0"/>'
SELECT
T.C.value('(SIMBATCHJOB)[1]', 'int') as test1
FROM
#xml.nodes('SIMBATCHJOB') T(C)
My table is structured like this:
ID | XML
If I try this query:
Select ID
,Value = cast([xml] as xml).value('SIMBATCHJOB[1]/#ID','int')
From SIM_JOBS
where id = 179
This query return me the ID and the Value is empty
Do you understand this ?
Thanks in advance
Jeroen is 100% correct, but one option is to use a CROSS APPLY and normalize your attributes.
The Sequence will hold true to the sequence in the actual XML. I suspect you could filter as needed.
Example
Declare #XML xml = '<SIMBATCHJOB ONFAIL="2" ID="4" ONPASS="0"/>'
Select Seq = row_number() over (Order By (Select NULL))
,Attribute = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','varchar(max)')
From #XML.nodes('/SIMBATCHJOB ') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
Returns
Seq Attribute Value
1 ONFAIL 2
2 ID 4
3 ONPASS 0
EDIT - Update for Comments ---
This will return the SECOND Attribute/Value regardles of what is is
Declare #YourTable table (ID int,[xml] xml)
Insert Into #YourTable values
(1,'<SIMBATCHJOB ONFAIL="2" ID="4" ONPASS="0"/>'),
(2,'<SIMBATCHJOB ONFAIL="2" ID="99" ONPASS="1"/>')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select *
From (
Select Seq = row_number() over (Order By (Select NULL))
,Attribute = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','varchar(max)')
From A.xml.nodes('/SIMBATCHJOB ') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
) B1
Where B1.Seq=2
) B
Returns
ID Seq Attribute Value
1 2 ID 4
2 2 ID 99
EDIT 2: - Get JUST ID regardless of Sequence
Declare #YourTable table (ID int,[xml] xml)
Insert Into #YourTable values
(1,'<SIMBATCHJOB ONFAIL="2" ID="4" ONPASS="0"/>'),
(2,'<SIMBATCHJOB ONFAIL="2" ID="99" ONPASS="1"/>')
Select A.ID
,Value = [xml].value('SIMBATCHJOB[1]/#ID','int')
From #YourTable A
Returns
ID Value
1 4
2 99

Query XML field in SQL Server

I am using sql server 2008 R2. I have table X with Column XXML with the following structure
<rec>
<set>
<Raw CLOrderID="GGM-30-08/24/10" Rej="Preopen" Sym="A" Tm="06:36:29.524" />
</set>
</rec>
I want to parse above column XXML and return output as below:
CLOrderID Rej Sym Tm
GGM-30-08/24/10 Preopen A 06:36:29.524
Use nodes() to shred the XML and value() to extract the attribute values.
select T.X.value('#CLOrderID', 'nvarchar(100)') as CLOrderID,
T.X.value('#Rej', 'nvarchar(100)') as Rej,
T.X.value('#Sym', 'nvarchar(100)') as Sym,
T.X.value('#Tm', 'time(3)') as Tm
from dbo.X
cross apply X.XXML.nodes('/rec/set/Raw') as T(X)
If you know for sure that you only will have one row extracted for each XML you can get the attribute values directly without shredding first.
select X.XXML.value('(/rec/set/Raw/#CLOrderID)[1]', 'nvarchar(100)') as CLOrderID,
X.XXML.value('(/rec/set/Raw/#Rej)[1]', 'nvarchar(100)') as Rej,
X.XXML.value('(/rec/set/Raw/#Sym)[1]', 'nvarchar(100)') as Sym,
X.XXML.value('(/rec/set/Raw/#Tm)[1]', 'time(3)') as Tm
from dbo.X
This can be done with a few for xml calls. This structure also remains flexible for future schema changes, provided /rec/set/Raw/#* is present. If that changes, you can always add a pipe with the new path for the attributes you're wanting to grab. Hope this helps.
declare #x table (id int identity(1,1) primary key, x xml)
insert into #x (x) values ('<rec>
<set>
<Raw CLOrderID="GGM-30-08/24/10" Rej="Preopen" Sym="A" Tm="06:36:29.524" />
</set>
</rec>')
select a.id, cast((
select (
select x.attribs.value('local-name(.)','nvarchar(20)') + ' '
from #x t
outer apply t.x.nodes('/rec/set/Raw/#*') x(attribs)
where t.id = a.id
for xml path('')
), (
select x.attribs.value('.','nvarchar(20)') + ' '
from #x t
outer apply t.x.nodes('/rec/set/Raw/#*') x(attribs)
where t.id = a.id
for xml path('')
)
for xml path('')
) as varchar(500))
from #x a

Concatenate multiple xml columntype rows in SQL

I have an online form, whereby each entry adds the data as xml to an xml column in SQL.
ApplicationID(uniqueidentifier) | DateModified | ApplicationForm(XML)
What I need to do is perform a select query that will grab all the ApplicationForm xml values, and concatenate them together to form one result, e.g.
Row1: <ApplicationForm type=""></ApplicationForm>
Row2: <ApplicationForm type=""></ApplicationForm>
Select result:
<Applications>
<ApplicationForm type=""></ApplicationForm>
<ApplicationForm type=""></ApplicationForm>
</Applications>
Probably a better way to do it than this, but I specified the ApplicationForm tag as a tag to be removed, and then stripped it out.
DECLARE #a TABLE (ID UNIQUEIDENTIFIER,
DateModified DATETIME,
ApplicationForm XML)
INSERT INTO #a
( ID, DateModified, ApplicationForm )
VALUES ( NEWID(), -- ID - uniqueidentifier
'2010-12-07 18:47:36', -- DateModified - datetime
'<Application><Form>123</Form></Application>'
) ,
( NEWID(), -- ID - uniqueidentifier
'2010-12-07 18:47:36', -- DateModified - datetime
'<Application><Form>456</Form></Application>'
)
DECLARE #Result VARCHAR(MAX)
SET #Result = CONVERT(VARCHAR(MAX), ( SELECT ApplicationForm AS "StripTagOut"
FROM #a
FOR XML PATH(''), ROOT('Applications'), TYPE ))
SELECT CONVERT(xml, REPLACE(REPLACE(#Result, '</StripTagOut>', ''), '<StripTagOut>', ''))
This was an experiment in using XML EXPLICIT which I believe works:
SELECT 1 AS Tag,
NULL AS Parent,
NULL [Applications!1],
NULL [ApplicationForm!2!!XMLTEXT]
UNION ALL
SELECT 2 AS Tag,
1 AS Parent,
NULL [Applications!1],
ApplicationForm [ApplicationForm!2!!XMLTEXT]
FROM YourTable
FOR XML EXPLICIT
For my own gratification, here is the sample script I used
CREATE TABLE XmlTest
(
ApplicationForm xml
)
INSERT INTO XmlTest VALUES ('<ApplicationForm type="a"><SomeTag>SomeContent</SomeTag></ApplicationForm>')
INSERT INTO XmlTest VALUES ('<ApplicationForm type="b"><SomeTag>SomeOtherContent</SomeTag></ApplicationForm>')
SELECT 1 AS Tag,
NULL AS Parent,
NULL [Applications!1],
NULL [ApplicationForm!2!!XMLTEXT]
UNION ALL
SELECT 2 AS Tag,
1 AS Parent,
NULL [Applications!1],
ApplicationForm [ApplicationForm!2!!XMLTEXT]
FROM XmlTest
FOR XML EXPLICIT
Which output
<Applications>
<ApplicationForm type="a">
<SomeTag>SomeContent</SomeTag>
</ApplicationForm>
<ApplicationForm type="b">
<SomeTag>SomeOtherContent</SomeTag>
</ApplicationForm>
</Applications>
Take a look at this page, which lists quite a number of ways to concatenate rows of data.