Reputation: 13
I have found examples of parsing xml to inserts. However these examples are really simple. They are usually just like this:
<person>
<name>Martin</name>
</person>
<person>
<name>John</name>
</person>
But I have XML similar to this - Where I need to have inserts into other tables for child elements.
<root>
<family>
<name>Smith</name>
<address>Some road 1</address>
<persons>
<person>
<name>Tina</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
<person>
<name>Martin</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
</persons>
</family>
<family>
<name>Lane</name>
<address>Some road 1</address>
<persons>
<person>
<name>Kevin</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
<person>
<name>Julia</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
</persons>
</family>
</root>
I need to iterate through this xml and first INSERT a row into table "Families" After that I return the ID for the family and use it as foreign key in the next INSERT for a person in the table "Persons" and same with the hobbies. I think you get the idea. And after a "Family" I need to do some update statements before moving on to the next family.
Could someone point me in the right direction ? Would be much appreciated.
Upvotes: 1
Views: 2519
Reputation: 1
The main problem here is that you can have many families with the same name. My solution take into account this "aspect" (note: I used a modified XML for testing). In this example, you can see two families with the same name (Smith) but diff. persons. These families have diff. RowNum's (and FamilyID's). The tricky part is in the last CROSS APPLY
that extracts all /person
elements for every family name occurrence:
CROSS APPLY @x.nodes('/root/family[name=sql:column("f.Name")][sql:column("f.RowNum")]/persons/person') AS a(b)
Note: you can develop this solution to extract, also, every hobby.
DECLARE @x XML = N'
<root>
<family>
<name>Smith</name>
<address>Some road 1</address>
<persons>
<person>
<name>Tina</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
<person>
<name>Martin</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
</persons>
</family>
<family>
<name>Lane</name>
<address>Some road 1</address>
<persons>
<person>
<name>Kevin</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
<person>
<name>Julia</name>
<hobbies>
<hobby>Some hobby 1</hobby>
<hobby>Some hobby 2</hobby>
</hobbies>
</person>
</persons>
</family>
<family>
<name>Smith</name>
<address>Some another road 11</address>
<persons>
<person>
<name>Coco</name>
</person>
<person>
<name>Jambo</name>
</person>
</persons>
</family>
</root>';
DECLARE @Family TABLE (
FamilyID INT IDENTITY(1,1) UNIQUE,
Name NVARCHAR(50) NOT NULL,
RowNum INT NOT NULL,
PRIMARY KEY (Name, RowNum)
);
INSERT @Family (Name, RowNum)
SELECT src.Name,
ROW_NUMBER() OVER(PARTITION BY src.Name ORDER BY @@SPID) AS RowNum
FROM (
SELECT a.b.value('(name)[1]', 'NVARCHAR(50)') AS Name
FROM @x.nodes('/root/family') AS a(b)
) src;
SELECT f.*
FROM @Family AS f
DECLARE @Person TABLE (
PersonID INT IDENTITY(1,1) UNIQUE,
FamilyID INT NOT NULL, -- Kind of FK
Name NVARCHAR(50) NOT NULL,
RowNum INT NOT NULL,
PRIMARY KEY (Name, RowNum)
)
INSERT @Person (FamilyID, Name, RowNum)
SELECT src.FamilyID,
src.Name,
ROW_NUMBER() OVER(PARTITION BY src.FamilyID, src.Name ORDER BY @@SPID) AS RowNum
FROM (
SELECT f.FamilyID,
a.b.value('(name)[1]', 'NVARCHAR(50)') AS Name
FROM @family f
CROSS APPLY @x.nodes('/root/family[name=sql:column("f.Name")][sql:column("f.RowNum")]/persons/person') AS a(b)
) src;
SELECT p.*
FROM @Person AS p;
Results:
FamilyID Name RowNum
-------- ----- ------
1 Lane 1
2 Smith 1
3 Smith 2
PersonID FamilyID Name RowNum
-------- -------- ------ ------
5 3 Coco 1
6 3 Jambo 1
1 1 Julia 1
2 1 Kevin 1
3 2 Martin 1
4 2 Tina 1
Upvotes: 0
Reputation: 4604
Unfortunately, SQL Server doesn't support multi-table insert, so you would need to do single inserts like this:
insert into family
select f.node.value('name[1]', 'varchar(32)') as name
from @xml.nodes('/root/family') f(node)
insert into person
select family.ID as familyID, p.node.value('name[1]', 'varchar(32)') as name
from @xml.nodes('/root/family') f(node)
cross apply f.node.nodes('persons/person') p(node)
inner join family on f.node.value('name[1]', 'varchar(32)') = family.name
Upvotes: 2