USE Open XML to extract the CDATA - sql-server

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

Related

T-SQL parse XML data into single line

I have XML saved into a column in a table as type nvarchar. Now I need to parse data from that xml. I do
SELECT
CONVERT(XML, columnX).value('(chatTranscript/message/msgText/text())[1]', 'nvarchar(max)')
as chat but I get only first value. How do I extract all into single line? XML can be long, depends on chat length.
I need to get userNick and then msgText and loop it till the end. Something like this:
userX:Hello<>userY:How are you;
XML:
<?xml version="1.0"?>
<chatTranscript startAt="2020-07-30T11:00:12Z" sessionId="......">
<newParty userId="......" timeShift="0" visibility="ALL" eventId="1">
<userInfo personId="" userNick="userX"/>
</newParty>
<message userId="..." timeShift="12" visibility="ALL" eventId="9">
<msgText msgType="text">Hello</msgText>
</message>
<newParty userId="..." timeShift="15" visibility="ALL" eventId="10">
<userInfo userNick="userY"/>
</newParty>
<message userId="..." timeShift="29" visibility="ALL" eventId="12">
<msgText treatAs="NORMAL">how are you?</msgText>
</message>
<partyLeft userId="..." timeShift="36" visibility="ALL" eventId="13" askerId="...">
<reason code="1">left with request to close if no agents</reason>
</partyLeft>
<partyLeft userId="..." timeShift="36" visibility="ALL" eventId="14" askerId="...">
<reason code="4">removed by other party</reason>
</partyLeft>
</chatTranscript>
You need code to do this cleanly. Trying to do what you are asking will be super messy T-SQL. I'd recommend parsing the xml in code to generate what you want based on that xml. You could also create a CLR function using code so that you can create a SQL function to do this. You can do some amazing things with XQuery and T-SQL, but sometimes it just gets to messy. For xml manipulation all within the database, CLR functions are perfect.
Here is my solution:
ALTER FUNCTION [dbo].[fn_parse_chat_xml] (#xml XML)
RETURNS NVARCHAR(MAX)
BEGIN
DECLARE #n INT, #content NVARCHAR(MAX), #userId NVARCHAR(200), #userNick1 NVARCHAR(200), #userNick2 NVARCHAR(200), #userNickX NVARCHAR(200)
SET #n = 1
SET #userId = #xml.value('(chatTranscript/newParty/#userId)[1]', 'nvarchar(max)')
SET #userNick1 = #xml.value('(chatTranscript/newParty/userInfo/#userNick)[1]', 'nvarchar(max)')
SET #userNick2 = #xml.value('(chatTranscript/newParty/userInfo/#userNick)[2]', 'nvarchar(max)')
WHILE DATALENGTH(#xml.value('(chatTranscript/message/msgText/text())[sql:variable("#n")][1]', 'nvarchar(max)'))>0
BEGIN
IF #userId = #xml.value('(chatTranscript/message/#userId)[sql:variable("#n")][1]', 'nvarchar(max)')
SET #userNickX = #userNick1
else
SET #userNickX = #userNick2
SET #content = concat(#content, ' <> ', #userNickX, ': ', #xml.value('(chatTranscript/message/msgText/text())[sql:variable("#n")][1]', 'nvarchar(max)'))
SET #n = #n + 1
END
RETURN #content
END

Extract information from Datastore using OPEN XML

I have the following sample XML held in a database field:
<datastore multipleDataSeparator=",">
<group name="BasicDetails">
<field name="CalculationType">REFUND</field>
</group>
</datastore>
I am trying to extract the value held for CalculationType node using the following SQL script:
DECLARE #xml xml
DECLARE #idoc int
declare #lstrCalculationType varchar(50)
SET #xml = (
'<datastore multipleDataSeparator=",">
<group name="BasicDetails">
<field name="CalculationType">REFUND</field>
</group>
</datastore>'
)
select #xml
EXEC sp_xml_preparedocument #idoc OUTPUT, #xml --Preparing XML handle
select #idoc,#xml
SELECT
#lstrCalculationType = CalculationType
FROM OPENXML(#idoc, 'datastore') --Row Pattern
WITH (
CalculationType VARCHAR(50) 'BasicDetails/CalculationType'
)
select #lstrCalculationType
EXEC sp_xml_removedocument #idoc --Releasing memory
However, the output is null for #lstrCalculationType. Any guidance or input would be appreciated.
I'd use the built-in XQuery support to do this:
DECLARE #xml xml
SET #xml = ('<datastore multipleDataSeparator=",">
<group name="BasicDetails">
<field name="CalculationType">REFUND</field>
</group>
</datastore>')
SELECT
#xml.value('(/datastore/group/field[#name="CalculationType"]/text())[1]', 'varchar(25)')

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

The XML parse error in MSSQL 2008R2

This is xml file:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.fleettracker.de/api/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<ns1:GetPositionsResponse>
<body>
<result>Found 1 vessels.</result>
<success>true</success>
<shipsWithPositions xsi:type="ns1:FleettrackerShip">
<ns1:imono>9456159</ns1:imono>
<ns1:sid>780</ns1:sid>
<ns1:name>Trenta</ns1:name>
<ns1:charterShipName>Trenta</ns1:charterShipName>
<ns1:pasttrack>
<lat>1832900</lat>
<lon>7570400</lon>
<timestamp>2014-01-14T08:28:45Z</timestamp>
<orderNumber>0</orderNumber>
<sog>9.5</sog>
<cog>22</cog>
<hdg>22</hdg>
<eta>2014-01-15T12:00:00</eta>
<nextport>KWANGYANG</nextport>
</ns1:pasttrack>
<ns1:pasttrack>
<lat>1224000</lat>
<lon>7188500</lon>
<timestamp>2014-01-11T08:07:45Z</timestamp>
<orderNumber>9</orderNumber>
<sog>7</sog>
<cog>39</cog>
<hdg>39</hdg>
<eta>2014-01-15T08:00:00</eta>
<nextport>KWANGYANG</nextport>
</ns1:pasttrack>
</shipsWithPositions>
</body>
</ns1:GetPositionsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
And this is MsSql query:
EXEC sp_xml_preparedocument #idoc OUTPUT, #XML,'<root xmlns:ns1="http://www.fleettracker.de/api/1.0">'
--select #idoc
insert into Position (ImoNo, sid, VesselName, time, lat, lon, sog, cog, hdg, eta, NextPort)
SELECT *
FROM OPENXML (#idoc, '/SOAP-ENV:Envelope/SOAP-ENV:Body/ns1:GetPositionsResponse/body/shipsWithPositions', 2)
WITH (ImoNo numeric(8, 0) 'ns1:imono',
sid numeric(5, 0) 'ns1:sid',
VesselName nvarchar(20) 'ns1:name',
time datetime 'ns1:pasttrack/timestamp',
lat numeric(9, 2) 'ns1:pasttrack/lat',
lon numeric(9, 2) 'ns1:pasttrack/lon',
sog numeric(9, 2) 'ns1:pasttrack/sog',
cog numeric(9, 2) 'ns1:pasttrack/cog',
hdg numeric(9, 2) 'ns1:pasttrack/hdg',
eta datetime 'ns1:pasttrack/eta',
NextPort nvarchar(20) 'ns1:pasttrack/nextport'
);
EXEC sp_xml_removedocument #idoc
Does somebody knows why I am getting the XML parse error?!
-------------------------------------------------------------
-------------------------------------------------------------
The problem is the type of the #XML variable.
As the file header decalres 'UTF-8' coding, the document must be inside a varchar() variable:
declare #XML varchar(max) = 'your UTF-8 document'
If you decalre it as nvarchar(max) 'UTF-8' coding is not valid.
declare #XML nvarchar(max) = 'your UTF-8 document' -- This cannot be parsed!

SELECT node text values from xml document in TSQL OPENXML

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

Resources