October 7, 2015 at 1:19 am
Hi All
XML has never been a strong point of mine. Need some help getting data into XML format as shown in the last code block.
The datatypes and table structures are pretty much fixed, but I can re-hash data into another Temp Table, CTE, etc...
This is a server running SQL Server 2012, but I'd guess any version that understands FOR XML PATH should be fine.
Let me know if anything else is needed.
Source tables and data
if object_id('Tempdb..#Element1') is not null drop table #Element1;
create table #Element1 (
[Attr1] varchar(10)
,[Attr2] varchar(4)
,[Attr3] varchar(2)
,[Attr4] varchar(5)
,[Attr5] integer
,[Attr6] varchar(1)
);
INSERT INTO #Element1
VALUES ('MONTH','2015','07','NEW',39,'Y');
if object_id('Tempdb..#Element2') is not null drop table #Element2;
create table #Element2 (
[Attr5] integer
,[Attr7] varchar(15)
,[Attr8] varchar(100)
,[Attr9] decimal
,[Attr10] decimal
,[Attr11] decimal
);
INSERT INTO #Element2
VALUES (39,'12345','My Reporting Group 1',41297,40616,681)
,(39,'67890','My Reporting Group 2',7372,3552,3820);
if object_id('Tempdb..#Element3') is not null drop table #Element3;
create table #Element3 (
[Attr7] varchar(15)
,[Element4] varchar(50)
,[Element5] decimal
,[Element6] decimal
,[Element7] decimal
,[Element8] decimal
);
INSERT INTO #Element3
VALUES('12345','SubGroup1',75959,209,1,0)
,('12345','SubGroup2',129687,283,1,0)
,('67890','SubGroup1',416931,498,4,0)
,('67890','SubGroup2',66720,82,1,0);
My SQL statement so far
SELECT
e1.[Attr1] [@Attr1]
,e1.[Attr2] as [@Attr2]
,e1.[Attr3] as [@Attr3]
,e1.[Attr4] as [@Attr4]
,e1.[Attr5] as [@Attr5]
,e1.[Attr6] as [@Attr6]
,(
SELECT
e2a.[Attr7] as [@Atrr7]
,e2a.[Attr8] as [@Atrr8]
,e2a.[Attr9] as [@Attr09]
,e2a.[Attr10] as [@Attr10]
,e2a.[Attr11] as [@Attr11]
,(
select
e3.[Element4]
,e3.[Element5]
,e3.[Element6]
,e3.[Element7]
,e3.[Element8]
from #Element2 e2_2
join #Element3 e3 on e3.[Attr7] = e2_2.[Attr7]
where e2a.[Attr5] = e2_2.[Attr5]
and e2a.[Attr7] = e2_2.[Attr7]
for xml path('Element3'), TYPE
) as Element3
from #Element2 e2a
where e1.[Attr5] = e2a.[Attr5]
for xml path('Element2'), TYPE
) as Element2
from #Element1 e1
for xml path('Element1')
Incorrect XML produced from SQL Statement
<Element1 Attr1="MONTH" Attr2="2015" Attr3="07" Attr4="NEW" Attr5="39" Attr6="Y">
<Element2>
<Element2 Atrr7="12345" Atrr8="My Reporting Group 1" Attr09="41297" Attr10="40616" Attr11="681">
<Element3>
<Element3>
<Element4>SubGroup1</Element4>
<Element5>75959</Element5>
<Element6>209</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</Element3>
<Element3>
<Element4>SubGroup2</Element4>
<Element5>129687</Element5>
<Element6>283</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</Element3>
</Element3>
</Element2>
<Element2 Atrr7="67890" Atrr8="My Reporting Group 2" Attr09="7372" Attr10="3552" Attr11="3820">
<Element3>
<Element3>
<Element4>SubGroup1</Element4>
<Element5>416931</Element5>
<Element6>498</Element6>
<Element7>4</Element7>
<Element8>0</Element8>
</Element3>
<Element3>
<Element4>SubGroup2</Element4>
<Element5>66720</Element5>
<Element6>82</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</Element3>
</Element3>
</Element2>
</Element2>
</Element1>
Desired XML output
<Element1 Attr1="MONTH" Attr2="2013" Attr3="07" Attr4="new" Attr5="39" Attr6="Y">
<Element2 Atrr7="201002" Atrr8="My Reporting Group 1" Atrr9="41297" Atrr10="40616" Atrr11="681">
<Element3>
<SubGroup1>
<Element5>75959</Element5>
<Element6>209</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup1>
<SubGroup2>
<Element5>129687</Element5>
<Element6>283</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup2>
</Element3>
</Element2>
<Element2 Atrr7="201338" Atrr7="My Reporting Group 2" Atrr9="7372" Atrr10="3552" Atrr11="3820">
<Element3>
<SubGroup1>
<Element5>416931</Element5>
<Element6>498</Element6>
<Element7>4</Element7>
<Element8>0</Element8>
</SubGroup1>
<SubGroup2>
<Element5>66720</Element5>
<Element6>82</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup2>
</Element3>
</Element2>
</Element1>
--------------------
Colt 45 - the original point and click interface
October 7, 2015 at 2:43 am
Quick solution
😎
USE tempdb;
GO
SET NOCOUNT ON;
if object_id('Tempdb..#Element1') is not null drop table #Element1;
create table #Element1 (
[Attr1] varchar(10)
,[Attr2] varchar(4)
,[Attr3] varchar(2)
,[Attr4] varchar(5)
,[Attr5] integer
,[Attr6] varchar(1)
);
INSERT INTO #Element1
VALUES ('MONTH','2015','07','NEW',39,'Y');
if object_id('Tempdb..#Element2') is not null drop table #Element2;
create table #Element2 (
[Attr5] integer
,[Attr7] varchar(15)
,[Attr8] varchar(100)
,[Attr9] decimal
,[Attr10] decimal
,[Attr11] decimal
);
INSERT INTO #Element2
VALUES (39,'12345','My Reporting Group 1',41297,40616,681)
,(39,'67890','My Reporting Group 2',7372,3552,3820);
if object_id('Tempdb..#Element3') is not null drop table #Element3;
create table #Element3 (
[Attr7] varchar(15)
,[Element4] varchar(50)
,[Element5] decimal
,[Element6] decimal
,[Element7] decimal
,[Element8] decimal
);
INSERT INTO #Element3
VALUES('12345','SubGroup1',75959,209,1,0)
,('12345','SubGroup2',129687,283,1,0)
,('67890','SubGroup1',416931,498,4,0)
,('67890','SubGroup2',66720,82,1,0);
SELECT
EL1.Attr1 AS '@Attr1'
,EL1.Attr2 AS '@Attr2'
,EL1.Attr3 AS '@Attr3'
,EL1.Attr4 AS '@Attr4'
,EL1.Attr5 AS '@Attr5'
,EL1.Attr6 AS '@Attr6'
,(
SELECT
EL2.Attr7 AS 'Element2/@Attr7'
,EL2.Attr8 AS 'Element2/@Attr8'
,EL2.Attr9 AS 'Element2/@Attr9'
,EL2.Attr10 AS 'Element2/@Attr10'
,EL2.Attr11 AS 'Element2/@Attr11'
,(
SELECT
X.[SubGroup1/Element5]
,X.[SubGroup1/Element6]
,X.[SubGroup1/Element7]
,X.[SubGroup1/Element8]
FROM (
SELECT
EL3.Element5 AS 'SubGroup1/Element5'
,EL3.Element6 AS 'SubGroup1/Element6'
,EL3.Element7 AS 'SubGroup1/Element7'
,EL3.Element8 AS 'SubGroup1/Element8'
FROM #Element3 EL3
WHERE EL3.Attr7 = EL2.Attr7
AND EL3.Element4 = 'SubGroup1'
UNION ALL
SELECT
EL3.Element5 AS 'SubGroup2/Element5'
,EL3.Element6 AS 'SubGroup2/Element6'
,EL3.Element7 AS 'SubGroup2/Element7'
,EL3.Element8 AS 'SubGroup2/Element8'
FROM #Element3 EL3
WHERE EL3.Attr7 = EL2.Attr7
AND EL3.Element4 = 'SubGroup2'
) AS X
FOR XML PATH(''), TYPE
) AS 'Element3'
FROM #Element2 EL2
FOR XML PATH(''), TYPE
)
FROM #Element1 EL1
FOR XML PATH('Element1'), TYPE;
Results
<Element1 Attr1="MONTH" Attr2="2015" Attr3="07" Attr4="NEW" Attr5="39" Attr6="Y">
<Element2 Attr7="12345" Attr8="My Reporting Group 1" Attr9="41297" Attr10="40616" Attr11="681" />
<Element3>
<SubGroup1>
<Element5>75959</Element5>
<Element6>209</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup1>
<SubGroup1>
<Element5>129687</Element5>
<Element6>283</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup1>
</Element3>
<Element2 Attr7="67890" Attr8="My Reporting Group 2" Attr9="7372" Attr10="3552" Attr11="3820" />
<Element3>
<SubGroup1>
<Element5>416931</Element5>
<Element6>498</Element6>
<Element7>4</Element7>
<Element8>0</Element8>
</SubGroup1>
<SubGroup1>
<Element5>66720</Element5>
<Element6>82</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup1>
</Element3>
</Element1>
October 7, 2015 at 3:51 am
You may wish to add root element to keep output well-formed xml document when Element1 table has more rows . And subgroups naming may be rendered with attribute which may be easier to parse later.
SELECT
e1.[Attr1] [element1/@Attr1]
,e1.[Attr2] as [element1/@Attr2]
,e1.[Attr3] as [element1/@Attr3]
,e1.[Attr4] as [element1/@Attr4]
,e1.[Attr5] as [element1/@Attr5]
,e1.[Attr6] as [element1/@Attr6]
,element1 = (SELECT
e2a.[Attr7] as [@Atrr7]
,e2a.[Attr8] as [@Atrr8]
,e2a.[Attr9] as [@Attr09]
,e2a.[Attr10] as [@Attr10]
,e2a.[Attr11] as [@Attr11]
,element3 = (SELECT
e3.[Element4] as [@name]
,e3.[Element5]
,e3.[Element6]
,e3.[Element7]
,e3.[Element8]
FROM #Element2 e2_2
join #Element3 e3 ON e3.[Attr7] = e2_2.[Attr7]
WHERE e2a.[Attr5] = e2_2.[Attr5]
and e2a.[Attr7] = e2_2.[Attr7]
FOR XML PATH('Element4'), TYPE
)
FROM #Element2 e2a
WHERE e1.[Attr5] = e2a.[Attr5]
FOR XML PATH('element2'), TYPE
)
FROM #Element1 e1
FOR XML PATH(''), ROOT('r')
P.S. root.
October 7, 2015 at 8:13 am
Your original query was very close. You're getting Element2 and Element3 twice, because you're specifying them both twice: once in the path() and once in the alias. Instead of giving your subqueries aliases of the elements, you need to give them the alias [*].
SELECT
e1.[Attr1] [@Attr1]
,e1.[Attr2] as [@Attr2]
,e1.[Attr3] as [@Attr3]
,e1.[Attr4] as [@Attr4]
,e1.[Attr5] as [@Attr5]
,e1.[Attr6] as [@Attr6]
,(
SELECT
e2a.[Attr7] as [@Atrr7]
,e2a.[Attr8] as [@Atrr8]
,e2a.[Attr9] as [@Attr09]
,e2a.[Attr10] as [@Attr10]
,e2a.[Attr11] as [@Attr11]
,(
select
e3.[Element4]
,e3.[Element5]
,e3.[Element6]
,e3.[Element7]
,e3.[Element8]
from #Element2 e2_2
join #Element3 e3 on e3.[Attr7] = e2_2.[Attr7]
where e2a.[Attr5] = e2_2.[Attr5]
and e2a.[Attr7] = e2_2.[Attr7]
for xml path('Element3'), TYPE
) as [*]
from #Element2 e2a
where e1.[Attr5] = e2a.[Attr5]
for xml path('Element2'), TYPE
) as [*]
from #Element1 e1
for xml path('Element1')
Drew
J. Drew Allen
Business Intelligence Analyst
Philadelphia, PA
October 7, 2015 at 2:47 pm
Thanks for all the responses so far. Unfortunately all of them don't quite get there :crying:
The tripping point seems to be the [Element4] values. As shown in the desired output, the value of the sub-type needs to be the element name, not an attribute of that element.
What I've provided is a cut-down list of data. There are 203 possible sub-groups with one or many various sub-groups assigned to each [Attr7] value.
This isn't really feasible given the varying quantity and value of sub-groups EL3.Element5 AS 'SubGroup1/Element5'
This has the value as an attribute instead of an element <Element4 name="SubGroup1">
This has the wrong element name <Element4>SubGroup1</Element4>
For the purposes of this post it may be better if my #Element3 insert was re-written like
INSERT INTO #Element3
VALUES('12345','SubGroup1',75959,209,1,0)
,('12345','SubGroup4',129687,283,1,0)
,('12345','SubGroup5',223157,343,1,0)
,('67890','SubGroup2',416931,498,4,0)
,('67890','SubGroup3',66720,82,1,0);
And the desired XML output would then be
<Element1 Attr1="MONTH" Attr2="2013" Attr3="07" Attr4="new" Attr5="39" Attr6="Y">
<Element2 Atrr7="201002" Atrr8="My Reporting Group 1" Atrr9="41297" Atrr10="40616" Atrr11="681">
<Element3>
<SubGroup1>
<Element5>75959</Element5>
<Element6>209</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup1>
<SubGroup4>
<Element5>129687</Element5>
<Element6>283</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup4>
<SubGroup5>
<Element5>223157</Element5>
<Element6>343</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup5>
</Element3>
</Element2>
<Element2 Atrr7="201338" Atrr7="My Reporting Group 2" Atrr9="7372" Atrr10="3552" Atrr11="3820">
<Element3>
<SubGroup2>
<Element5>416931</Element5>
<Element6>498</Element6>
<Element7>4</Element7>
<Element8>0</Element8>
</SubGroup2>
<SubGroup3>
<Element5>66720</Element5>
<Element6>82</Element6>
<Element7>1</Element7>
<Element8>0</Element8>
</SubGroup3>
</Element3>
</Element2>
</Element1>
--------------------
Colt 45 - the original point and click interface
October 8, 2015 at 1:43 am
Ok, do it with string manipulation. You may eliminate inner CAST if building entire string this way. But for redability i just use two CASTs.
if object_id('Tempdb..#Element1') is not null drop table #Element1;
create table #Element1 (
[Attr1] varchar(10)
,[Attr2] varchar(4)
,[Attr3] varchar(2)
,[Attr4] varchar(5)
,[Attr5] integer
,[Attr6] varchar(1)
);
INSERT INTO #Element1
VALUES ('MONTH','2015','07','NEW',39,'Y');
if object_id('Tempdb..#Element2') is not null drop table #Element2;
create table #Element2 (
[Attr5] integer
,[Attr7] varchar(15)
,[Attr8] varchar(100)
,[Attr9] decimal
,[Attr10] decimal
,[Attr11] decimal
);
INSERT INTO #Element2
VALUES (39,'12345','My Reporting Group 1',41297,40616,681)
,(39,'67890','My Reporting Group 2',7372,3552,3820);
if object_id('Tempdb..#Element3') is not null drop table #Element3;
create table #Element3 (
[Attr7] varchar(15)
,[Element4] varchar(50)
,[Element5] decimal
,[Element6] decimal
,[Element7] decimal
,[Element8] decimal
);
INSERT INTO #Element3
VALUES('12345','SubGroup1',75959,209,1,0)
,('12345','SubGroup4',129687,283,1,0)
,('12345','SubGroup5',223157,343,1,0)
,('67890','SubGroup2',416931,498,4,0)
,('67890','SubGroup3',66720,82,1,0);
go
SELECT
e1.[Attr1] [element1/@Attr1]
,e1.[Attr2] as [element1/@Attr2]
,e1.[Attr3] as [element1/@Attr3]
,e1.[Attr4] as [element1/@Attr4]
,e1.[Attr5] as [element1/@Attr5]
,e1.[Attr6] as [element1/@Attr6]
,element1 = (SELECT
e2a.[Attr7] as [@Atrr7]
,e2a.[Attr8] as [@Atrr8]
,e2a.[Attr9] as [@Attr09]
,e2a.[Attr10] as [@Attr10]
,e2a.[Attr11] as [@Attr11]
,element3 = (SELECT CAST('<' + e3.[Element4] +'>' +
CAST(
(SELECT
e3.[Element5]
,e3.[Element6]
,e3.[Element7]
,e3.[Element8]
FOR XML PATH ('')) AS NVARCHAR(4000))
+'</'+ e3.[Element4] +'>' AS XML)
FROM #Element2 e2_2
join #Element3 e3 ON e3.[Attr7] = e2_2.[Attr7]
WHERE e2a.[Attr5] = e2_2.[Attr5]
and e2a.[Attr7] = e2_2.[Attr7]
FOR XML PATH(''), TYPE
)
FROM #Element2 e2a
WHERE e1.[Attr5] = e2a.[Attr5]
FOR XML PATH('element2'), TYPE
)
FROM #Element1 e1
FOR XML PATH(''), ROOT('r')
October 8, 2015 at 5:37 pm
That looks to have done it. Awesome work thanks.
--------------------
Colt 45 - the original point and click interface
October 8, 2015 at 7:52 pm
I think it fascinating that the end result in the form of XML is 919 characters (without spaces) and that the code to conditionally drop the tables, recreate the tables, and populate the tables is only 955 characters (without spaces) . The actual data in "Comedy Separated Format" is only 296 characters including the spaces in the data.
And people say that SQL is antiquated! Between the tag bloat and relative unreadability and all the trouble that you have to go to put the data into hierarchical format and all the trouble that someone on the other end will have flattening the data back out into it's respective entities not to mention the quadrupling of space used in all areas concerning backups, diskspace, IO, and traffic over the pipe, let me express just how disgusted I am with XML. Any attempts to express the actual datatypes or guarantee deentitization of reserved characters would only make it take more space and twice as long to pack. :sick:
--Jeff Moden
Change is inevitable... Change for the better is not.
October 8, 2015 at 8:57 pm
Jeff, surely you're forgetting the times when you need a XSL file and namespaces declarations :-D:-D
--------------------
Colt 45 - the original point and click interface
October 8, 2015 at 9:57 pm
philcart (10/8/2015)
Jeff, surely you're forgetting the times when you need a XSL file and namespaces declarations :-D:-D
Heh... not forgetting... trying to give it a chance. I figured it was bad enough as it was. 🙂
--Jeff Moden
Change is inevitable... Change for the better is not.
Viewing 10 posts - 1 through 9 (of 9 total)
You must be logged in to reply to this topic. Login to reply