insert declared variable into xml code - sql-server

Hi i want just insert xml variable into xml code.
My code looks like :
DECLARE #outMsg xml
SET #outMsg='<jbpmEngineSignal>
<type>WORK_ITEM_COMPLETE</type>
<elementId>257976516</elementId>
<priority>0</priority>
<results />
<tryCount>344</tryCount>
<uid>7028D745-1C62-46C3-9543-6C1D233450C8</uid>
</jbpmEngineSignal>';
Now i just need to do something like this :
DECLARE #UID xml
set #UID = '7028D745-1C62-46C3-9543-6C1D233450C8'
And finally
DECLARE #outMsg xml
DECLARE #UID xml
set #UID = '7028D745-1C62-46C3-9543-6C1D233450C8'
SET #outMsg='<jbpmEngineSignal>
<type>WORK_ITEM_COMPLETE</type>
<elementId>257976516</elementId>
<priority>0</priority>
<results />
<tryCount>344</tryCount>
<uid>#UID</uid>
</jbpmEngineSignal>';
but this don't work, what am i doing wrong? Can someone just edit my code and show me how to do this ?
Thank you. Please be patient for newebies. When you need more info just write in comment :)

Any reason why you don't use nvarchar for the UID? Then you could it simple as this:
DECLARE #outMsg xml
DECLARE #UID nvarchar(1000);
set #UID = '7028D745-1C62-46C3-9543-6C1D233450C8'
SET #outMsg='<jbpmEngineSignal>
<type>WORK_ITEM_COMPLETE</type>
<elementId>257976516</elementId>
<priority>0</priority>
<results />
<tryCount>344</tryCount>
<uid>' + #UID + '</uid>
</jbpmEngineSignal>';

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

Find and replace just a part of a xml value using XQuery?

I have an XML in one of my columns, that is looking something like this:
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path3/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path21/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path15/test123/file.doc</VorlagenHistorie>
</BenutzerEinstellungen>
I would like to replace all test123 occurances (there can be more than one) in VorlagenHistorie with another test, that all paths direct to test123 after my update.
I know, how you can check and replace all values with an equality-operator, I saw it in this answer:
Dynamically replacing the value of a node in XML DML
But is there a CONTAINS Operator and is it possible to replace INSIDE of a value, I mean only replace a part of the value?
Thanks in advance!
I would not suggest a string based approach normally. But in this case it might be easiest to do something like this
declare #xml XML=
'<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
SELECT CAST(REPLACE(CAST(#xml AS nvarchar(MAX)),'/test123/','/anothertest/') AS xml);
UPDATE
If this approach is to global you might try something like this:
I read the XML as derived table and write it back as XML. In this case you can be sure, that only Nodes with VorlageHistorie will be touched...
SELECT #xml.value('(/BenutzerEinstellungen/State)[1]','nvarchar(max)') AS [State]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM #xml.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen');
UPDATE 2
Try this. It will read all nodes, which are not called VorlagenHistorie as is and will then add the VorlageHistorie nodes with replaced values. The only draw back might be, that the order of your file will be different, if there are other nodes after the VorlagenHistorie elements. But this should not really touch the validity of your XML...
declare #xml XML=
'<BenutzerEinstellungen>
<State>Original</State>
<Unknown>Original</Unknown>
<UnknownComplex>
<A>Test</A>
</UnknownComplex>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
SELECT #xml.query('/BenutzerEinstellungen/*[local-name(.)!="VorlagenHistorie"]') AS [node()]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM #xml.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen');
UPDATE 3
Use an updateable CTE to first get the values and then set them in one single go:
declare #tbl TABLE(ID INT IDENTITY,xmlColumn XML);
INSERT INTO #tbl VALUES
(
'<BenutzerEinstellungen>
<State>Original</State>
<Unknown>Original</Unknown>
<UnknownComplex>
<A>Test</A>
</UnknownComplex>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>')
,('<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>');
WITH NewData AS
(
SELECT ID
,xmlColumn AS OldData
,(
SELECT t.xmlColumn.query('/BenutzerEinstellungen/*[local-name(.)!="VorlagenHistorie"]') AS [node()]
,(
SELECT REPLACE(vh.value('.','nvarchar(max)'),'/test123/','/anothertest/') AS [*]
FROM t.xmlColumn.nodes('/BenutzerEinstellungen/VorlagenHistorie') AS A(vh)
FOR XML PATH('VorlagenHistorie'),TYPE
)
FOR XML PATH('BenutzerEinstellungen'),TYPE
) AS NewXML
FROM #tbl AS t
)
UPDATE NewData
SET OldData=NewXml;
SELECT * FROM #tbl;
A weird solution, but it worked well:
DECLARE #xml XML = '
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path5/test123/third.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
DECLARE #Counter int = 1,
#newValue nvarchar(max),
#old nvarchar(max) = N'test123',
#new nvarchar(max) = N'anothertest';
WHILE #Counter <= #xml.value('fn:count(//*//*)','int')
BEGIN
SET #newValue = REPLACE(CONVERT(nvarchar(100), #xml.query('((/*/*)[position()=sql:variable("#Counter")]/text())[1]')), #old, #new)
SET #xml.modify('replace value of ((/*/*)[position()=sql:variable("#Counter")]/text())[1] with sql:variable("#newValue")');
SET #Counter = #Counter + 1;
END
SELECT #xml;
Output:
<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/anothertest/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path5/anothertest/third.doc</VorlagenHistorie>
</BenutzerEinstellungen>
If #shnugo's answer does not fit your needs, you can use XML/XQuery approach:
DECLARE #xml xml = '<BenutzerEinstellungen>
<State>Original</State>
<VorlagenHistorie>/path/path/test123/file.doc</VorlagenHistorie>
<VorlagenHistorie>/path/path/anothertest/second.doc</VorlagenHistorie>
</BenutzerEinstellungen>';
DECLARE #from nvarchar(20) = N'test123';
DECLARE #to nvarchar(20) = N'another test';
DECLARE #newValue nvarchar(100) = REPLACE(CONVERT(nvarchar(100), #xml.query('(/BenutzerEinstellungen/VorlagenHistorie/text()[contains(.,sql:variable("#from"))])[1]')), #from, #to)
SET #xml.modify('
replace value of (/BenutzerEinstellungen/VorlagenHistorie/text()[contains(.,sql:variable("#from"))])[1]
with sql:variable("#newValue")')
SELECT #xml
gofr1's answer might be enhanced by using more specific XPath expressions:
DECLARE #Counter int = 1,
#newValue nvarchar(max),
#old nvarchar(max) = N'test123',
#new nvarchar(max) = N'anothertest';
WHILE #Counter <= #xml.value('fn:count(/BenutzerEinstellungen/VorlagenHistorie)','int')
BEGIN
SET #newValue = REPLACE(CONVERT(nvarchar(100), #xml.value('(/BenutzerEinstellungen/VorlagenHistorie)[sql:variable("#Counter")][1]','nvarchar(max)')), #old, #new)
SET #xml.modify('replace value of (/BenutzerEinstellungen/VorlagenHistorie[sql:variable("#Counter")]/text())[1] with sql:variable("#newValue")');
SET #Counter = #Counter + 1;
END
SELECT #xml;

SQL Server XML modify with no result

I have an xml document like
<xsd:form-definition ...>
<xsd:page ..></xsd:page>
<xsd:page ..></xsd:page>
...
</xsd:form-definition>
and I want to insert a new node in the first <xsd:page> element
DECLARE #res XML = '<Subject>English</Subject>'
SET #myXmlContent.modify('insert sql:variable("#res") as first into (/form-definition/page)[1]')
but nothing changes, why ?
The XQuery for your modify needs to declare and reference the xsd namespace:
SET #myXmlContent.modify('declare namespace xsd="..."; insert sql:variable("#res") as first into (/xsd:form-definition/xsd:page)[1]')
Try this one -
DECLARE #XML XML
SELECT #XML =
'<xsd_form-definition>
<xsd_page></xsd_page>
<xsd_page></xsd_page>
</xsd_form-definition>'
DECLARE #res XML = '<VehicleManufacturerID>abc</VehicleManufacturerID>'
SELECT #XML.modify('insert sql:variable("#res") into (xsd_form-definition/xsd_page)[1]')
SELECT #XML

Parameterizing XPath for modify() in SQL Server XML Processing

Just like the title suggests, I'm trying to parameterize the XPath for a modify() method for an XML data column in SQL Server, but running into some problems.
So far I have:
DECLARE #newVal varchar(50)
DECLARE #xmlQuery varchar(50)
SELECT #newVal = 'features'
SELECT #xmlQuery = 'settings/resources/type/text()'
UPDATE [dbo].[Users]
SET [SettingsXml].modify('
replace value of (sql:variable("#xmlQuery"))[1]
with sql:variable("#newVal")')
WHERE UserId = 1
with the following XML Structure:
<settings>
...
<resources>
<type> ... </type>
...
</resources>
...
</settings>
which is then generating this error:
XQuery [dbo.Users.NewSettingsXml.modify()]: The target of 'replace' must be at most one node, found 'xs:string ?'
Now I realize that the modify method must not be capable of accepting a string as a path, but is there a way to accomplish this short of using dynamic SQL?
Oh, by the way, I'm using SQL Server 2008 Standard 64-bit, but any queries I write need to be compatible back to 2005 Standard.
Thanks!
In case anyone was interested, I came up with a pretty decent solution myself using a dynamic query:
DECLARE #newVal nvarchar(max)
DECLARE #xmlQuery nvarchar(max)
DECLARE #id int
SET #newVal = 'foo'
SET #xmlQuery = '/root/node/leaf/text()'
SET #id = 1
DECLARE #query nvarchar(max)
SET #query = '
UPDATE [Table]
SET [XmlColumn].modify(''
replace value of (' + #xmlQuery + '))[1]
with sql:variable("#newVal")'')
WHERE Id = #id'
EXEC sp_executesql #query,
N'#newVal nvarchar(max) #id int',
#newVal, #id
Using this, the only unsafe part of the dynamic query is the xPath, which, in my case, is controlled entirely by my code and so shouldn't be exploitable.
The best I could figure out was this:
declare #Q1 varchar(50)
declare #Q2 varchar(50)
declare #Q3 varchar(50)
set #Q1 = 'settings'
set #Q2 = 'resources'
set #Q3 = 'type'
UPDATE [dbo].[Users]
SET [SettingsXml].modify('
replace value of (for $n1 in /*,
$n2 in $n1/*,
$n3 in $n2/*
where $n1[local-name(.) = sql:variable("#Q1")] and
$n2[local-name(.) = sql:variable("#Q2")] and
$n3[local-name(.) = sql:variable("#Q3")]
return $n3/text())[1]
with sql:variable("#newVal")')
WHERE UserId = 1
Node names are parameters but the level/number of nodes is sadly not.
Here is the solution we found for parameterizing both the property name to be replaced and the new value. It needs a specific xpath, and the parameter name can be an sql variable or table column.
SET Bundle.modify
(
'replace value of(//config-entry-metadata/parameter-name[text() = sql:column("BTC.Name")]/../..//value/text())[1] with sql:column("BTC.Value") '
)
This is the hard coded x path: //config-entry-metadata/parameter-name ... /../..//value/text()
The name of the parameter is dynamic: [text() = sql:column("BTC.Name")]
The new value is also dynamic: with sql:column("BTC.Value")

How to parse xml in sql server to process NULL value in DateTime DataType

I have created a sample query in sql server to parse data from xml and to display it right now.
Although I will be inserting this data in my table but before that I am facing a simple problem.
I want to insert NULL in datetime field ADDED_DATE="NULL" as shown in xml given below. But when I executes this query. It gives me error
Conversion failed when converting datetime from character string.
What mistake am i doing. Please highlight my mistake.
declare #xml varchar(1000)
set #xml= '
<ROOT>
<TX_MAP FK_GUEST_ID="1" FK_CATEGORY_ID="2" ATTRIBUTE="Test" DESCRIPTION="TestDesc" IS_ACTIVE="1" ADDED_BY="NULL" ADDED_DATE="NULL" MODIFIED_BY="NULL" MODIFIED_DATE="NULL"></TX_MAP>
<TX_MAP FK_GUEST_ID="2" FK_CATEGORY_ID="1" ATTRIBUTE="Test2" DESCRIPTION="TestDesc2" IS_ACTIVE="1" ADDED_BY="NULL" ADDED_DATE="NULL" MODIFIED_BY="NULL" MODIFIED_DATE="NULL"></TX_MAP>
</ROOT> '
declare #handle int
exec sp_xml_preparedocument #handle output, #xml
select * from OPENXML(#handle,'/ROOT/TX_MAP',1)
with
(
FK_GUEST_ID INT
,FK_CATEGORY_ID VARCHAR(10)
,ATTRIBUTE VARCHAR(100)
,[DESCRIPTION] VARCHAR(100)
,IS_ACTIVE VARCHAR(10)
,ADDED_BY VARCHAR(100)
,ADDED_DATE DATETIME NULL
,MODIFIED_BY VARCHAR(100)
,MODIFIED_DATE DATETIME NULL
)
I am using Sql Server 2005.
After googling an hour, I got answer to my question and would like to share with you all so that for future users it become easy.
declare #xml varchar(1000)
set #xml= '
<ROOT>
<TX_MAP FK_GUEST_ID="1" FK_CATEGORY_ID="2" ATTRIBUTE="Test" DESCRIPTION="TestDesc" IS_ACTIVE="1" ADDED_BY="NULL" ADDED_DATE="12/3/2010" MODIFIED_BY="NULL" MODIFIED_DATE="12/3/2010"></TX_MAP>
<TX_MAP FK_GUEST_ID="2" FK_CATEGORY_ID="1" ATTRIBUTE="Test2" DESCRIPTION="TestDesc2" IS_ACTIVE="1" ></TX_MAP>
</ROOT> '
declare #handle int
exec sp_xml_preparedocument #handle output, #xml
select * from OPENXML(#handle,'/ROOT/TX_MAP',1)
with
(
FK_GUEST_ID INT
,FK_CATEGORY_ID VARCHAR(10)
,ATTRIBUTE VARCHAR(100)
,[DESCRIPTION] VARCHAR(100)
,IS_ACTIVE VARCHAR(10)
,ADDED_BY VARCHAR(100)
,ADDED_DATE DATETIME
,MODIFIED_BY VARCHAR(100)
,MODIFIED_DATE DATETIME
)
What you need to do is just to omit
those attributes that will result into
NULL value.
An XML element can be set to null like:
<ADDED_DATE xsi:nil="true"/>
I can't find a way to set an attribute to null though. Perhaps the only way is to omit it?

Resources