XML in TSQL: looping through each child node and insert them - sql-server

I have a SQL table receiving orders
<XMLGateway>
<Header>
....
</Header>
<Body>
<Orders>
<Order>
<ItemCode>315689</ItemCode>
<ProductName>Item1</ProductName>
</Order>
<Order>
<ItemCode>123456</ItemCode>
<ProductName>Product 1</ProductName>
</Order>
</Orders>
</Body>
I would then like to iterate through each order and insert them separately into my Orders table
insert into orders (ItemCode,ProductName) as separate records
Is there a simpler solution than a cursor?

You can try with this XQuery statement - no cursors needed for sure!
-- you have your XML in a SQL variable here
DECLARE #input XML = '<XMLGateway>
<Body>
<Orders>
<Order>
<ItemCode>315689</ItemCode>
<ProductName>Item1</ProductName>
</Order>
<Order>
<ItemCode>123456</ItemCode>
<ProductName>Product 1</ProductName>
</Order>
</Orders>
</Body>
</XMLGateway>'
-- Commented out the "INSERT" part, so you can try what the "SELECT" returns
-- INSERT INTO dbo.Orders (ItemCode, ProductName)
SELECT
-- pick out the "ItemCode" and "ProductName" subelements from the <Order> node
ItemCode = XC.value('(ItemCode/text())[1]', 'int'),
ProductName = XC.value('(ProductName/text())[1]', 'varchar(100)')
FROM
-- get the "<Order>" XML nodes, as a list of XML fragments
#input.nodes('/XMLGateway/Body/Orders/Order') AS XT(XC)

Related

How to query SQL Server XML column

I have a table which contains a XML column and I need to get a value from the XML.
<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>Reverify</Key>
<Value>0</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>
Sometimes Reverify key will be present in the XML document, and other times it won't. The document can contain other key / value pairs as well.
But RequestNumber key / value pair will always be present, but it might be the second or third key / value item in the document. So I could have:
<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>
Currently I am using this:
SELECT TOP 10 *
FROM dbo.myTable
WHERE Parameters.value('(/ArrayOfItem/Item/Value)[2]', 'varchar(max)') LIKE '%revision%'
ORDER BY Id DESC
But I was assuming that RequestNumber was always the 2nd Key/Value Item in the document, but I just learned that that is not always the case.
Let's say the table looks like:
CREATE TABLE dbo.myTable
(
Parameters XML NOT NULL,
Field1 VARCHAR(50) NULL
)
INSERT INTO dbo.myTable (Parameters, Field1)
VALUES
( '<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>Reverify</Key>
<Value>0</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>', -- XMLParameters - xml
'myText' -- Field1 - varchar(50)
)
and I want the value of /ArrayOfItem/Item/Value where the key is RequestNumber.
Thank you for your help.
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Parameters XML NOT NULL);
INSERT INTO #tbl (Parameters) VALUES
(N'<ArrayOfItem>
<Item>
<Key>Member_Claim_Id</Key>
<Value>1802538</Value>
</Item>
<Item>
<Key>Reverify</Key>
<Value>0</Value>
</Item>
<Item>
<Key>RequestNumber</Key>
<Value>First Request</Value>
</Item>
</ArrayOfItem>');
-- DDL and sample data population, end
DECLARE #param VARCHAR(30) = 'RequestNumber';
SELECT ID
, c.value('(Key/text())[1]', 'VARCHAR(30)') AS [Key]
, c.value('(Value/text())[1]', 'VARCHAR(30)') AS [Value]
FROM #tbl
CROSS APPLY Parameters.nodes('/ArrayOfItem/Item[Key[text()=sql:variable("#param")]]') AS t(c);
Output
+----+---------------+---------------+
| ID | Key | Value |
+----+---------------+---------------+
| 1 | RequestNumber | First Request |
+----+---------------+---------------+

T-SQL XPath query including Parent

This problem keeps messing around with my Friday afternoon:
I have this XML:
declare #xml as XML
set #xml =
'<fields>
<field>
<id>1</id>
<items>
<item>
<name>name1_1</name>
<value>value1_1</value>
</item>
<item>
<name>name1_2</name>
<value>value1_2</value>
</item>
</items>
</field>
<field>
<id>2</id>
<items>
<item>
<name>name2_1</name>
<value>value2_1</value>
</item>
<item>
<name>name2_2</name>
<value>value2_2</value>
</item>
</items>
</field>
</fields>'
Using T-SQL and XPath, I need a query to get this result:
id name value
1 name1_1 value1_1
1 name1_2 value1_2
2 name2_1 value2_1
2 name2_2 value2_2
I'm getting name and value with:
SELECT c.value('name[1]', 'nvarchar(255)') name,
c.value('value[1]', 'nvarchar(255)') value
FROM #xml.nodes('fields/field/items/item') t(c)
...but how to insert the parent column "id"?
Your own code uses .nodes() to get a derived table from repeating elements. In your case there are two levels of repeating elements:
many fields and within each field
many items
You have to use .nodes() twice:
SELECT fld.value(N'(id/text())[1]',N'int') AS FieldID
,itm.value(N'(name/text())[1]',N'nvarchar(max)') AS ItemName
,itm.value(N'(value/text())[1]',N'nvarchar(max)') AS ItemValue
FROM #xml.nodes(N'/fields/field') AS A(fld)
OUTER APPLY A.fld.nodes(N'items/item') AS B(itm);
The first .nodes() comes back with XML fragments, one for each field, the second node is called for each of these field-fragments to pick their items.
Use OUTER APPLY if there might be fields without <item> nodes and CROSS APPLY when you do not want to see fields without <item> nodes (similar to LEFT JOIN vs INNER JOIN)
Assumption: there is only one id element per field.
SELECT c.value('../../id[1]', 'int') id,
c.value('name[1]', 'nvarchar(255)') name,
c.value('value[1]', 'nvarchar(255)') value
FROM #xml.nodes('fields/field/items/item') t(c)
The .. operator means "select parent of node" in XPATH. So the query will select the parent of item, then the parent of items, then the first child node id

SQL Server 2014 - FOR XML AUTO avoid automatic node nesting

I'm trying to build some query to export data in XML and I build this query:
select
[invoice].*,
[rows].*,
[payment].payerID,
[items].picture
from InvoicesHeader [invoice]
join InvoicesRows [rows] on [rows].invoiceID=[invoice].invoiceID
join Payments [payments] on [payments].paymentID=[invoice].paymentID
join Items [items] on [items].itemID=[rows].itemID
FOR XML Auto, ROOT ('invoices'), ELEMENTS
and I got something like this as result
<invoices>
<invoice>
<ID>82</ID>
<DocType>R</DocType>
<DocYear>2017</DocYear>
<DocNumber>71</DocNumber>
<IssueDate>2017-07-17T15:17:30.237</IssueDate>
<OrderID>235489738019</OrderID>
...
<payments>
<payerID>3234423f33</payerID>
<rows>
<ID>163</ID>
<ItemID>235489738019</ItemID>
<Quantity>2</Quantity>
<Price>1</Price>
<VATCode>22</VATCode>
<Color>-</Color>
<Size></Size>
<SerialNumber></SerialNumber>
<items>
<picture>http://nl.imgbb.com/AAOSwOdpXyB4I.JPG</picture>
</items>
</rows>
....
</payments>
</invoice>
</invoices>
while I would like to have something like this where
[rows] is childnode of invoice and not of payments
<invoices>
<invoice>
<ID>82</ID>
<DocType>R</DocType>
<DocYear>2017</DocYear>
<DocNumber>71</DocNumber>
<IssueDate>2017-07-17T15:17:30.237</IssueDate>
<OrderID>235489738019</OrderID>
...
<payments>
<payerID>3234423f33</payerID>
</payments>
<rows>
<ID>163</ID>
<ItemID>235489738019</ItemID>
<Quantity>2</Quantity>
<Price>1</Price>
<VATCode>22</VATCode>
<Color>-</Color>
<Size></Size>
<SerialNumber></SerialNumber>
<items>
<picture>http://nl.imgbb.com/AAOSwOdpXyB4I.JPG</picture>
</items>
</rows>
....
</invoice>
</invoices>
seen some solution where there are many
FOR XML AUTO
put all together, but the data here comes from connected table, would be a pity to re-query 2-3 times same values
how can achieve it?
Thanks
Try changing the select order around to this;
select
[invoice].*,
[payment].payerID,
[items].picture,
[rows].*
from InvoicesHeader [invoice]
join InvoicesRows [rows] on [rows].invoiceID=[invoice].invoiceID
join Payments [payments] on [payments].paymentID=[invoice].paymentID
join Items [items] on [items].itemID=[rows].itemID
FOR XML Auto, ROOT ('invoices'), ELEMENTS
well, found that have to use FOR XML PATH instead and add the other table as subquery with each FOR XML PATH as follows:
select
[invoice].*,
p.payerID,
(select r.* from InvoiceRows r where r.invoiceID=i.invoiceID for XML PATH ('rows'), type)
from InvoicesHeader i
join payment p on i.paymentID=p.paymentID
FOR XML PATH('invoice'), ROOT ('invoices'), ELEMENTS

Stored procedure to read xml file and Scope_identity error

I am reading an xml file in a stored procedure to insert data into an existing table. I have read the xml in a small test to see if I can read it like I think should be able to and it does. I now want to create a stored procedure that inserts data from xml in a 'Question' table and also an 'Answer' table. The thing is I need the primary key from the question table that was just created to insert into the Answer table or else it causes a problem because of the FK.
I have this:
insert into Questions(Question_Text, Questionaire_ID, QuestionType, Filter)
select
X.xmlData.query('Question').value('.', 'varchar(100)') Question_Text,--Question Text
X.xmlData.query('QuestionaireID').value('.', 'varchar(100)') Questionaire_ID,-- Questionaire ID
X.xmlData.query('Type').value('.', 'varchar(100)') WuestioneType,--Question TYPE
X.xmlData.query('Filter').value('.', 'varchar(100)') Filter--Filter
from
(select
cast(x as XML)
from openrowset(bulk 'C:\sqlXML.xml', single_blob) as T(x)
) as T(x)
cross apply
x.nodes('data/New') as X(xmlData);
select #newQuestionId = SCOPE_IDENTITY() <--I need this to know the current
questionID that relate to the following possible answers
insert into Possible_Answers(Question_ID, Possible_Answer_Text,
Explanation_Required, Review_Required, Question_Type)
select #newQuestionId ,
X.xmlData.query('AnswerChoice[1]').value('.', 'varchar(100)') Possible_Answer_Text,
X.xmlData.query('AnswerChoice[2]').value('.', 'varchar(100)') Possible_Answer_Text,
X.xmlData.query('AnswerChoice[3]').value('.', 'varchar(100)') Possible_Answer_Text,
X.xmlData.query('AnswerChoice[4]').value('.', 'varchar(100)') Possible_Answer_Text,
X.xmlData.query('ExplanationRequired').value('.', 'varchar(100)') Explanation_Required,
X.xmlData.query('ReviewRequired').value('.', 'varchar(100)') Review_Rewuired
from (
select cast(x as XML)
from openrowset(
bulk 'C:\sqlXML.xml',
single_blob) as T(x)
)
as T(x)
cross apply x.nodes('data/New') as X(xmlData);
Now this could be all wrong because in my test I just read the data and didn't insert it into a table.
And this is my XML:
<?xml version="1.0" encoding="UTF-8" ?>
<data>
<New>
<QuestionaireID>2</QuestionaireID>
<Type>1</Type>
<Question>Does this test work?</Question>
<Filter>31</Filter>
<AnswerChoice>true</AnswerChoice>
<AnswerChoice>false</AnswerChoice>
<ExplanationRequired></ExplanationRequired>
<ReviewRequired></ReviewRequired>
</New>
<New>
<QuestionaireID>3</QuestionaireID>
<Type>2</Type>
<Question>Does this test work really?</Question>
<Filter>127</Filter>
<AnswerChoice>answer A</AnswerChoice>
<AnswerChoice>Answer B</AnswerChoice>
<AnswerChoice>Answer C</AnswerChoice>
<AnswerChoice>Answer D</AnswerChoice>
<ExplanationRequired></ExplanationRequired>
<ReviewRequired></ReviewRequired>
</New>
</data>
As you can see there may be question with on 2 possible answers or 4 possible answers...
I get the errors:
An INSERT statement cannot contain a SELECT statement that assigns values to a variable.
A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations.
I know where the errors are coming from but I was wondering if there was a way to do this?

Adding an Element to XML SQL 2008 Query

I am trying to add in a few elements in between the ROOT and PATH in a MSSQL 2008 Query. For Example I generate this something like this:
<Employees>
<Employee ID="1">
<LastName>David</LastName>
<FirstName>Larry</FirstName>
<Title>Writer</Title>
</Employee>
<Employee ID="2">
<LastName>Colbert</LastName>
<FirstName>Stephen</FirstName>
<Title>President of South Carolina</Title>
</Employee>
With
SELECT
[EmployeeID] AS '#ID',
[LastName], [FirstName],
[Title]
FROM
[dbo].[Employees]
FOR XML PATH('Employee'), ROOT('Employees')
I'd like to add a few elements like this:
<Employees>
<Company>ACME DYNAMITE</Company>
<CreateDate>JAN 01 2013</CreateDate>
<Employee ID="1">
<LastName>David</LastName>
<FirstName>Larry</FirstName>
<Title>Writer</Title>
</Employee>
<Employee ID="2">
<LastName>Colbert</LastName>
<FirstName>Stephen</FirstName>
<Title>President of South Carolina</Title>
</Employee>
I am using BCP to generate an output file so it would be possible to append a header and footer to the output file. If its possible in the query I'd like to do it that way. I have tried a variety of syntax but just can't seem to get it.
Any help is much appreciated.
SELECT
'ACME DYNAMITE' as Company,
'JAN 01 2013' as CreateDate,
(
SELECT
[EmployeeID] AS '#ID',
[LastName],
[FirstName],
[Title]
FROM
[dbo].[Employees]
FOR XML PATH('Employee'), TYPE
)
FOR XML PATH(''), ROOT('Employees')

Resources