TSQL Inserting records from XML string - sql-server

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

Related

Insert XML data into multiple tables in SQL Server

I have a XML like below :
<Employees>
<Employee>
<AccountInfo>
<AccountNumber>1234567</AccountNumber>
<AccountType>Test</AccountType>
</AccountInfo>
<DocumentType>Test Doc</DocumentType>
<Date>12/01/2020</Date>
<Description>Test Description</Description>
<ImageFileType>pdf</ImageFileType>
<ImageFileName>321.PDF</ImageFileName>
<AdditionalInfo>
<FieldName>docDescription</FieldName>
<FieldValue>ABC XYZ</FieldValue>
</AdditionalInfo>
<AdditionalInfo>
<FieldName>Creation Date</FieldName>
<FieldValue>12/01/2020</FieldValue>
</AdditionalInfo>
<AdditionalInfo>
<FieldName>Department Code</FieldName>
<FieldValue>63</FieldValue>
</AdditionalInfo>
<AdditionalInfo>
<FieldName>ID No</FieldName>
<FieldValue>3214567</FieldValue>
</AdditionalInfo>
</Employee>
</Employees>
I want to insert this XML data into 3 tables EmployeeInfo, AccountInfo and AdditionalInfo with schema like this:
EmployeeInfo
(
EmployeeNumber Int Identity(1,1) NOT NULL,
DocumentType varchar(500) NULL,
[Description] varchar(500) NULL,
ImageFileName varchar(500) NULL,
ImageFileType varchar(500) NULL,
[Date] varchar(500) NULL
);
AccountInfo
(
EmployeeNumber int NOT NULL,
AccountNumber varchar(500) NULL,
AccountType varchar(500) NULL
);
AdditionalInfo
(
EmployeeNumber int NOT NULL,
FieldName varchar(500) NULL,
FieldValue varchar(500) NULL
);
EmployeeNumber column is used for linking AccountInfo and AdditionalInfo table with EmployeeInfo.
AccountInfo table will get below node:
<AccountInfo>
<AccountNumber>1234567</AccountNumber>
<AccountType>Test</AccountType>
</AccountInfo>
The AdditionalInfo table will get these XML nodes:
<AdditionalInfo>
<FieldName>docDescription</FieldName>
<FieldValue>ABC XYZ</FieldValue>
</AdditionalInfo>
<AdditionalInfo>
<FieldName>Creation Date</FieldName>
<FieldValue>12/01/2020</FieldValue>
</AdditionalInfo>
<AdditionalInfo>
<FieldName>Department Code</FieldName>
<FieldValue>63</FieldValue>
</AdditionalInfo>
<AdditionalInfo>
<FieldName>ID No</FieldName>
<FieldValue>3214567</FieldValue>
</AdditionalInfo>
Rest xml node are inserted into EmployeeInfo.
I tried with the query shown here. I'm able to get xml data and insert it into main table EmployeeInfo, but not able to link AdditionalInfo and AccountInfo with identity generated in EmployeeInfo table.
Note: I have multiple employee nodes in the xml.
DECLARE #EmpNumber int
DECLARE #x xml
SELECT #x = X FROM OPENROWSET (BULK 'C:\Test\Sample.xml', SINGLE_BLOB) AS EmpInfo(X)
DECLARE #hdoc int
EXEC sp_xml_preparedocument #hdoc OUTPUT, #x
INSERT INTO EmployeeInfo (DocumentType, [Description], ImageFileName, ImageFileType, [Date])
SELECT * FROM OPENXML (#hdoc, '/Employees/Employee', 2)
WITH ( DocumentType varchar(500), [Description] varchar(500), ImageFileName varchar(500), ImageFileType varchar(500), [Date] varchar(500))
SELECT #EmpNumber=SCOPE_IDENTITY()
INSERT INTO AccountInfo ([EmployeeNumber],[AccountNumber], [AccountType])
SELECT #EmpNumber, *
FROM OPENXML (#hdoc, '/Employees/Employee/AccountInfo', 2)
WITH (AccountNumber varchar(500), AccountType varchar(500))
INSERT INTO AdditionalInfo ([EmployeeNumber],[FieldName], [FieldValue])
SELECT #EmpNumber, *
FROM OPENXML (#hdoc, '/Employees/Employee/AdditionalInfo', 2)
WITH (
FieldName varchar(5000), FieldValue varchar(5000)
)
EXEC sp_xml_removedocument #hdoc
Can someone help me out in this. Thanks in Advance.
Here is a conceptual example how to do it.
Two tables, state as a parent, and city as a child, with one-to-many relationship. Primary keys are IDENTITY based.
INSERT into a parent table generates new IDENTITY values that are captured and stored in a table variable, and later used to INSERT into a child table to preserve foreign key constraint.
SQL
-- DDL and sample data population, start
USE tempdb;
GO
DROP TABLE IF EXISTS #city;
DROP TABLE IF EXISTS #state;
-- parent table
CREATE TABLE #state (
stateID INT IDENTITY PRIMARY KEY,
stateName VARCHAR(30),
abbr CHAR(2),
capital VARCHAR(30)
);
-- child table (1-to-many)
CREATE TABLE #city (
cityID INT IDENTITY,
stateID INT NOT NULL FOREIGN KEY REFERENCES #state(stateID),
city VARCHAR(30),
[population] INT,
PRIMARY KEY (cityID, stateID, city)
);
-- mapping table to preserve IDENTITY ids
DECLARE #idmapping TABLE (GeneratedID INT PRIMARY KEY,
NaturalID VARCHAR(20) NOT NULL UNIQUE);
DECLARE #xml XML =
N'<root>
<state>
<StateName>Florida</StateName>
<Abbr>FL</Abbr>
<Capital>Tallahassee</Capital>
<cities>
<city>
<city>Miami</city>
<population>470194</population>
</city>
<city>
<city>Orlando</city>
<population>285713</population>
</city>
</cities>
</state>
<state>
<StateName>Texas</StateName>
<Abbr>TX</Abbr>
<Capital>Austin</Capital>
<cities>
<city>
<city>Houston</city>
<population>2100263</population>
</city>
<city>
<city>Dallas</city>
<population>5560892</population>
</city>
</cities>
</state>
</root>';
-- DDL and sample data population, end
;WITH rs AS
(
SELECT stateName = p.value('(StateName/text())[1]', 'VARCHAR(30)'),
abbr = p.value('(Abbr/text())[1]', 'CHAR(2)'),
capital = p.value('(Capital/text())[1]', 'VARCHAR(30)')
FROM #xml.nodes('/root/state') AS t(p)
)
MERGE #state AS o
USING rs ON 1 = 0
WHEN NOT MATCHED THEN
INSERT(stateName, abbr, capital)
VALUES(rs.stateName, rs.Abbr, rs.Capital)
OUTPUT inserted.stateID, rs.stateName
INTO #idmapping (GeneratedID, NaturalID);
;WITH Details AS
(
SELECT NaturalID = p.value('(StateName/text())[1]', 'VARCHAR(30)'),
city = c.value('(city/text())[1]', 'VARCHAR(30)'),
[population] = c.value('(population/text())[1]', 'INT')
FROM #xml.nodes('/root/state') AS A(p) -- parent
CROSS APPLY A.p.nodes('cities/city') AS B(c) -- child
)
INSERT #city (stateID, city, [Population])
SELECT m.GeneratedID, d.city, d.[Population]
FROM Details AS d
INNER JOIN #idmapping AS m ON d.NaturalID = m.NaturalID;
-- test
SELECT * FROM #state;
SELECT * FROM #idmapping;
SELECT * FROM #city;
Avoid using sp_xml_preparedocument, OPENXML and sp_xml_removedocument because they are inefficient, often cause resource leakage when sp_xml_removedocument is forgotten, and encourage RBAR-like constructs in what's supposed to be a set-based RDBMS.
Prefer to use nodes() and value() where possible, such as with the following...
insert dbo.EmployeeInfo (DocumentType, [Description], ImageFileName, ImageFileType, [Date])
select
Employee.value('(DocumentType/text())[1]', 'varchar(500)'),
Employee.value('(Description/text())[1]', 'varchar(500)'),
Employee.value('(ImageFileName/text())[1]', 'varchar(500)'),
Employee.value('(ImageFileType/text())[1]', 'varchar(500)'),
Employee.value('(Date/text())[1]', 'varchar(500)')
from #xml.nodes('/Employees/Employee') root(Employee);
declare #EmpNumber int = SCOPE_IDENTITY();
insert dbo.AccountInfo ([EmployeeNumber], [AccountNumber], [AccountType])
select
#EmpNumber,
AccountInfo.value('(AccountNumber/text())[1]', 'varchar(500)'),
AccountInfo.value('(AccountType/text())[1]', 'varchar(500)')
from #xml.nodes('/Employees/Employee/AccountInfo') root(AccountInfo);
insert dbo.AdditionalInfo ([EmployeeNumber], [FieldName], [FieldValue])
select
#EmpNumber,
AdditionalInfo.value('(FieldName/text())[1]', 'varchar(500)'),
AdditionalInfo.value('(FieldValue/text())[1]', 'varchar(500)')
from #xml.nodes('/Employees/Employee/AdditionalInfo') root(AdditionalInfo);

How to combine two FOR XML AUTO into 1 XML?

We are using SQL Server 2012.
Table myTbl has a one to many relationship to table myAllocation
Table ABC_myTbl has a one to many relationship to table ABC_myAllocation
The below query combined 2 FOR XML AUTO into 1 XML, but the problem is ID, SystemSource, Manager are included in element TradeTicket instead of on their own, and accountManager, unitPrice are included in element allocationRow instead of on their own.
Thank you
SELECT '<?xml version="1.0"?>'+
(SELECT
( SELECT trTicket.[id],trTicket.[manager],'PFM' as SystemSource
,allocationRow.accountNumber,allocationRow.unitPrice
FROM myTbl AS trTicket
LEFT JOIN myAllocation AS allocationRow ON allocationRow.trade_ticket_id=trTicket.id
WHERE trTicket.ID = 8779631
ORDER BY trTicket.id,allocationRow.AccountNumber
FOR XML AUTO, type)
,
(
SELECT trTicket.[id],trTicket.[manager],'CRD' as SystemSource
,allocationRow.accountNumber,allocationRow.unitPrice
FROM ABC_myTbl AS trTicket
LEFT JOIN ABC_myAllocation AS allocationRow ON allocationRow.trade_ticket_id=trTicket.id
WHERE trTicket.ID = 8
ORDER BY trTicket.id,allocationRow.AccountNumber
FOR XML AUTO, type)
FOR XML PATH('trTickets'), ELEMENTS) AS XMLResult
This is the current result:
<?xml version="1.0"?>
<trTickets>
<trTicket id="8779631" SystemSource="PFM" manager="MCM">
<allocationRow accountNumber="292 " unit_Price="300"/>
</trTicket>
<trTicket id="8" SystemSource="CRD" manager="DOYLE">
<allocationRow unitPrice="100" accountNumber="F11 "/>
<allocationRow unitPrice="200" accountNumber="F22 "/>
</trTicket>
</trTickets>
This is the desired result that I am looking for:
<?xml version="1.0"?>
<trTickets>
<trTicket>
<id>8</id>
<manager>DOYLE</manager>
<SystemSource>CRD</SystemSource>
<allocationRow>
<accountNumber>F11</accountNumber>
<unitPrice>100</unitPrice>
</allocationRow>
<allocationRow>
<accountNumber>F22</accountNumber>
<unitPrice>200</unitPrice>
</allocationRow>
</trTicket>
<trTicket>
<id>8779631</id>
<manager>MCM</manager>
<SystemSource>PFM</SystemSource>
<allocationRow>
<accountNumber>292</accountNumber>
<unitPrice>300</unitPrice>
</allocationRow>
</trTicket>
</trTickets>
Data sample:
Table ABC_myTbl:
ID Manager
-----------
8 DOYLE
Table ABC_myAllocation:
accountNumber unitPrice
-------------------------
F11 100
F22 200
Table myTbl:
ID Manager
---------------
8779631 MCM
Table myAllocation:
accountNumber unitPrice
--------------------------
292 300
DDL for the tables and their data:
CREATE TABLE dbo.ABC_myTbl
(
ID INT NOT NULL,
MANAGER VARCHAR(10) NOT NULL
)
CREATE TABLE dbo.myTbl
(
ID INT NOT NULL,
MANAGER VARCHAR(10) NOT NULL
)
CREATE TABLE dbo.ABC_myAllocation
(
accountNumber VARCHAR(10) NOT NULL,
unitprice NUMERIC(10, 3) NOT NULL
)
CREATE TABLE dbo.myAllocation
(
accountNumber VARCHAR(10) NOT NULL,
unitprice NUMERIC(10, 3) NOT NULL
)
INSERT INTO dbo.ABC_myTbl VALUES (8,'DOYLE')
INSERT INTO dbo.ABC_myAllocation VALUES ('F11',100)
INSERT INTO dbo.ABC_myAllocation VALUES ('F22',200)
INSERT INTO dbo.myTbl VALUES (8779631,'MCM')
INSERT INTO dbo.myAllocation VALUES ('292',300)
I didn't wait for your DDL and sample data population. So I created a conceptual sample for you. Please pay attention that the tables have implied relationships and they are used in the WHERE clauses.
SQL
-- DDL and sample data population, start
DECLARE #tbl1 TABLE (ID INT PRIMARY KEY, Manager VARCHAR(20));
INSERT INTO #tbl1 (ID, Manager) VALUES
(8, 'DOYLE'),
(9, 'XYZ');
DECLARE #tbl1Child TABLE (accountNumber CHAR(3) PRIMARY KEY, ParentID INT, unitPrice DECIMAL(10,2));
INSERT INTO #tbl1Child (accountNumber, ParentID, unitPrice) VALUES
('F11', 8, 100)
,('F22', 8, 200)
,('F70', 9, 770);
DECLARE #tbl2 TABLE (ID INT PRIMARY KEY, Manager VARCHAR(20));
INSERT INTO #tbl2 (ID, Manager) VALUES
(8779631, 'MCM')
,(8779555, 'TTT');
DECLARE #tbl2Child TABLE (accountNumber CHAR(3) PRIMARY KEY, ParentID INT, unitPrice DECIMAL(10,2));
INSERT INTO #tbl2Child (accountNumber, ParentID, unitPrice) VALUES
('292', 8779631, 300)
,('255', 8779555, 500);
-- DDL and sample data population, end
SELECT TOP(1) NULL
, (
SELECT *
, (
SELECT * FROM #tbl1Child AS c
WHERE p.ID = c.ParentID
FOR XML PATH('allocation_row'), TYPE
)
FROM #tbl1 AS p
FOR XML PATH('tradeTicket'), TYPE
)
, (
SELECT *
, (
SELECT * FROM #tbl2Child AS c
WHERE p.ID = c.ParentID
FOR XML PATH('allocation_row'), TYPE
)
FROM #tbl2 AS p
FOR XML PATH('tradeTicket'), TYPE
)
FROM #tbl1
FOR XML PATH(''), TYPE, ROOT('tradeTickets');
Output
<tradeTickets>
<tradeTicket>
<ID>8</ID>
<Manager>DOYLE</Manager>
<allocation_row>
<accountNumber>F11</accountNumber>
<ParentID>8</ParentID>
<unitPrice>100.00</unitPrice>
</allocation_row>
<allocation_row>
<accountNumber>F22</accountNumber>
<ParentID>8</ParentID>
<unitPrice>200.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>9</ID>
<Manager>XYZ</Manager>
<allocation_row>
<accountNumber>F70</accountNumber>
<ParentID>9</ParentID>
<unitPrice>770.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>8779555</ID>
<Manager>TTT</Manager>
<allocation_row>
<accountNumber>255</accountNumber>
<ParentID>8779555</ParentID>
<unitPrice>500.00</unitPrice>
</allocation_row>
</tradeTicket>
<tradeTicket>
<ID>8779631</ID>
<Manager>MCM</Manager>
<allocation_row>
<accountNumber>292</accountNumber>
<ParentID>8779631</ParentID>
<unitPrice>300.00</unitPrice>
</allocation_row>
</tradeTicket>
</tradeTickets>

Querying XML Node with a where criteria

I have a table with following attributes:
[Students]
ID Class Data (xml)
1 Secondary XML Data
Below is a sample xml structure that I store on the data attribute.
<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>2</Rank>
</Data>
</Root>
I want to write a sql query which will give me following data from the table:
ID, Class and Rank 2 student Name
My Query is
Select ID,Class,Data.value('(/Root/Data/Name)[1]', 'NVARCHAR(3)') AS [Rank 2 Student Name]
FROM [Students]
This works fine, but hardcoding the the node number to 1 is not a good idea here becuase if I add a new Data node in my xml then the data will change. And this is a possibility. So is there a way I can specify that return Name for the node where Rank value is 2.
Thanks.
you can use this:
DECLARE #tbl TABLE (xmlcol xml )
INSERT INTO #tbl
VALUES ('<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>2</Rank>
</Data>
</Root>')
SELECT *
FROM
(
SELECT r.d.value ('(./Name/text())[1]', 'varchar(50)') [Name],
r.d.value ('(./Rank/text())[1]', 'varchar(50)') [Rank]
FROM #tbl
CROSS APPLY xmlcol.nodes ('/Root/Data') R(d)
) P
WHERE P.[Rank] = 2
which will output the following result:
Name Rank
Peter 2
You can apply filter in xml as follows:
DECLARE #Students TABLE(ID int, Class varchar(10), Data xml)
INSERT #Students VALUES (1,'A', '<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>2</Rank>
</Data>
</Root>')
SELECT ID,Class,Data.value('(/Root/Data[Rank=2]/Name)[1]', 'NVARCHAR(5)') AS [Rank 2 Student Name]
FROM #Students
Result
ID Class Rank 2 Student Name
----------- ---------- -------------------
1 A Peter
Edit. If Rank contains free text, use quotes:
DECLARE #Students TABLE(ID int, Class varchar(10), Data xml)
INSERT #Students VALUES (1,'A', '<Root>
<Data>
<Name>John</Name>
<Rank>1</Rank>
</Data>
<Data>
<Name>Peter</Name>
<Rank>0:Company name</Rank>
</Data>
</Root>')
SELECT ID,Class,Data.value('(/Root/Data[Rank="0:Company name"]/Name)[1]', 'NVARCHAR(5)') AS [Rank 2 Student Name]
FROM #Students

Insert XML in SQL Server

Good afternoon all.
Currently, I have make a small demo with XML in SQl Server.
I have table with name: tb_xml_demo(ID, Name, Descr)
And each time when I insert to this table. ID column like this:
001, 002, 003 ....
This is my procedure
alter proc sp_xml_demo_cud
#p_xml xml
as
begin
declare #dochandle int;
exec sp_xml_preparedocument #dochandle output,#p_xml;
insert into tb_xml_demo(id, name, descr)
select
(select
format(isnull(convert(int, max(id)), 0) + 1, '000')
from tb_xml_demo) id,
name,
descr
from
OPENXML(#dochandle,'/root/item',2)
with
(name nvarchar(50),
descr nvarchar(50),
crud varchar(1)
)
end;
And this is my xml:
exec sp_xml_demo_cud
'<root>
<item>
<name>9876543</name>
<descr>1sdfsd</descr>
</item>
<item>
<name>333</name>
<descr>333</descr>
</item>
</root>';
And this is result after executing the procedure:
id Name Descr
001 9876543 1sdfsd
001 333 333
Please help me.
Thanks a lot.
I would recommend doing this:
create your table with a ID INT IDENTITY(1,1) column to let SQL Server handle the generation of unique ID values
add a computed column (I called it PaddedID) that uses the system-generated, valid ID to display with leading zeroes
parse the XML with the built-in, native XQuery functionality (instead of the legacy OPENXML stuff which is notorious for memory leaks)
This gives me this code:
-- create your table
CREATE TABLE tb_xml_demo
(ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
PaddedID AS RIGHT('0000' + CAST(ID AS VARCHAR(4)), 4) PERSISTED,
Name VARCHAR(50),
Descr VARCHAR(100)
)
-- declare your INPUT XML document
DECLARE #input XML = '<root>
<item>
<name>9876543</name>
<descr>1sdfsd</descr>
</item>
<item>
<name>333</name>
<descr>333</descr>
</item>
</root>'
-- parse the XML using XQuery and insert the results into that newly created table
INSERT INTO dbo.tb_xml_demo
(Name, Descr)
SELECT
ItemName = xc.value('(name)[1]', 'varchar(50)'),
ItemDescr = xc.value('(descr)[1]', 'varchar(100)')
FROM
#input.nodes('/root/item') AS XT(XC)
-- select the values from the table
SELECT * FROM dbo.tb_xml_demo
and this results in an output of:

SQL Server: Import XML document with one-to-many relationship (insert into multiple tables)

I'm using SQL Server 2012 to import a simple XML document. Here's a sample of what the XML looks like:
<Orders>
<Order>
<Customer>Bob Smith</Customer>
<Address>123 Main St, Anytown, NY</Address>
<OrderItems>
<Item>
<ItemName>Table</ItemName>
<Quantity>1</Quantity>
</Item>
<Item>
<ItemName>Chair</ItemName>
<Quantity>4</Quantity>
</Item>
</OrderItems>
</Order>
<Order>
<Customer>Jane Doe</Customer>
<Address>456 Broadway Ave, Someplace, TX</Address>
<OrderItems>
<Item>
<ItemName>Banana Slicer</ItemName>
<Quantity>1</Quantity>
</Item>
</OrderItems>
</Order>
<Order>
<Customer>Joe Public</Customer>
<Address>789 Euclid Rd, Random, ID</Address>
<OrderItems>
<Item>
<ItemName>Hammer</ItemName>
<Quantity>1</Quantity>
</Item>
<Item>
<ItemName>Nails</ItemName>
<Quantity>50</Quantity>
</Item>
<Item>
<ItemName>Chisel</ItemName>
<Quantity>2</Quantity>
</Item>
</OrderItems>
</Order>
</Orders>
Note that each order can have one or more items in it.
Here are the destination tables (using temp tables for now):
CREATE TABLE dbo.#Order
(
[OrderID] int IDENTITY(1001,1) PRIMARY KEY,
[Customer] varchar(200) NOT NULL,
[Address] varchar(200) NOT null
);
CREATE TABLE dbo.#OrderItem
(
[OrderItemID] int IDENTITY(5001,1) PRIMARY KEY,
[OrderID] int
FOREIGN KEY REFERENCES dbo.#Order(OrderID)
ON DELETE CASCADE,
[ItemName] varchar(100) NOT NULL,
[Quantity] int NOT NULL
);
The above should be imported as follows:
OrderID Customer Address
------- -------- -------
1001 Bob Smith 123 Main St, Anytown, NY
1002 Jane Doe 456 Broadway Ave, Someplace, TX
1003 Joe Public 789 Euclid Rd, Random, ID
OrderItemID OrderID ItemName Quantity
----------- ------- -------- --------
5001 1001 Table 1
5002 1001 Chair 4
5003 1002 Banana Slicer 1
5004 1003 Hammer 1
5005 1003 Nails 50
5006 1003 Chisel 2
Is there a way to insert all the data into both the Order and the OrderItem tables without using a cursor? I'm not against using a cursor for this, but I would like to know if there is a simpler, set-based alternative. The tricky part is that OrderItem needs to know about the inserted OrderID of each order that was imported.
Here's my initial attempt at importing the data (using a cursor). I'm using temp tables (instead of permnanent tables) and hardcoding the XML to make it easier to just copy and run this code:
-------------------------------------------------------------------------------
-- Create Temp Tables
-------------------------------------------------------------------------------
IF OBJECT_ID('tempdb.dbo.#OrderItem') IS NOT NULL
DROP TABLE #OrderItem
GO
IF OBJECT_ID('tempdb.dbo.#Order') IS NOT NULL
DROP TABLE #Order
GO
CREATE TABLE dbo.#Order
(
[OrderID] int IDENTITY(1001,1) PRIMARY KEY,
[Customer] varchar(200) NOT NULL,
[Address] varchar(200) NOT null
);
CREATE TABLE dbo.#OrderItem
(
[OrderItemID] int IDENTITY(5001,1) PRIMARY KEY,
[OrderID] int
FOREIGN KEY REFERENCES dbo.#Order(OrderID)
ON DELETE CASCADE,
[ItemName] varchar(100) NOT NULL,
[Quantity] int NOT NULL
);
-------------------------------------------------------------------------------
-- Define Sample XML Document
-------------------------------------------------------------------------------
DECLARE #xml xml = '
<Orders>
<Order>
<Customer>Bob Smith</Customer>
<Address>123 Main St, Anytown, NY</Address>
<OrderItems>
<Item>
<ItemName>Table</ItemName>
<Quantity>1</Quantity>
</Item>
<Item>
<ItemName>Chair</ItemName>
<Quantity>4</Quantity>
</Item>
</OrderItems>
</Order>
<Order>
<Customer>Jane Doe</Customer>
<Address>456 Broadway Ave, Someplace, TX</Address>
<OrderItems>
<Item>
<ItemName>Banana Slicer</ItemName>
<Quantity>1</Quantity>
</Item>
</OrderItems>
</Order>
<Order>
<Customer>Joe Public</Customer>
<Address>789 Euclid Rd, Random, ID</Address>
<OrderItems>
<Item>
<ItemName>Hammer</ItemName>
<Quantity>1</Quantity>
</Item>
<Item>
<ItemName>Nails</ItemName>
<Quantity>50</Quantity>
</Item>
<Item>
<ItemName>Chisel</ItemName>
<Quantity>2</Quantity>
</Item>
</OrderItems>
</Order>
</Orders>';
-------------------------------------------------------------------------------
-- Query XML Document
-------------------------------------------------------------------------------
--SELECT
-- Tbl.Col.value('Customer[1]', 'varchar(200)') AS [Customer],
-- Tbl.Col.value('Address[1]', 'varchar(200)') AS [Address],
-- Tbl.Col.query('./OrderItems/Item') AS [ItemsXML]
--FROM
-- #xml.nodes('//Order') AS Tbl(Col)
-------------------------------------------------------------------------------
-- Import XML document (Attempt 1 - using a cursor)
-------------------------------------------------------------------------------
DECLARE #OrderNode xml;
DECLARE #OrderID int;
DECLARE cur CURSOR FAST_FORWARD READ_ONLY LOCAL FOR
SELECT Tbl.Col.query('.') FROM #xml.nodes('//Order') AS Tbl(Col)
OPEN cur
FETCH NEXT FROM cur INTO #OrderNode
WHILE ##FETCH_STATUS = 0 BEGIN
------------------------------------------
-- Import order
------------------------------------------
INSERT INTO #Order
(
[Customer],
[Address]
)
SELECT
Tbl.Col.value('Customer[1]', 'varchar(200)'),
Tbl.Col.value('Address[1]', 'varchar(200)')
FROM #OrderNode.nodes('Order') AS Tbl(Col);
------------------------------------------
-- Get the inserted order ID
------------------------------------------
SELECT #OrderID = SCOPE_IDENTITY();
------------------------------------------
-- Import order items
------------------------------------------
INSERT INTO #OrderItem
(
OrderID,
ItemName,
Quantity
)
SELECT
#OrderID,
Tbl.Col.value('ItemName[1]', 'varchar(100)'),
Tbl.Col.value('Quantity[1]', 'int')
FROM #OrderNode.nodes('Order/OrderItems/Item') AS Tbl(Col);
-------------------------------------------------------------------------------
-- Move on to next order
-------------------------------------------------------------------------------
FETCH NEXT FROM cur INTO #OrderNode
END
CLOSE cur
DEALLOCATE cur
-------------------------------------------------------------------------------
-- Show results
-------------------------------------------------------------------------------
SELECT * FROM #Order
SELECT * FROM #OrderItem
To extend on the comment by xQbert, if your sample XML data is in an XML variable #x, you can use the below to put all orders & order items into a temp table:
DECLARE #x XML = '... your sample xml ...';
SELECT Ord.n.value('for $i in . return count(../*[. << $i]) + 1', 'int') AS OrderNbr
, Ord.n.value('./Customer[1]','varchar(200)') AS Customer
, Ord.n.value('./Address[1]','varchar(200)') AS Address
, Item.n.value('./ItemName[1]','varchar(200)') AS ItemName
, Item.n.value('./Quantity[1]','int') AS Quantity
INTO #Orders
FROM #x.nodes('/Orders/Order') AS Ord(n)
CROSS APPLY Ord.n.nodes('./OrderItems/Item') AS Item(n);
Then you need to do two inserts, one to the parent table which includes DISTINCT master order records, preserving the identity values with an OUTPUT clause, and the second insert would include all order items and use the preserved PK values from the first insert. See this link:
How can I INSERT data into two tables simultaneously in SQL Server?
Building further upon Kevin Suchlicki's answer, if you declare your XML as an XML variable #x, then plug in the following, you will get the results in your original post:
DECLARE #Order TABLE ([OrderID] int IDENTITY(1001,1) PRIMARY KEY,
[Customer] varchar(200) NOT NULL,
[Address] varchar(200) NOT NULL)
DECLARE #OrderItem TABLE (
[OrderItemID] int IDENTITY(5001,1) PRIMARY KEY,
[OrderID] int,
[ItemName] varchar(100) NOT NULL,
[Quantity] int NOT NULL
)
INSERT INTO #Order (Customer, Address)
SELECT t.c.value('(Customer)[1]','varchar(200)') AS 'Customer',
t.c.value('(Address)[1]','varchar(200)') AS 'Address'
FROM #x.nodes('Orders/Order') t(c);
INSERT INTO #OrderItem (OrderID, ItemName, Quantity)
SELECT
Ordr.OrderID
, Item.n.value('./ItemName[1]','varchar(200)') AS ItemName
, Item.n.value('./Quantity[1]','int') AS Quantity
FROM #x.nodes('Orders/Order') Ord(n)
CROSS APPLY Ord.n.nodes('./OrderItems/Item') AS Item(n)
JOIN #Order Ordr ON Ordr.Customer = Ord.n.value('./Customer[1]','varchar(200)')
SELECT *
FROM #Order
SELECT *
FROM #OrderItem
Obviously table variables are used here, but you can just as easily swap them out for temp tables.

Resources