Control characters handling in XML - sql

I am following XML based transport of data using FOR XML AUTO. When a column containing control character was encountered and a XML transform as below was attempted, it resulted in an exception:
DECLARE #TrialData NVARCHAR(200) = ''; --Not empty string..contains a control character '<-'
DECLARE #TrialDataInHex VARBINARY(100)
SET #TrialDataInHex = CONVERT(VARBINARY(100),#TrialData)
SELECT #TrialDataInHex; -- returns 0x1B00 looks like this '<-'
Now, when I try to insert the same into XML variable like below
DECLARE #a_XML XML;
SET #a_XML = #TrialData;
This resulted in
XML parsing: line 0, character 0, unrecognized input signature
Can Anyone suggest how control characters are usually handled when XML FROM SQL data medium is used?

You have to wrap that value in an XML element:
XMLELEMENT("TrialData" , #TrialData)
generates
<TrialData>←</TrialData>

Related

How can I write this SQL while loop code to get an XML results in one line instead of 3 separate lines?

I'm trying to get all this XML result in one line instead of 3 for each column
DECLARE #ii INT = 10;
DECLARE #String1 NVARCHAR(4000);
SET #String1 = '';
WHILE(#ii <= 18)
BEGIN
SET #String1 = (#String1 + 'SELECT LoanNumber = ''Complaint'+CONVERT(VARCHAR(2),#ii)+'-Call1'' , LoanStatus=''Compliants'' , LoanStatusDate = CAST(GETDATE() AS DATE)
UNION
SELECT LoanNumber = ''Complaint'+CONVERT(VARCHAR(2),#ii)+'-Call2'', LoanStatus=''Compliants'' , LoanStatusDate = CAST(GETDATE() AS DATE)
UNION
SELECT LoanNumber = ''Complaint'+CONVERT(VARCHAR(2),#ii)+'-Call3'', LoanStatus=''Compliants'' , LoanStatusDate = CAST(GETDATE() AS DATE)')
IF #ii != 18
SET #string1 = #string1 + ' UNION '
ELSE
SET #string1 = #string1 + 'FOR XML PATH (''Loan''),ROOT(''Loans'') '
SET #ii = #ii+1
END
EXEC sp_executesql #String1
I want something like this:
<Loans>
<LoanNumber>Complaint10-Call1<LoanStatus>Compliants<LoanStatusDate>2019-01-18
</Loan>
<Loan>
<LoanNumber>Complaint10-Call2 <LoanStatus>Compliants<LoanStatusDate>2019-01-18
</Loan>
<Loan>
<LoanNumber>Complaint10-Call3<LoanStatus>Compliants<LoanStatusDate>2019-01-18
</Loan>
Instead of the result that you get when you execute the code I provided. I appreciate your help.
This might be wild guessing, but I've got the feeling, that I understand, what this is about:
if you run the code you will see the result. no input data is needed .
I just want the structure of the xml outcome to all be on one line for
one set of each loop
Your provided code leads to this:
<Loans>
<Loan>
<LoanNumber>Complaint10-Call1</LoanNumber>
<LoanStatus>Compliants</LoanStatus>
<LoanStatusDate>2019-01-22</LoanStatusDate>
</Loan>
<Loan>
<LoanNumber>Complaint10-Call2</LoanNumber>
<LoanStatus>Compliants</LoanStatus>
<LoanStatusDate>2019-01-22</LoanStatusDate>
</Loan>
<!-- more of them-->
</Loans>
This is perfectly okay, valid XML.
But you want the result
outcome to all be on one line for one set of each loop
Something like this?
<Loans>
<Loan>
<LoanNumber>Complaint10-Call1</LoanNumber><LoanStatus>Compliants</LoanStatus><LoanStatusDate>2019-01-22</LoanStatusDate>
</Loan>
<!-- more of them-->
</Loans>
There is a big misconception I think... XML is not the thing you see. The same XML can look quite differently, without any semantic difference:
Check this out:
DECLARE #xmltable table(SomeXml XML)
INSERT INTO #xmltable VALUES
--the whole in one line
('<root><a>test</a><a>test2</a></root>')
--all <a>s in one line
,('<root>
<a>test</a><a>test2</a>
</root>')
--each element in one line
,('<root>
<a>test</a>
<a>test2</a>
</root>')
--white space going wild...
,('<root>
<a>test</a>
<a>test2</a>
</root>');
--now check the results
SELECT * FROM #xmltable;
This means: How the XML appears is a matter of the interpreter. The same XML opened with another tool might appear differently. Dealing with XML means dealing with data but not with format... The actual format has no meaning and should not matter at all...
Starting with SQL-Server 2016 you might have a look at JSON, if you need a tiny format:
DECLARE #somedata table(SomeValue VARCHAR(100),SomeStatus VARCHAR(100),SomeDate DATE);
INSERT INTO #somedata VALUES
('Complaint10-Call1','Complaints','2019-01-22')
,('Complaint10-Call2','Complaints','2019-01-22')
,('Complaint10-Call3','Complaints','2019-01-22');
SELECT * FROM #somedata FOR JSON PATH;
The result comes in one line:
[{"SomeValue":"Complaint10-Call1","SomeStatus":"Complaints","SomeDate":"2019-01-22"},{"SomeValue":"Complaint10-Call2","SomeStatus":"Complaints","SomeDate":"2019-01-22"},{"SomeValue":"Complaint10-Call3","SomeStatus":"Complaints","SomeDate":"2019-01-22"}]

Escaping characters in T-SQL OPENJSON queries

I have the following JSON Data
DECLARE #jsonData NVARCHAR(MAX)
SET #jsonData =
'{
"insertions":[
{
"id":"58735A79-DEA8-462B-B3EB-C2797CA9D44E",
"last-modified":"2017-08-08 13:07:32",
"label":"HelloWorld1"
},
{
"id":"00565BCD-4240-46CF-A48F-849CB5A8114F",
"last-modified":"2017-08-08 13:11:38",
"label":"HelloWorld12"
}
]
}'
And trying to perform a select from it:
SELECT
*
FROM
OPENJSON(JSON_QUERY(#jsonData,'$.insertions'))
WITH
(uuid UNIQUEIDENTIFIER '$.id',
modified DATETIME '$.last-modified',
Label NVARCHAR(128) '$.label'
)
It doesn't like the dash in the last-modified field.
Msg 13607, Level 16, State 4, Line 18
JSON path is not properly formatted. Unexpected character '-' is found at position 6.
Is there a way to escape the dash in the query? Everything works fine if there is no dash.
As required to support JSON, I'm using SQL Server 2016 with compatibility level = 130
Adding double quotes around the field name seems to work
SELECT
*
FROM
OPENJSON(JSON_QUERY(#jsonData,'$.insertions'))
WITH
(uuid UNIQUEIDENTIFIER '$.id',
modified DATETIME '$."last-modified"',
Label NVARCHAR(128) '$.label'
)

Update(Replace partcial value) XML Column in SQL

I have an XML column in my Table and i wanted to replace particular text wherever it appear in that column with a new text. Here is the xml structure,
<Story>
<StoryNonText>
<NonText>
<ImageID>1</ImageID>
<Src>http://staging.xyz.com/FolderName/1.png</Src>
</NonText>
<NonText>
<ImageID>2</ImageID>
<Src>http://staging.xyz.com/FolderName/2.png</Src>
</NonText>
</StoryNonText>
</Story>
In the above XML I wanted to replace all the <Src> values having http://staging.xyz.com/ to http://production.xyz.com/. Please guide me how i can do this!
You can use Replace() function as below:
Update TableName
SET
ColumnName=replace(CAST(ColumnName AS VARCHAR(8000)),'<Src>http://staging.xyz.com/','<Src>http://production.xyz.com/')
With a little help from a couple of XML functions you can do this in a loop.
The loop is necessary since replace value of can only replace one value at a time. This code assumes the URL is located first in the node and not embedded in text anywhere.
declare #T table(X xml);
insert into #T(X) values('<Story>
<StoryNonText>
<NonText>
<ImageID>1</ImageID>
<Src>http://staging.xyz.com/FolderName/1.png</Src>
</NonText>
<NonText>
<ImageID>2</ImageID>
<Src>http://staging.xyz.com/FolderName/2.png</Src>
</NonText>
</StoryNonText>
</Story> ');
declare #FromURL nvarchar(100);
declare #ToURL nvarchar(100);
set #FromURL = 'http://staging.xyz.com/';
set #ToURL = 'http://production.xyz.com/';
while 1 = 1
begin
update #T
set X.modify('replace value of (//*/text()[contains(., sql:variable("#FromURL"))])[1]
with concat(sql:variable("#ToURL"), substring((//*/text()[contains(., sql:variable("#FromURL"))])[1], string-length(sql:variable("#FromURL"))+1))')
where X.exist('//*/text()[contains(., sql:variable("#FromURL"))]') = 1;
if ##rowcount = 0
break;
end;
select *
from #T
replace value of (XML DML)
concat Function (XQuery)
contains Function (XQuery)
string-length Function (XQuery)
sql:variable() Function (XQuery)
There are many ways to do that.
The first way is to add a WHILE loop. Inside a loop, you search (CHARINDEX) for a position of first tag and first tag. Then, knowing the start and end positions, replace the value. Then on the next iteration you search again, but change starting position in CHARINDEX() function
The second way is to use SELECT ... FROM OPENXML + EXEC sp_xml_preparedocument

SQL Replace and where CAST(PromotionRuleData as NVARCHAR(MAX))

Can't seem to edit my old post but I am trying to execute this SQL script
UPDATE Promotions
set Description = '£5 Off £25 Spend',
UsageText = '£5 Off £25 Spend',
EmailText = '£5 Off £25 Spend',
PromotionRuleData= '<ArrayOfPromotionRuleBase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><PromotionRuleBase xsi:type="StartDatePromotionRule"><StartDate>2013-11-18T00:00:00</StartDate></PromotionRuleBase><PromotionRuleBase xsi:type="ExpirationDatePromotionRule"><ExpirationDate>2014-01-13T00:00:00</ExpirationDate></PromotionRuleBase><PromotionRuleBase xsi:type="ExpirationNumberOfUsesPerCustomerPromotionRule"><NumberOfUsesAllowed>1</NumberOfUsesAllowed> </PromotionRuleBase><PromotionRuleBase xsi:type="MinimumCartAmountPromotionRule"><CartAmount>24.99</CartAmount></PromotionRuleBase></ArrayOfPromotionRuleBase>',
PromotionDiscountData = '<ArrayOfPromotionDiscountBase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><PromotionDiscountBase xsi:type="OrderPromotionDiscount"><DiscountType>Fixed</DiscountType><DiscountAmount>5.00</DiscountAmount></PromotionDiscountBase></ArrayOfPromotionDiscountBase>'
where Name = 'test1,test2,etc...'
It comes back with this error
Msg 402, Level 16, State 1, Line 1
The data types varchar and text are incompatible in the equal to operator.
I try to use where CAST(PromotionRuleData as NVARCHAR(MAX))
So the line reads as
CAST(PromotionRuleData as NVARCHAR(MAX)) = '<ArrayOfPromotionRuleBase ...
but no luck.
You cannot compare a string literal against a text column in SQL Server.
Which column is of datatype text ? Your name column that you use in the WHERE clause by any chance?
If so, use this WHERE instead:
WHERE CAST(Name AS VARCHAR(MAX)) = 'test1,test2,etc...'
or better yet: convert your column to a more appropriate datatype like VARCHAR(n) (unless you really need 2 GB = 2 billion characters - if so, then use VARCHAR(MAX))

SQL: Parse JSON

Using SQL, how can I select the value for "conversion_event" from the JSON below?
{"str":[1,1342886173,100000627571405,"offsite_conversion.lead",{"action.type":"offsite_conversion","conversion_event":387756207950188,"tag":"lead"},["conversion_event"],[],{"amount":12623486},"1:11:1:0:0:1:0"]}
There are some unusual things in here, for example JSON within JSON, and the square brackets. Assume that lengths of all values vary by row, so you cannot slice by a set number of character positions.
Based on comments:
declare #astr varchar(max);
declare #start int;
declare #end int;
set #astr = '{"str":[1,1342886173,100000627571405,"offsite_conversion.lead",{"action.type":"offsite_conversion","conversion_event":387756207950188,"tag":"lead"},["conversion_event"],[],{"amount":12623486},"1:11:1:0:0:1:0"]}';
select #start = charindex('"conversion_event":',#astr)
select #end = charindex(',"tag":',#astr)
select substring(#astr,#start,#end-#start);
returns
"conversion_event":387756207950188
add
set #start = #start + 19;
to get just the number.
SELECT substring('{"str":[1,1342886173,100000627571405,"offsite_conversion.lead",{"action.type":"offsite_conversion","conversion_event":387756207950188,"tag":"lead"},["conversion_event"],[],{"amount":12623486},"1:11:1:0:0:1:0"]}',
101,16);
or
select substring('{"str":[1,1342886173,100000627571405,"offsite_conversion.lead",{"action.type":"offsite_conversion","conversion_event":387756207950188,"tag":"lead"},["conversion_event"],[],{"amount":12623486},"1:11:1:0:0:1:0"]}',
151,16)
Ok this is the structure of the object:
{
"str":[1,
1173,
10005,
"offsite_conversion.lead",
{"action.type":"offsite_conversion",
"conversion_event":387756207950188,
"tag":"lead"},
["conversion_event"],
[],
{"amount":14486},
"1:11:1:0:0:1:0"
]}
An object with an atribute str which is an array.
The 5th element has a 2nd attribute conversion event
The 6th element is an array of one element which is conversion event.
So the question is... is this structure the same and which of these do you want?