Insert XML in SQL Server - 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:

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);

Stored procedure which writes xml into a DB, T-SQL

I am trying to write a stored procedure which writes xml into a DB, T-SQL.
Here is my sample xml (which will have a significant number of <RECORD>s in prod environment):
<?xml version="1.0" encoding="windows-1251"?>
<DATA FORMAT_VERSION="1.0">
<RECORD>
<NAME>МІЖНАРОДНА ГРОМАДСЬКА ОРГАНІЗАЦІЯ МІЖНАРОДНА АКАДЕМІЯ БІОЕНЕРГОТЕХНОЛОГІЙ</NAME>
<SHORT_NAME>МАБЕТ</SHORT_NAME>
<EDRPOU>00011601</EDRPOU>
<ADDRESS>01001, м.Київ, Шевченківський район, ВУЛИЦЯ ПРОРІЗНА, будинок 8, офіс 426</ADDRESS>
<STAN>зареєстровано</STAN>
</RECORD>
</DATA>
I pass the path to the xml file in the #pathToXml parameter.
Here is my stored procedure:
CREATE PROCEDURE [dbo].[LegalContractorsDataSynchronize]
(
#pathToXml varchar
)
AS
BEGIN
BEGIN TRANSACTION
DELETE FROM [dbo].[LegalContractors];
INSERT INTO [dbo].[LegalContractors]([Code], [ShortName], [Name], [LegalAddress], [Status])
SELECT CONVERT([Code], [ShortName], [Name], [LegalAddress], [Status])
FROM OPENROWSET(BULK, #pathToXml, SINGLE_CLOB) as x
COMMIT TRANSACTION
END
I am using Entity Framework to call the stored procedure. The call just happens (with no errors), but the DB is not updated. I am very sure that I mistyped something in the INSERT statement. I followed this example.
Could someone point out how could I fill the three columns in my DB using the data from the respective elements in xml? The columns are Code, ShortName, Name, LegalAddress and Status.
UPDATE
After the answer being posted I tried the suggested solution. I am getting the error:
Msg 102, Level 15, State 1, Procedure LegalContractorsDataSynchronize, Line 15 [Batch Start Line 0]
Incorrect syntax near '#pathToXml'.
Here is my code:
CREATE PROCEDURE [dbo].[LegalContractorsDataSynchronize]
(
#pathToXml varchar
)
AS
BEGIN
BEGIN TRANSACTION
DELETE FROM [dbo].[LegalContractors];
;WITH XmlFile (xmlData) AS
(
SELECT TRY_CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK #pathToXml, SINGLE_BLOB) AS x
)
INSERT INTO [dbo].[LegalContractors] ([Code], [ShortName], [Name], [LegalAddress], [Status])
SELECT c.value('(EDRPOU/text())[1]','NVARCHAR(100)') AS [Code]
, c.value('(SHORT_NAME/text())[1]','NVARCHAR(512)') AS [ShortName]
, c.value('(NAME/text())[1]','NVARCHAR(2048)') AS [Name]
, c.value('(ADDRESS/text())[1]','NVARCHAR(2048)') AS [LegalAddress]
, c.value('(STAN/text())[1]','NVARCHAR(100)') AS [Status]
FROM XmlFile CROSS APPLY xmlData.nodes('/DATA/RECORD') AS t(c);
COMMIT TRANSACTION
END
Please try the following. To the best of my knowledge, OPENROWSET() doesn't accept file name parameter as a variable.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (
ID INT IDENTITY PRIMARY KEY,
Code NVARCHAR(50) NOT NULL,
ShortName NVARCHAR(100) NOT NULL,
[Name] NVARCHAR(100) NOT NULL,
LegalAddress NVARCHAR(100) NOT NULL,
[Status] NVARCHAR(50) NOT NULL
);
-- DDL and sample data population, end
-- Method #1
-- XML file is hardcoded
;WITH XmlFile (xmlData) AS
(
SELECT TRY_CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'c:\...\Ukraine.xml', /*CODEPAGE = '65001',*/ SINGLE_BLOB) AS x
)
INSERT INTO #tbl (Code, ShortName, [Name], LegalAddress, [Status])
SELECT c.value('(EDRPOU/text())[1]','NVARCHAR(50)') AS [Code]
, c.value('(SHORT_NAME/text())[1]','NVARCHAR(100)') AS [ShortName]
, c.value('(NAME/text())[1]','NVARCHAR(100)') AS [Name]
, c.value('(ADDRESS/text())[1]','NVARCHAR(100)') AS [LegalAddress]
, c.value('(STAN/text())[1]','NVARCHAR(50)') AS [Status]
FROM XmlFile CROSS APPLY xmlData.nodes('/DATA/RECORD') AS t(c);
-- test
SELECT * FROM #tbl;
-- Method #2
-- dynamic XML file name as a parameter
DECLARE #xml XML
, #sql NVARCHAR(MAX)
, #fileName VARCHAR(256) = 'c:\...\Ukraine.xml';
SET #sql = N'SELECT #xmlOut = XmlDoc FROM OPENROWSET (BULK ' + QUOTENAME(#fileName,NCHAR(39)) + ', SINGLE_BLOB) AS Tab(XmlDoc)';
EXEC master.sys.sp_executesql #sql, N'#xmlOut XML OUTPUT', #xmlOut = #xml OUTPUT;
INSERT INTO #tbl (Code, ShortName, [Name], LegalAddress, [Status])
SELECT c.value('(EDRPOU/text())[1]','NVARCHAR(50)') AS [Code]
, c.value('(SHORT_NAME/text())[1]','NVARCHAR(100)') AS [ShortName]
, c.value('(NAME/text())[1]','NVARCHAR(100)') AS [Name]
, c.value('(ADDRESS/text())[1]','NVARCHAR(100)') AS [LegalAddress]
, c.value('(STAN/text())[1]','NVARCHAR(50)') AS [Status]
FROM #xml.nodes('/DATA/RECORD') AS t(c);
-- test
SELECT * FROM #tbl;

insert row by row data in table via openxml stored procedure in MS SQL

I m trying to insert the data VIA XML Format. And same the XML format defined as below & i wants to insert the field row by row in SQL Table. But, none of address is inserting in SQL Table, ONLY Customer information is inserting in Dummy Table.
<XML>
<Customer>
<NAME>YOGESH</NAME>
<CONTACT>YOGESH SHARMA</CONTACT>
<Mobile>123456789</Mobile>
<Status>A</Status>
<MALE>1</MALE>
<Add>
<ADD1>
<Address>AHMEDABAD</Address>
<State>GUJARAT</State>
<City>AHMEDABAD</City>
<Pincode>380016</Pincode>
</ADD1>
<ADD2>
<Address>RAJKOT</Address>
<State>GUJARAT</State>
<City>RAJKOT</City>
<Pincode>360001</Pincode>
</ADD2>
</Add>
</Customer>
</XML>
MY SP AS BELOW :
ALTER PROCEDURE [dbo].[OPENXMLDUMMY]
#xmlCustomer NTEXT
AS
BEGIN
DECLARE #DOC INT;
EXEC sp_xml_preparedocument
#DOC OUTPUT,
#xmlCustomer;
INSERT INTO Dummy
(Name,
Contact,
Mobile,
Status,
Male,
InsertDate
)
SELECT XML.NAME,
XML.Contact,
XML.Mobile,
XML.Status,
XML.Male,
GETDATE()
FROM OPENXML(#DOC, '/XML/Customer', 2) WITH(Name VARCHAR(50),
Contact VARCHAR(75), Mobile BIGINT, Status VARCHAR(10), Male VARCHAR(10),
InsertDate DATETIME) XML;
INSERT INTO DummyExtd
(
ID,
Address,
State,
City,
Pincode
) SELECT (SELECT ID FROM DUMMY WHERE Name = Name),
XML.Address,
XML.State,
XML.City,
XML.Pincode
FROM OPENXML(#DOC, '/XML/Customer/Add',2) WITH (ID INT, Address
VARCHAR(50), State VARCHAR(50), City VARCHAR(50), Pincode INT) XML;
EXEC sp_xml_removedocument #DOC;
END;
So, i just want to insert the data as below format in SQL Tables:
ID Name Contact Mobile Status Male InsertDate
1 YOGESH YOGESH SHARMA 123456789 A 1 2017-07-26 13:28:30.957
ID Address State City Pincode
1 AHMEDABAD GUJARAT AHMEDABAD 380016
1 RAJKOT GUJARAT RAJKOT 360001
So, what is the issue in my current stored procedure & needs to correct it.
Thanking you
Yogesh
Here I made one demo for the same. Please look into this.
Firstly, I created two tables which are Customer(your table name Dummy) and Customer_Address(your table name DummyText). They are look like below snaps.
Table : Customer
Table : Customer_Address
Below is your updated store procedure.
ALTER PROCEDURE [dbo].[OPENXMLDUMMY]
#xmlCustomer NTEXT
AS
BEGIN
DECLARE #DOC INT;
Declare #CustId INT;
EXEC sp_xml_preparedocument
#DOC OUTPUT,
#xmlCustomer;
INSERT INTO Customer(Name, Contact, Mobile, Status, Male, InsertDate)
SELECT XML.[NAME], XML.Contact, XML.Mobile, XML.Status, XML.Male, GETDATE() AS InsertDate
FROM OPENXML(#DOC, '/XML/Customer', 2) WITH(NAME VARCHAR(50), CONTACT VARCHAR(75), Mobile BIGINT, Status VARCHAR(10), MALE VARCHAR(10), InsertDate DATETIME) XML;
SET #CustId = SCOPE_IDENTITY()
INSERT INTO Customer_Address(Cust_Id, Address, State, City, Pincode)
SELECT #CustId AS Cust_Id, XML.Address, XML.State, XML.City, XML.Pincode
FROM OPENXML(#DOC, '/XML/Customer/Add/ADD1',2) WITH
(
Address VARCHAR(50),
State VARCHAR(50),
City VARCHAR(50),
Pincode INT
) XML;
INSERT INTO Customer_Address(Cust_Id, Address, State, City, Pincode)
SELECT #CustId AS Cust_Id, XML.Address, XML.State, XML.City, XML.Pincode
FROM OPENXML(#DOC, '/XML/Customer/Add/ADD2',2) WITH
(
Address VARCHAR(50),
State VARCHAR(50),
City VARCHAR(50),
Pincode INT
) XML;
EXEC sp_xml_removedocument #DOC;
END
Using this procedure I executed your sample xml data and it looks like below entries in both tables.
Your issue is at
(SELECT ID FROM DUMMY WHERE Name = Name)
It returns all values from DUMMY table, and causes error.
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
Solution:
-- After the first insert
Declare #DummyId int = scope_identity() -- get new inserted id from dummy
INSERT INTO DummyExtd
(
ID,
Address,
State,
City,
Pincode
) SELECT
#DummyId,
XML.Address,
XML.State,
XML.City,
XML.Pincode
FROM OPENXML(#DOC, '/XML/Customer/Add',2) WITH
( -- ID INT, --- Remove it, ID tag doesn't exist in your xml
Address VARCHAR(50),
State VARCHAR(50),
City VARCHAR(50),
Pincode INT
) XML;

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.

Import XML with Attribute to SQL Server Table

I can't get the value for the XML attribute 'Country' into my table.
What am I doing wrong?
Here is my XML:
<?xml version="1.0" encoding="utf-8"?>
<CustomerDetails>
<PersonalInfo Country="USA">
<CustID>1001</CustID>
<CustLastName>Smith</CustLastName>
<DOB>2011-05-05T09:25:48.253</DOB>
<Address>
<Addr1>100 Smith St.</Addr1>
<City>New York</City>
</Address>
</PersonalInfo>
</CustomerDetails>
Here is my SQL:
Drop table #Cust
CREATE TABLE #Cust
(CustID INT, CustLastName VARCHAR(10)
, DOB DATETIME, Addr1 VARCHAR(100), City VARCHAR(10), Country VARCHAR(20))
insert into #Cust
select
c3.value('CustID[1]','int'),
c3.value('CustLastName[1]','varchar(10)'),
c3.value('DOB[1]','DATETIME'),
c3.value('(Address/Addr1)[1]','VARCHAR(100)'),
c3.value('(Address/City)[1]','VARCHAR(10)'),
c3.value('Country[1]','VARCHAR(20)')
from
(
select
cast(c1 as xml)
from
OPENROWSET (BULK 'C:\Users\wattronts\Documents\XMLImportTest.xml',SINGLE_BLOB) as T1(c1)
)as T2(c2)
cross apply c2.nodes('/CustomerDetails/PersonalInfo') T3(c3)
Select * from #Cust
Thanks for your help.
Use # to specify that you want an attribute.
T3.c3.value('#Country', 'varchar(50)')

Resources