TSQL Query Inserting data from xPath - sql-server

I am using SQL Server 2012 and trying trying to insert data into multiple tables from an XML string containing the data. The issue and confusion stems from the XML containing multiple nodes so its not just a single record at a time.
Due to this, I am using the output method to insert the data along with the Identity so I know the result of each of the records it inserts.
My problem is due to the structure of the XML string, it is not inserting all of the data it needs to.
Here is the block of code I am working with along with a SQL Fiddle:
Fiddle: http://sqlfiddle.com/#!6/d41d8/24236
DECLARE #xml xml = '<root>
<trainingEventID>572</trainingEventID>
<segment>
<segmentDate>03/03/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User12341</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/04/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/13/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
</root>'
-- Declare our temp tables
DECLARE #tmpSeg TABLE (teSegmentID INT, trainingEventID INT, segmentDate DATE, nonProdHrs int);
DECLARE #tmpEvents TABLE (teSegmentID INT IDENTITY(1,1), trainingEventID INT, segmentDate DATE, nonProdHrs INT);
-- First, Insert the main segments
INSERT INTO #tmpEvents(trainingEventID, segmentDate, nonProdHrs)
OUTPUT Inserted.teSegmentID, Inserted.trainingEventID, Inserted.segmentDate, Inserted.nonProdHrs INTO #tmpSeg
SELECT ParamValues.x1.value('../trainingEventID[1]', 'INT'),
ParamValues.x1.value('(segmentDate/text())[1]', 'DATE'),
ParamValues.x1.value('(hours/text())[1]', 'INT')
FROM #xml.nodes('/root/segment') AS ParamValues(x1);
SELECT * FROM #tmpSeg
-- Now, we join on our temp table and insert the Segment Details
SELECT s.teSegmentID,
ParamValues.x1.value('(details/locale/text())[1]', 'INT') AS localeID,
ParamValues.x1.value('(details/teammates/text())[1]', 'INT') AS teammates,
ParamValues.x1.value('(details/leaders/text())[1]', 'INT') AS leaders,
ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT') AS eventID,
ParamValues.x1.value('(segmentDate/text())[1]', 'DATE') AS date,
ParamValues.x1.value('(hours/text())[1]', 'INT') AS hours
FROM #tmpSeg AS s
INNER JOIN #xml.nodes('/root/segment') AS ParamValues(x1)
ON s.trainingEventID = ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT')
AND s.segmentDate = ParamValues.x1.value('(segmentDate/text())[1]', 'DATE')
AND s.nonProdHrs = ParamValues.x1.value('(hours/text())[1]', 'INT')
As you can see from the XML structure, it is broken down into parts. There is a segment and then within the segment there can be multiple Details Nodes.
The first step in the query is to create all of the segments which appears to be working fine. Each segment gets created and the Identity is stored in a temp table from the output.
Next, I need to create records for each details node using the Identity of its parent segment. I do this by joining the temp table from the output some some of its data to get the details needed.
The issue with this is due to multiple details nodes, it is only accessing the first one and storing its data.
The output in the last statement using this example should contain 9 records. There are 3 details nodes for each segment and there are 3 segments total.
Not sure how to accomplish this but its driving me crazy.
Thanks for any help.

You need another level for details, first off, but there's also the question of trainers..I took a bit of liberty with the solution here, so feel free to modify as needed.
DECLARE #xml xml = '<root>
<trainingEventID>572</trainingEventID>
<segment>
<segmentDate>03/03/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User12341</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/04/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
<segment>
<segmentDate>03/13/2015</segmentDate>
<hours>4</hours>
<details>
<locale>653</locale>
<teammates>3</teammates>
<leaders>4</leaders>
</details>
<details>
<locale>655</locale>
<teammates>44</teammates>
<leaders>55</leaders>
</details>
<details>
<locale>657</locale>
<teammates>55</teammates>
<leaders>66</leaders>
</details>
<trainers>
<trainer>
<empID>User1234</empID>
</trainer>
</trainers>
</segment>
</root>'
-- Declare temp tables
DECLARE #tmpSeg TABLE (teSegmentID INT IDENTITY(1,1), trainingEventID INT, segmentDate DATE, nonProdHrs INT, trainer varchar(30));
DECLARE #tmpLocales TABLE (teSegmentID INT, trainingEventID INT/*, segmentDate DATE, nonProdHrs int*/, locale int, teammates int, leaders int);
DECLARE #tmpTrainers TABLE (teSegmentID INT, trainingEventID INT, empID VARCHAR(30));
-- Get Segment info
INSERT INTO #tmpSeg(trainingEventID, segmentDate, nonProdHrs, trainer)
SELECT
ParamValues.x1.value('../trainingEventID[1]', 'INT')
, ParamValues.x1.value('segmentDate[1]', 'DATE')
, ParamValues.x1.value('hours[1]', 'INT')
, ParamValues.x1.value('trainers[1]/trainer[1]/empID[1]', 'VARCHAR(30)')
FROM #xml.nodes('/root/segment') AS ParamValues(x1);
SELECT * FROM #tmpSeg
-- Get Segment-dependent trainer info
INSERT INTO #tmpTrainers(teSegmentID, trainingEventID, empID)
SELECT
S.teSegmentID
, D.trainingEventID
, D.empID
FROM (
SELECT
ParamValues.x1.value('empID[1]', 'VARCHAR(30)') AS empID
, ParamValues.x1.value('../../../trainingEventID[1]', 'INT') AS trainingEventID
, ParamValues.x1.value('../../segmentDate[1]', 'DATE') AS segmentDate
, ParamValues.x1.value('../../hours[1]', 'INT') AS nonProdHours
FROM #xml.nodes('/root/segment/trainers/trainer') AS ParamValues(x1)
) D
INNER JOIN #tmpSeg S ON D.trainingEventID = S.trainingEventID
AND D.segmentDate = S.segmentDate
AND D.nonProdHours = S.nonProdHrs
SELECT * FROM #tmpTrainers
-- Get segment-dependent locale info
INSERT INTO #tmpLocales
SELECT
S.teSegmentID
, D.trainingEventID
, D.locale
, D.teammates
, D.leaders
FROM (
SELECT
ParamValues.x1.value('locale[1]', 'INT') AS locale
, ParamValues.x1.value('teammates[1]', 'INT') AS teammates
, ParamValues.x1.value('leaders[1]', 'INT') AS leaders
, ParamValues.x1.value('../../trainingEventID[1]', 'INT') AS trainingEventID
, ParamValues.x1.value('../segmentDate[1]', 'DATE') AS segmentDate
, ParamValues.x1.value('../hours[1]', 'INT') AS nonProdHours
FROM #xml.nodes('/root/segment/details') AS ParamValues(x1)
) D
INNER JOIN #tmpSeg S ON D.trainingEventID = S.trainingEventID
AND D.segmentDate = S.segmentDate
AND D.nonProdHours = S.nonProdHrs
SELECT *
FROM #tmpLocales

Your last SELECT is still iterating through the <segment> nodes, of which there are only 3. You need to shed it to the <details> level by using another CROSS APPLY:
-- Now, we join on our temp table and insert the Segment Details
SELECT s.teSegmentID,
D.Detail.value('locale[1]', 'INT') AS localeID,
D.Detail.value('teammates[1]', 'INT') AS teammates,
D.Detail.value('leaders[1]', 'INT') AS leaders,
ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT') AS eventID,
ParamValues.x1.value('(segmentDate/text())[1]', 'DATE') AS date,
ParamValues.x1.value('(hours/text())[1]', 'INT') AS hours
FROM #tmpSeg AS s
INNER JOIN #xml.nodes('/root/segment') AS ParamValues(x1)
CROSS APPLY ParamValues.x1.nodes('details') AS D(Detail)
ON s.trainingEventID = ParamValues.x1.value('(../trainingEventID/text())[1]', 'INT')
AND s.segmentDate = ParamValues.x1.value('(segmentDate/text())[1]', 'DATE')
AND s.nonProdHrs = ParamValues.x1.value('(hours/text())[1]', 'INT')

Related

Create XML nodes for values in SQL

I'm having an issue to create XML nodes. Help is much appreciated !
This is a sample code
declare #tbl as table
(
employeeName nvarchar(50),
payFrequency nvarchar(50)
)
insert into #tbl
select 'John', 'Monthly'
union
select 'Carl', 'Biweekly'
select
employeeName AS 'Company/Employee',
payFrequency AS 'Company/PayFrequency'
from #tbl
for xml path ('employees'), root('paySchedule')
above code creates this output:
<paySchedule>
<employees>
<Company>
<Employee>John</Employee>
<PayFrequency>Monthly</PayFrequency>
</Company>
</employees>
<employees>
<Company>
<Employee>Carl</Employee>
<PayFrequency>Biweekly</PayFrequency>
</Company>
</employees>
</paySchedule>
I want to get the "paymentFrequency" values as a node. Is there a way to do this?
<paySchedule>
<employees>
<Company>
<Employee>John</Employee>
<PayFrequency>
<Monthly/>
</PayFrequency>
</Company>
</employees>
<employees>
<Company>
<Employee>Carl</Employee>
<PayFrequency>
<Biweekly/>
</PayFrequency>
</Company>
</employees>
</paySchedule>
You can use a CASE conditional for each possibility, returning an empty string when you want that node, and null otherwise.
SELECT
t.employeeName AS [Company/Employee],
CASE WHEN t.payFrequency = 'Monthly' THEN '' END AS [Company/PayFrequency/Monthly],
CASE WHEN t.payFrequency = 'Biweekly' THEN '' END AS [Company/PayFrequency/Biweekly]
FROM #tbl t
FOR XML PATH('employees'), ROOT('paySchedule'), TYPE;
You can do this also in a nested FOR XML.
SELECT
t.employeeName AS [Company/Employee],
(
SELECT
CASE WHEN t.payFrequency = 'Monthly' THEN '' END AS Monthly,
CASE WHEN t.payFrequency = 'Biweekly' THEN '' END AS Biweekly
FOR XML PATH(''), TYPE
) AS [Company/PayFrequency]
FROM #tbl t
FOR XML PATH('employees'), ROOT('paySchedule'), TYPE;
db<>fiddle
Note that <Monthly></Monthly> and <Monthly /> are semantically equivalent.
Please try the following solution.
It is using XQuery's FLWOR expression to compose the desired XML.
SQL
-- DDL and sample data population, start
DECLARE #tbl as table (employeeName NVARCHAR(50), payFrequency NVARCHAR(50));
INSERT INTO #tbl VALUES
('John', 'Monthly'),
('Carl', 'Biweekly');
-- DDL and sample data population, end
SELECT (
SELECT * FROM #tbl
FOR XML PATH('r'), TYPE, ROOT('root')
).query('<paySchedule>
{
for $r in /root/r
return <employees>
<Company>
<Employee>{data($r/employeeName)}</Employee>
<PayFrequency>
{
if ($r/payFrequency/text()="Monthly") then <Monthly/>
else <Biweekly/>
}
</PayFrequency>
</Company>
</employees>
}
</paySchedule>');
Output
<paySchedule>
<employees>
<Company>
<Employee>John</Employee>
<PayFrequency>
<Monthly />
</PayFrequency>
</Company>
</employees>
<employees>
<Company>
<Employee>Carl</Employee>
<PayFrequency>
<Biweekly />
</PayFrequency>
</Company>
</employees>
</paySchedule>

Iterate values on xml nodes SQL Server

I'm trying to extract all the values ​​from each XML node. For example for the <Cash> node, I would like to recover the value (0,1) but I can not get it
CREATE TABLE #TableXML (Col1 INT PRIMARY KEY, Col2 XML)
INSERT INTO #TableXML
VALUES (1,
'<CookedUP>
<Evenement Calcul="16">
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>0</Cash>
<CashInterest>0</CashInterest>
<CashSource>Undefined</CashSource>
<Code>A</Code>
<SmallAmount>0</SmallAmount>
<SmallAmountType>Undefined</SmallAmountType>
</Cookie>
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>1</Cash>
<CashInterest>2</CashInterest>
<CashSource>Undefined</CashSource>
<Code>B</Code>
<SmallAmount>1</SmallAmount>
<SmallAmountType>1</SmallAmountType>
</Cookie>
</Evenement>
</CookedUP> '
)
WITH XMLNAMESPACES ('http://services.ariel.morneausobeco.com/2007/02' AS ns)
SELECT
b.Col1,
x.XmlCol.value('(ns:Cookie/ns:SmallAmount/text())[2]', 'int') AS SmallAmount,
x.XmlCol.value('(ns:Cookie/ns:Cash/text())[2]', 'int') AS Cash
FROM
#TableXML b
CROSS APPLY
b.Col2.nodes('CookedUP/Evenement') x(XmlCol);
Small tweak to what you already have and you got it.
The namespaces doesn't come into play until the Cookie node.
I update the example to a table variable, give this a try:
DECLARE #TableXML TABLE(Col1 int primary key, Col2 xml)
Insert into #TableXML values ( 1,
'<CookedUP>
<Evenement Calcul="16">
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>0</Cash>
<CashInterest>0</CashInterest>
<CashSource>Undefined</CashSource>
<Code>A</Code>
<SmallAmount>0</SmallAmount>
<SmallAmountType>Undefined</SmallAmountType>
</Cookie>
<Cookie xmlns="http://services.ariel.morneausobeco.com/2007/02" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<AlternateOptionTypeCodes />
<Cash>1</Cash>
<CashInterest>2</CashInterest>
<CashSource>Undefined</CashSource>
<Code>B</Code>
<SmallAmount>1</SmallAmount>
<SmallAmountType>1</SmallAmountType>
</Cookie>
</Evenement>
</CookedUP> '
)
;WITH XMLNAMESPACES ('http://services.ariel.morneausobeco.com/2007/02' AS ns)
SELECT b.Col1
--Also include the namespaces here with ns: for each "field" you are after contained within Cookie
,XmlCol.value('(./ns:SmallAmount)[1]', 'int') AS SmallAmount
,XmlCol.value('(./ns:Cash)[1]', 'int') AS Cash
,XmlCol.value('(./ns:Code)[1]', 'nvarchar(10)') AS Code
,XmlCol.value('(./ns:CashSource)[1]', 'nvarchar(10)') AS CashSource
FROM #TableXML b
CROSS APPLY b.Col2.nodes('/CookedUP/Evenement/ns:Cookie') AS x(XmlCol) --ns, the namespace, only at Cookie node.

How to insert null value in a table from a XML?

I have a table which has several columns (Int, Bool) as nullable. I have one stored procedure which is taking XML as the input parameter. I'm trying to pass null values of some of these columns, But its inserting 0 instead of null.
declare #temp XML;
set #temp = '
<ArrayOfTestFileEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TestFileEntity>
<TestId xsi:nil="true" />
<MainTestNo xsi:nil="true" />
<TestCode xsi:nil="true" />
<TestCode1 />
<FlgTemp xsi:nil="true" />
</TestFileEntity>
</ArrayOfTestFileEntity>'
declare #xmlInput as XML = #temp;
declare #xmlOutput as XML;
BEGIN
SET NOCOUNT ON;
DECLARE #insertedTable table(Id int);
MERGE INTO Test AS Trg USING (
SELECT
d.x.value('TestId[1]', 'int') AS TestId,
d.x.value('MainTestNo[1]', 'int') AS MainTestNo,
d.x.value('TestCode[1]', 'int') AS TestCode,
d.x.value('TestCode1[1]', 'int') AS TestCode1,
d.x.value('FlgTemp[1]', 'bit') AS FlgTemp
FROM
#xmlInput.nodes('/ArrayOfTestFileEntity/TestFileEntity') AS d(x)
) AS Src ON Trg.Id = Src.Id
WHEN Matched THEN
UPDATE SET
Trg.TestId = Src.TestId,
Trg.MainTestNo = Src.MainTestNo,
Trg.TestCode = Src.TestCode,
Trg.TestCode1 = Src.TestCode1,
Trg.FlgTemp = Src.FlgTemp,
WHEN NOT matched BY TARGET THEN
INSERT
([TestId]
,[MainTestNo]
,[TestCode]
,[TestCode1]
,[FlgTemp])
VALUES
(Src.TestId,
Src.MainTestNo,
Src.TestCode,
Src.TestCode1,
Src.FlgTemp)
OUTPUT INSERTED.Id INTO #insertedTable;
set #xmlOutput = (SELECT * FROM #insertedTable for XML AUTO, ROOT('RowsUpserted'));
select #xmlOutput;
END
Basically you need to use [not(#xsi:nil = "true")] when you select value from xml.
So i have applied it in your query. So if you observed the xml you have noticed that i have added value 16 for <TestCode1> tag and keep rest of the tag as it is.
declare #temp XML;
set #temp = '
<ArrayOfTestFileEntity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<TestFileEntity>
<TestId xsi:nil="true" />
<MainTestNo xsi:nil="true" />
<TestCode xsi:nil="true" />
<TestCode1>16</TestCode1>
<FlgTemp xsi:nil="true" />
</TestFileEntity>
</ArrayOfTestFileEntity>'
DECLARE #xmlInput as XML = #temp;
--MERGE INTO Test AS Trg USING (
SELECT
d.x.value('TestId[1][not(#xsi:nil = "true")]', 'int') AS TestId,
d.x.value('MainTestNo[1][not(#xsi:nil = "true")]', 'int') AS MainTestNo,
d.x.value('TestCode[1][not(#xsi:nil = "true")]', 'int') AS TestCode,
d.x.value('TestCode1[1][not(#xsi:nil = "true")]', 'int') AS TestCode1,
d.x.value('FlgTemp[1][not(#xsi:nil = "true")]', 'bit') AS FlgTemp
FROM
#xmlInput.nodes('/ArrayOfTestFileEntity/TestFileEntity') AS d(x)

Create Xml with Same tag name

Table Name : sample
Column Name : id,name
Every Row Create Separate tag with inside.
Show the Xml value like this
<Details>
<id>1</id>
<name>na</name>
<Details>
<id>2</id>
<name>aa</name>
</Details>
</Details>
I tried like this but its not working
select
id 'Details\id'
,name 'Details\name'
from sample
How do get that xml output?
It is hardcoded but should work:
DECLARE #x xml
SELECT #x = (
SELECT x+''
FROM (
SELECT '%details?%id?'+CAST(id as nvarchar(max))+'%/id?%name?'+name+'%/name?' x
FROM [sample] s
UNION ALL
SELECT '%/details?'
FROM [sample] s
) as t
FOR XML PATH('')
)
SELECT CAST(REPLACE(REPLACE((CAST(#x as nvarchar(max))),'%','<'),'?','>') as xml)
In [sample] table I got:
(1,'na'),
(2,'aa'),
(3,'sd')
Output:
<details>
<id>1</id>
<name>na</name>
<details>
<id>2</id>
<name>aa</name>
<details>
<id>3</id>
<name>sd</name>
</details>
</details>
</details>
EDIT
Also it could be done with recursive CTE:
DECLARE #x xml
;WITH rec AS (
SELECT CAST((
SELECT TOP 1 id,
[name]
FROM [sample]
ORDER BY id DESC
FOR XML PATH('details')
) as xml) as d,
1 as [Level]
UNION ALL
SELECT CAST((
SELECT id,
[name],
cast(r.d as xml)
FROM [sample]
WHERE s.id = id
FOR XML PATH('details')
) as xml) as d,
r.[Level]+1
FROM [sample] s
INNER JOIN rec r
ON s.id = CAST(r.d.query('/details/id/text()') as nvarchar(max))-1
)
SELECT TOP 1 WITH TIES d
FROM rec
ORDER BY [Level] desc
Same output.
You can use query like this:
SELECT
*,
(SELECT
*
FROM #details
WHERE id = 2
FOR xml PATH ('Details'), TYPE)
FROM #details
WHERE id = 1
FOR xml PATH ('Details')
For inner loop you can use CTE
Table creation scripts :
CREATE TABLE #details (
id int,
name varchar(10)
)
INSERT INTO #details (id, name)
VALUES (1, 'test'), (2, 'test2')

TSQL Inserting records from XML string

I have a SQL query that is inserting records into a table from an XML string that I pass to it. The string could contain 1 node or multiple so each one is a new record.
Here is my XML string:
<root>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/10/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS123</empID>
</trainer>
<trainer>
<empID>Dan123</empID>
</trainer>
</trainers>
</segment>
</data>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/25/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS123</empID>
</trainer>
<trainer>
<empID>Dan123</empID>
</trainer>
</trainers>
</segment>
</data>
</root>
Every segment is a new record that is added into the table.
Now, I have a separate table called trainers. For each trainer, I need to also insert a record into that table but it needs to have the last inserted record id of the segment.
Here is my query:
INSERT INTO myTable(trainingEventID, localeID, segmentDate, numofTeammates, nonProdHrs)
SELECT ParamValues.x1.value('trainingEventID[1]', 'INT'),
ParamValues.x1.value('localeID[1]', 'INT'),
ParamValues.x1.value('segmentDate[1]', 'DATE'),
ParamValues.x1.value('numOfTeammates[1]', 'INT'),
ParamValues.x1.value('nonProdHrs[1]', 'FLOAT')
FROM #xml.nodes('/root/data/segment') AS ParamValues(x1);
How can I go about inserting the trainers into another table with the record ID that was created from the segment insert?
Given the clarification of this statement in the question:
For each trainer, I need to also insert a record into that table but it needs to have the last inserted record id of the segment.
being (as found in the comments on the question):
There would be a total of 4 records inserted into the trainer table, 2 have the segment id of 1 and the other 2 with the segment id of 2.
The following will insert this data into related tables that have auto-incrementing IDs. In the sample data, I varied the EmpID values slightly to make it clearer that it is indeed working as expected.
DECLARE #DocumentID INT, #ImportData XML;
SET #ImportData = N'
<root>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/10/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS123</empID>
</trainer>
<trainer>
<empID>Dan123</empID>
</trainer>
</trainers>
</segment>
</data>
<data>
<segment>
<trainingEventID>9</trainingEventID>
<localeID>641</localeID>
<numOfTeammates>12</numOfTeammates>
<nonProdHrs>21</nonProdHrs>
<segmentDate>10/25/2014</segmentDate>
<trainers>
<trainer>
<empID>HUS1234</empID>
</trainer>
<trainer>
<empID>Dan1234</empID>
</trainer>
</trainers>
</segment>
</data>
</root>';
DECLARE #Segment TABLE (SegmentId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
TrainingEventID INT NOT NULL, -- Unique
LocaleID INT NOT NULL, -- Unique
NumOfTeammates INT,
NonProdHrs INT,
SegmentDate DATE); -- Unique
-- Ideally create UNIQUE INDEX with the 3 fields noted above
DECLARE #Trainer TABLE (TrainerId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
SegmentID INT NOT NULL, -- FK to Segment.SegmentID
EmpID VARCHAR(50) NOT NULL);
EXEC sp_xml_preparedocument #DocumentID OUTPUT, #ImportData;
-- First pass: extract "Segment" rows
INSERT INTO #Segment
(TrainingEventID, LocaleID, NumOfTeammates, NonProdHrs, SegmentDate)
SELECT TrainingEventID, LocaleID, NumOfTeammates, NonProdHrs, SegmentDate
FROM OPENXML (#DocumentID, N'/root/data/segment', 2)
WITH (TrainingEventID INT './trainingEventID/text()',
LocaleID INT './localeID/text()',
NumOfTeammates INT './numOfTeammates/text()',
NonProdHrs INT './nonProdHrs/text()',
SegmentDate DATE './segmentDate/text()');
-- Second pass: extract "Trainer" rows
INSERT INTO #Trainer (SegmentID, EmpID)
SELECT seg.SegmentID, trnr.EmpID
FROM OPENXML (#DocumentID, N'/root/data/segment/trainers/trainer', 2)
WITH (TrainingEventID INT '../../trainingEventID/text()',
LocaleID INT '../../localeID/text()',
SegmentDate DATE '../../segmentDate/text()',
EmpID VARCHAR(50) './empID/text()') trnr
INNER JOIN #Segment seg
ON seg.[TrainingEventID] = trnr.[TrainingEventID]
AND seg.[LocaleID] = trnr.[LocaleID]
AND seg.[SegmentDate] = trnr.[SegmentDate];
EXEC sp_xml_removedocument #DocumentID;
-------------------
SELECT * FROM #Segment ORDER BY [SegmentID];
SELECT * FROM #Trainer ORDER BY [SegmentID];
Output:
SegmentId TrainingEventID LocaleID NumOfTeammates NonProdHrs SegmentDate
1 9 641 12 21 2014-10-10
2 9 641 12 21 2014-10-25
TrainerId SegmentID EmpID
1 1 HUS123
2 1 Dan123
3 2 HUS1234
4 2 Dan1234
References:
OPENXML
sp_xml_preparedocument
sp_xml_removedocument

Resources