SELECT node text values from xml document in TSQL OPENXML - sql-server

I have a xml document I want to use to update values in a stored procedure. I can process the XML using OPENXML, but I'm confused about extracting the values I want. Each row in the xml is a product record and I want to create a variable for each property. Cell0 is the ID, Cell2 description etc
DECLARE #idoc int
DECLARE #doc varchar(1000)
SET #doc ='
<products>
<rows>
<row>
<cell>1</cell>
<cell>BALSAMO DERMOSCENT</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>18.00</cell>
<cell>18.00</cell>
<cell>8.00</cell>
<cell>427</cell>
<cell>No</cell>
</row>
<row>
<cell>2</cell>
<cell>BAYTRIL 150 MG 1 CPDO</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>3.50</cell>
<cell>3.50</cell>
<cell>8.00</cell>
<cell>57</cell>
<cell>No</cell>
</row>
</rows>
</products>'
--Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc
-- Execute a SELECT statement that uses the OPENXML rowset provider.
SELECT *
FROM OPENXML (#idoc, '/products/rows/row/cell',1)
with (Col1 varchar(29) 'text()')
Running the above query returns 1 record for each CELL in the xml. I want to be able to return 1 record per row with different columns for each cell, something like:-
Prod Description Qty
---------- -------------------- --------
1 BALSAMO DERMOSCENT 1.00
2 BAYTRIL 150 MG 1 CPDO 1.00
I'm using MSSQL 2008

I've come up with the following which does the job for me
DECLARE #idoc int
DECLARE #doc varchar(1000)
SET #doc ='
<products>
<rows>
<row>
<cell>1</cell>
<cell>BALSAMO DERMOSCENT</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>18.00</cell>
<cell>18.00</cell>
<cell>8.00</cell>
<cell>427</cell>
<cell>No</cell>
</row>
<row>
<cell>2</cell>
<cell>BAYTRIL 150 MG 1 CPDO</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>3.50</cell>
<cell>3.50</cell>
<cell>8.00</cell>
<cell>57</cell>
<cell>No</cell>
</row>
</rows>
</products>'
--Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc
-- Execute a SELECT statement that uses the OPENXML rowset provider.
SELECT *
FROM OPENXML (#idoc, '/products/rows/row',1)
with (pLineNo int 'cell[1]/text()',
pDesc varchar(50) 'cell[2]/text()',
pQty float 'cell[3]/text()',
pCost float 'cell[4]/text()',
pPvp float 'cell[5]/text()',
pTotal float 'cell[6]/text()',
pIva float 'cell[7]/text()',
pId int 'cell[8]/text()',
pnoFact varchar(5) 'cell[9]/text()')

Why use openxml on sql server 2008?
This is a better option (I used varchar(max) as the datatype, but enter whatever is applicable). Note you have to declare the variable as xml, not varchar.
SELECT
Row.Item.value('data(cell[1])', 'varchar(max)') As Prod,
Row.Item.value('data(cell[2])', 'varchar(max)') As Description,
Row.Item.value('data(cell[3])', 'varchar(max)') As Qty
FROM
#doc.nodes('//row') AS Row(Item)
Note: If you're doing this is a stored procedure you may have to include the following before the select statement:
SET ARITHABORT ON -- required for .nodes
If you must use openxml, at least clean it up when you're done:
exec sp_xml_removedocument #idoc

Related

How to split string from XML content and get the required value

Hello all I am converting an xml content and inserting it to a table variable as follows
DECLARE #iDoc int
SET #XMLData = '<NewDataSet>
<Table>
<DataId>2324205.3933251.7336404</DataId>
<IsVisible>true</IsVisible>
<Notes />
<Marks>85.5</Marks>
</Table>
</NewDataSet>'
EXEC sp_xml_preparedocument #idoc OUTPUT, #XMLData
SELECT DataId FROM OPENXML(#idoc, 'NewDataSet/Table', 1)
WITH (DataId NVARCHAR(250) 'DataId')```
I would like to split the dot value and retrieve the the first value, can some one help me how to can I do that with in XML
IsVisible is a bit filed, Marks is deicmal like that I will have
Starting from SQL Server 2005 onwards, it is better to use XQuery language, based on the w3c standards, while dealing with the XML data type.
Microsoft proprietary OPENXML and its companions sp_xml_preparedocument and sp_xml_removedocument are kept just for backward compatibility with the obsolete SQL
Server 2000. Their use is diminished just to very few fringe cases.
It is strongly recommended to re-write your SQL and switch it to XQuery.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Notes NVARCHAR(MAX));
INSERT INTO #tbl (Notes) VALUES
(N'<NewDataSet>
<Table>
<DataId>2324205.3933251.7336404</DataId>
</Table>
</NewDataSet>');
-- DDL and sample data population, end
WITH rs AS
(
SELECT *
, TRY_CAST(Notes AS XML).value('(/NewDataSet/Table/DataId/text())[1]', 'VARCHAR(MAX)') AS x
FROM #tbl
)
SELECT *
, LEFT(x, CHARINDEX('.', x) - 1) AS [After]
, PARSENAME(x, 3) AS [After2]
FROM rs;
Output
+-------------------------+---------+
| Before | After |
+-------------------------+---------+
| 2324205.3933251.7336404 | 2324205 |
+-------------------------+---------+

How to use parameters in the stored procedure to check if a specific xml element has a specific value?

I want to create this stored procedure in SQL Server:
create procedure Test
#pathToElement nvarchar(50),
#value nvarchar(50)
as
select top 10 *
from MyTable
where SerializedXml.exist('#pathToElement[text() = #value]') = 1;
SerializedXml is an xml column in MyTable.
I get the following error:
Top-level attribute nodes are not supported
An easy way would be to make it a dynamic query, for example:
CREATE TABLE #t(id INT IDENTITY(1,1),SerializedXml XML);
INSERT INTO #t(SerializedXml)VALUES
('<FormResults> <ID>00008</ID> <LocationID>42</LocationID> <LastSaveDate>2016-09-10T21:21:36</LastSaveDate> <Data> <txtSerGen>YY</txtSerGen> <txtGen>0602</txtGen> <txtYear>2016</txtYear> </Data> </FormResults>'),
('<FormResults> <ID>00008</ID> <LocationID>42</LocationID> <LastSaveDate>2016-09-10T21:21:36</LastSaveDate> <Data> <txtGen>0602</txtGen> <txtYear>2016</txtYear> </Data> </FormResults>');
DECLARE #pathToElement NVARCHAR(4000)='FormResults/Data/txtSerGen';
DECLARE #value NVARCHAR(4000)='YY';
DECLARE #dsql NVARCHAR(MAX) ='
select top 10 *
from #t
where
SerializedXml.exist('''+#pathToElement+'[(text()[1]) = "'+REPLACE(#value,'''','''''')+'"]'') = 1;';
EXEC sp_executesql #dsql;
DROP TABLE #t;
This will select the first row only.

SQL Server and XML Utilization

I'm trying to utilize XML with SQL Server. All I'm trying to do is print out all three guests. When I run my code, it only shows the prints the first guest's information, and I need all three guest's information to be printed. What am I doing wrong?
SELECT Guest.GuestID, GuestFirst, GuestLast, CheckinDate, Nights
FROM GUEST
JOIN FOLIO
ON Guest.GuestID = Folio.GuestID
FOR XML RAW
Declare #idoc int
Declare #xmldoc nvarchar(4000)
Set #xmldoc = '
<ROOT>
<GUEST>
<GuestID>4431</GuestID>
<GuestFirst>Lacey</GuestFirst>
<GuestLast>Byington</GuestLast>
<RESERVATIONDETAIL>
<CheckInDate>2016-08-02</CheckInDate>
<Nights>2</Nights>
</RESERVATIONDETAIL>
</GUEST>
<GUEST>
<GuestID>5563</GuestID>
<GuestFirst>Jonathan</GuestFirst>
<GuestLast>Langford</GuestLast>
<RESERVATIONDETAIL>
<CheckInDate>2016-08-05</CheckInDate>
<Nights>2</Nights>
</RESERVATIONDETAIL>
</GUEST>
<GUEST>
<GuestID>6680</GuestID>
<GuestFirst>Tanner</GuestFirst>
<GuestLast>Olson</GuestLast>
<RESERVATIONDETAIL>
<CheckInDate>2015-09-11</CheckInDate>
<Nights>3</Nights>
</RESERVATIONDETAIL>
</GUEST>
</ROOT>'
EXEC sp_xml_preparedocument #idoc OUTPUT, #xmldoc
SELECT * FROM OPENXML (#idoc, '/ROOT', 3)
WITH
(
GuestID smallint 'GUEST/GuestID',
GuestFirst varchar(30) 'GUEST/GuestFirst',
GuestLast varchar(30) 'GUEST/GuestLast',
CheckinDate smalldatetime 'GUEST/RESERVATIONDETAIL/CheckInDate',
Nights tinyint 'GUEST/RESERVATIONDETAIL/Nights'
)
EXEC sp_xml_removedocument #idoc
GO
Instead xml document try it with xquery,
DECLARE #xmldoc XML
Set #xmldoc = '
<ROOT>
<GUEST>
<GuestID>4431</GuestID>
<GuestFirst>Lacey</GuestFirst>
<GuestLast>Byington</GuestLast>
<RESERVATIONDETAIL>
<CheckInDate>2016-08-02</CheckInDate>
<Nights>2</Nights>
</RESERVATIONDETAIL>
</GUEST>
<GUEST>
<GuestID>5563</GuestID>
<GuestFirst>Jonathan</GuestFirst>
<GuestLast>Langford</GuestLast>
<RESERVATIONDETAIL>
<CheckInDate>2016-08-05</CheckInDate>
<Nights>2</Nights>
</RESERVATIONDETAIL>
</GUEST>
<GUEST>
<GuestID>6680</GuestID>
<GuestFirst>Tanner</GuestFirst>
<GuestLast>Olson</GuestLast>
<RESERVATIONDETAIL>
<CheckInDate>2015-09-11</CheckInDate>
<Nights>3</Nights>
</RESERVATIONDETAIL>
</GUEST>
</ROOT>'
SELECT
a.b.value('GuestID[1]','smallint') AS GuestID,
a.b.value('GuestFirst[1]','varchar(30)') AS GuestFirst,
a.b.value('GuestLast[1]','varchar(30)') AS GuestLast,
a.b.value('RESERVATIONDETAIL[1]/CheckInDate[1]','smalldatetime') AS CheckInDate,
a.b.value('RESERVATIONDETAIL[1]/Nights[1]','tinyint') AS Nights
FROM #xmldoc.nodes('ROOT/GUEST') AS a(b)
GO
btw, the select query you have given on the top will not produce the same xml which you have given below.
#Jatin answer is good, you can use xquery. You can also use OPENXML like this:
EXEC sp_xml_preparedocument #idoc OUTPUT, #xmldoc
SELECT * FROM OPENXML (#idoc, '/ROOT/GUEST', 3)
WITH
(
GuestID smallint './GuestID',
GuestFirst varchar(30) './GuestFirst',
GuestLast varchar(30) './GuestLast',
CheckinDate smalldatetime './RESERVATIONDETAIL/CheckInDate',
Nights tinyint './RESERVATIONDETAIL/Nights'
)
EXEC sp_xml_removedocument #idoc
You are almost there. You just need to make this small change:
SELECT * FROM OPENXML (#idoc, '/ROOT/*', 3)
WITH
(
GuestID smallint 'GuestID',
GuestFirst varchar(30) 'GuestFirst',
GuestLast varchar(30) 'GuestLast',
CheckinDate smalldatetime 'RESERVATIONDETAIL/CheckInDate',
Nights tinyint 'RESERVATIONDETAIL/Nights'
)

USE Open XML to extract the CDATA

I have xml which has the data embedded in CDATA. I would like to extract the info in different fields. But not able to do it.
<Item_Response Format="text/xml">
<![CDATA[ <Item sequence="1" type="item" itemId="999999"
itemVersion="2012-04-07T13:43:27">
<response><bubbleinput answered="y" input_id="bubbleinput1">
<bubble id="bubble1"/>
</bubbleinput></response></Item> ]]>
</Item_Response>
You can extract CDATA value with XPath. Then use openxml on extracted value.
declare #xml xml = '<Item_Response Format="text/xml"><![CDATA[ <Item sequence="1" type="item" itemId="999999" itemVersion="2012-04-07T13:43:27"><response><bubbleinput answered="y" input_id="bubbleinput1"><bubble id="bubble1"/></bubbleinput></response></Item> ]]></Item_Response>'
-- openxml
declare
#idoc int,
#qxml xml = cast(#xml.value('(/Item_Response)[1]', 'nvarchar(max)') as xml)
exec sp_xml_preparedocument #idoc output, #qxml
select
*
from
openxml(#idoc, '/Item', 0) with (
sequence int '#sequence',
bubbleinput nvarchar(1) './response/bubbleinput/#answered'
) as XMLData
exec sp_xml_removedocument #idoc

Inserting an attribute in multiple XML Nodes using XML.modify() in SQL 2005

I have an #XML document created from a single select statement.
<root>
<node>
<node1>
<targetNode>
</targetNode>
</node1>
<node1>
<targetNode>
</targetNode>
</node1>
<node1>
<targetNode>
</targetNode>
</node1>
</node>
<node>
......
</node>
</root>
I want to insert the xsi:nil as an attribute of 'targetNode' for this document.
#XML.modify( 'insert attribute xsi:nil {"true"} into (root/node/node1/targetNode) [1]')
The above will insert the attribute into the first occurance of the targetNode in the #XML document. The insert statement however will only work on a single node. Is there any way I can insert this attribute into all instances of targetNode in the #XML document.
I found a simple and elegant solution in DML operations on multiple nodes
http://blogs.msdn.com/b/denisruc/archive/2005/09/19/471562.aspx
The idea is to count how many nodes and modify them one by one:
DECLARE #iCount int
SET #iCount = #var.value('count(root/node/node1/targetNode)','int')
DECLARE #i int
SET #i = 1
WHILE (#i <= #iCount)
BEGIN
#xml.modify('insert attribute xsi:nil {"true"} into (root/node/node1/targetNode)[sql:variable("#i")][1]')
SET #i = #i + 1
END
That's not possible with the modify-function. It only works on a single node.
You can manipulate it as string, although that is definitely ugly and possibly wrong in some cases, depending on the actual structure of your XML.
Like this:
declare #xml as xml
set #xml = '<root>
<node>
<node1>
<targetNode>
</targetNode>
</node1>
<node1>
<targetNode>
</targetNode>
</node1>
<node1>
<targetNode>
</targetNode>
</node1>
</node>
</root>
'
set #xml = replace(cast(#xml as nvarchar(max)), '<targetNode/>', '<targetNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />')
select #xml
you can do this in the select, that you are using to create your xml, using the XSINILL parameter.
http://msdn.microsoft.com/en-us/library/ms178079.aspx
(here is a very rough example)
--create 2 tables and put some data in them
create table node
(
id int identity(1,1) primary key,
node int
)
GO
create table node1
(
id int identity(1,1) primary key,
nodeid int foreign key references node(id),
targetnode int
)
GO
insert into node
select 1
GO 5
insert into node1
select 1,2
union
select 2,null
union
select 3,2
union
select 4,null
--
--select statement to generate the xml
SELECT TOP(1)
(SELECT
( SELECT targetnode
FROM node1
WHERE nodeid = node.id
FOR XML AUTO,
ELEMENTS XSINIL,
TYPE
)
FROM node FOR XML AUTO,
ELEMENTS,
TYPE
)
FROM node FOR XML RAW('root'),
ELEMENTS

Resources