The argument 1 of the XML data type method "modify" must be a string literal - mysql

Hi i am getting the string literal error when i am trying to add an attribute to the child node. How can i modify my code in order to add an attribute successfully.
declare #count int=(select mxGraphXML.value('count(/mxGraphModel/root/Cell/#Value )','nvarchar') from TABLE_LIST
where Table_ListID=1234 )
declare #index int=1;
while #index<=#count
begin
declare #Value varchar(100)= #graphxml.value('(/mxGraphModel/root/Cell/#Value )[1]','nvarchar');
SET #graphxml.modify('insert attribute copyValueID {sql:variable("#Value ")}
as first into (/mxGraphModel/root/Cell)['+convert(varchar,#index)+']');
end
set #index=#index+1;
end

You're using the addition operator where you should be using the CONCAT function. So
'insert attribute copyValueID {sql:variable("#Value ")}
as first into (/mxGraphModel/root/Cell)['+convert(varchar,#index)+']'
is being coerced into a number. Try:
CONCAT('insert attribute copyValueID {sql:variable("#Value ")}
as first into (/mxGraphModel/root/Cell)[',convert(varchar,#index),']')
instead.

Adam, you can do it in Microsoft T-SQL like this:
declare #sql nvarchar(max)
set #sql = 'set #myxml.modify(''
insert (
attribute scalableFieldId {sql:variable("#sf_id")},
attribute myTypeId {sql:variable("#my_type_id")}
) into (/VB/Condition/Field[#fieldId=sql:variable("#field_id")
and #fieldCode=sql:variable("#field_code")])['+
cast(#instance as varchar(3))+']'')'
exec sp_executesql
#sql
,N'#myxml xml output, #field_code varchar(20),
#field_id varchar(20), #sf_id int, #my_type_id tinyint'
,#myxml = #myxml output
,#field_code = #field_code
,#field_id = #field_id
,#sf_id = #sf_id
,#my_type_id = #my_type_id
See what I've done here? It's just a clever usage of Dynamic SQL to overcome Microsoft's moronic limitation of "string literal error".
IMPORTANT NOTE: yes, you can MOSTLY do this by using sql:variable() in SOME places BUT good luck trying to use it in the node number qualifier inside the square brackets! You can't do this without Dynamic SQL by design!
The trick is not mine actually, I got the idea from https://www.opinionatedgeek.com/Snaplets/Blog/Form/Item/000299/Read after banging my head against the wall for a while.
Feel free to ask questions if my sample does not work or something is not clear.

Related

Extracting a substring after finding a different substring

I've been playing around with Substring, left, right, charindex and can't quite get this to work
If this is the value in column name 'Data' (this is all one line)
{"email":{"RecipientId":"usertest","RecipientEmail":"test#test.com","Subject":"This is a test subject heading","RecipientSubject":"A recipient subject"}}
How do I do a SELECT statement to find the 'Subject' heading and then get the data 'This is a test subject'? The Subject value is different for every record so I just can't look for 'This is a test subject'.
So the end result should be This is a test subject for that SELECT result
The following query should do what you want:
declare #string varchar(max);
set #string = '{"email":{"RecipientId":"usertest","RecipientEmail":"test#test.com","Subject":"This is a test subject heading","RecipientSubject":"A recipient subject"}}';
select substring(#string,charindex('"Subject":',#string)+11,charindex('"RecipientSubject"',#string)-charindex('"Subject"',#string)-13);
The plain and easy-cheesy approach is this:
SELECT SUBSTRING(
t.YourString
,A.StartPosition
,CHARINDEX('"'
,t.YourString
,A.StartPosition+1) - A.StartPosition
)
FROM #dummyTable t
CROSS APPLY(SELECT CHARINDEX('"Subject":"',t.YourString)+11) A(StartPosition)
I use APPLY to calculate a value and use it like you'd use a variable. The idea is: Find the starting point and look for the closing quote from there. But this will break, whenever the content includes an (escaped) quote like in
"Subject":"This is \"quoted\" internally"
A more generic approach
Starting with v2016 JSON-support was introduced. With this (or a higher) version this is really simple:
Use this mockup-table for testing
DECLARE #dummyTable TABLE (YourString VARCHAR(1000));
INSERT INTO #dummyTable VALUES('{"email":{"RecipientId":"usertest","RecipientEmail":"test#test.com","Subject":"This is a test subject heading","RecipientSubject":"A recipient subject"}}');
--The OPENJSON-method will read this for you:
SELECT JsonContent.*
FROM #dummyTable t
CROSS APPLY OPENJSON(t.YourString,'$.email')
WITH(RecipientId VARCHAR(100)
,RecipientEmail VARCHAR(100)
,[Subject] VARCHAR(100)
,RecipientSubject VARCHAR(100)) JsonContent;
But with a lower version you will need to trick this out. It is the easiest, to tranform JSON to attribute centered XML like here:
<email RecipientId="usertest" RecipientEmail="test#test.com" Subject="This is a test subject heading" RecipientSubject="A recipient subject" />
We can achieve this by some string methods and I must warn you, that there are several pit-falls with forbidden characters and other stuff... Just try it out:
SELECT Casted.ToXml.value('(/email/#RecipientId)[1]','varchar(1000)') AS RecipientId
,Casted.ToXml.value('(/email/#RecipientEmail)[1]','varchar(1000)') AS RecipientEmail
,Casted.ToXml.value('(/email/#Subject)[1]','varchar(1000)') AS [Subject]
,Casted.ToXml.value('(/email/#RecipientSubject)[1]','varchar(1000)') AS RecipientSubject
,Casted.ToXml.query('.') LookHowThisWasTransformed
FROM #dummyTable t
CROSS APPLY
(
SELECT CAST(CONCAT('<email '
,REPLACE(REPLACE(REPLACE(REPLACE(t.YourString,'{"email":{"',''),'}}',''),'","','" '),'":"',' ="')
,' />') AS XML)
) Casted(ToXml);

JSON text is not properly formatted. Unexpected character 'N' is found at position 0

I am new to JSON in SQL. I am getting the error "JSON text is not properly formatted. Unexpected character 'N' is found at position 0." while executing the below -
DECLARE #json1 NVARCHAR(4000)
set #json1 = N'{"name":[{"FirstName":"John","LastName":"Doe"}], "age":31, "city":"New York"}'
DECLARE #v NVARCHAR(4000)
set #v = CONCAT('N''',(SELECT value FROM OPENJSON(#json1, '$.name')),'''')
--select #v as 'v'
SELECT JSON_VALUE(#v,'$.FirstName')
the " select #v as 'v' " gives me
N'{"FirstName":"John","LastName":"Doe"}'
But, using it in the last select statement gives me error.
DECLARE #v1 NVARCHAR(4000)
set #v1 = N'{"FirstName":"John","LastName":"Doe"}'
SELECT JSON_VALUE(#v1,'$.FirstName') as 'FirstName'
also works fine.
If you're using SQL Server 2016 or later there is build-in function ISJSON which validates that the string in the column is valid json.
Therefore you can do things like this:
SELECT
Name,
JSON_VALUE(jsonCol, '$.info.address.PostCode') AS PostCode
FROM People
WHERE ISJSON(jsonCol) > 0
You are adding the Ncharacter in your CONCAT statement.
Try changing the line:
set #v = CONCAT('N''',(SELECT value FROM OPENJSON(#json1, '$.name')),'''')
to:
set #v = CONCAT('''',(SELECT value FROM OPENJSON(#json1, '$.name')),'''')
JSON_VALUE function may first be executed on all rows before applying the where clauses. it will depend on execution plan so small things like having top clause or ordering may have a impact on that.
It means that if your json data is invalid anywhere in that column(in the whole table), it will throw an error when the query is executed.
So find and fix those invalid json formats first. for example if that column has a ' instead of " it cannot be parsed and will cause the whole TSQL query to throw an error

unescape diactrics in \u0 format (json) in ms sql (SQL Server)

I'm getting json file, which I load to Azure SQL databese. This json is direct output from API, so there is nothing I can do with it before loading to DB.
In that file, all Polish diactircs are escaped to "C/C++/Java source code" (based on: http://www.fileformat.info/info/unicode/char/0142/index.htm
So for example:
ł is \u0142
I was trying to find some method to convert (unescape) those to proper Polish letters.
In worse case scenario, I can write function which will replace all combinations
Repalce(Replace(Replace(string,'\u0142',N'ł'),'\u0144',N'ń')))
And so on, making one big, terrible function...
I was looking for some ready functions like there is for URLdecode, which was answered here on stack in many topics, and here: https://www.codeproject.com/Articles/1005508/URL-Decode-in-T-SQL
Using this solution would be possible but I cannot figure out cast/convert with proper collation and types in there, to get result I'm looking for.
So if anyone knows/has function that would make conversion in string for unescaping that \u this would be great, but I will manage to write something on my own if I would get right conversion. For example I tried:
select convert(nvarchar(1), convert(varbinary, 0x0142, 1))
I made assumption that changing \u to 0x will be the answer but it gives some Chinese characters. So this is wrong direction...
Edit:
After googling more I found exactly same question here on stack from #Pasetchnik: Json escape unicode in SQL Server
And it looks this would be the best solution that there is in MS SQL.
Onlty thing I needed to change was using NVARCHAR instead of VARCHAR that is in linked solution:
CREATE FUNCTION dbo.Json_Unicode_Decode(#escapedString nVARCHAR(MAX))
RETURNS nVARCHAR(MAX)
AS
BEGIN
DECLARE #pos INT = 0,
#char nvarCHAR,
#escapeLen TINYINT = 2,
#hexDigits TINYINT = 4
SET #pos = CHARINDEX('\u', #escapedString, #pos)
WHILE #pos > 0
BEGIN
SET #char = NCHAR(CONVERT(varbinary(8), '0x' + SUBSTRING(#escapedString, #pos + #escapeLen, #hexDigits), 1))
SET #escapedString = STUFF(#escapedString, #pos, #escapeLen + #hexDigits, #char)
SET #pos = CHARINDEX('\u', #escapedString, #pos)
END
RETURN #escapedString
END
Instead of nested REPLACE you could use:
DECLARE #string NVARCHAR(MAX)= N'\u0142 \u0144\u0142';
SELECT #string = REPLACE(#string,u, ch)
FROM (VALUES ('\u0142',N'ł'),('\u0144', N'ń')) s(u, ch);
SELECT #string;
DBFiddle Demo

xQuery, getting the value with namespaces?

I am just starting with xquery and currently I don't know, why the following is not working. I've created two XML files, they are identically, except that one has namespaces and the other doesn't:
<Envelope>
<Header>
<JustAValue>12345</JustAValue>
</Header>
<Body>
<someBody>
<ValueIWant>12345678910</ValueIWant>
</someBody>
</Body>
</Envelope>
<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
<env:Header xmlns:wsa='http://www.w3.org/2005/08/addressing'>
<JustAValue>12345</JustAValue>
</env:Header>
<env:Body xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' wsu:Id='element-28-1344266615792-223644235'>
<ns1:someBody xmlns='http://someURL.com' xmlns:ns1='http://someURL.com'>
<ValueIWant>12345678910</ValueIWant>
</ns1:someBody>
</env:Body>
</env:Envelope>
Both xml files are in a xml field in the database.
Now I try to get the "ValueIWant" and I do it like this:
DECLARE #xml xml
Select #xml = completexml from MyDB.dbo.Xml where PKxml = 3 -- XML without namespaces
declare #somevalue nvarchar(max)
set #somevalue = #xml.value('(/Envelope/Body/someBody/ValueIWant/text())[1]', 'nvarchar(max)');
select #somevalue -- -> 12345678910
Select #xml = completexml from MyDB.dbo.Xml where PKxml = 2 -- XML with namespaces
set #somevalue = #xml.value('
declare namespace env="http://schemas.xmlsoap.org/soap/envelope/";
declare namespace ns1="http://someURL.com";
(/env:Envelope/env:Body/ns1:someBody/ValueIWant/text())[1]', 'nvarchar(max)');
select #somevalue -- -> NULL
What am I doing wrong here?
I've played around a bit with the XML:
<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
<env:Header xmlns:wsa='http://www.w3.org/2005/08/addressing'>
<JustAValue>12345</JustAValue>
</env:Header>
<env:Body xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' wsu:Id='element-28-1344266615792-223644235'>
<ns1:someBody xmlns='http://someURL.com' xmlns:ns1='http://someURL.com'>
<ValueIWant>12345678910</ValueIWant>
</ns1:someBody>
<BodyValue>BodyValue</BodyValue>
<env:BodyValue>BodyValue</env:BodyValue>
</env:Body>
<env:TestValue1>TestValue</env:TestValue1>
<TestValue2>TestValue2</TestValue2>
</env:Envelope>
I can get all the values: BodyValue, TestValue1 and TestValue2. No problem at all, but as soon as I want to change into the ns1 namespace and get ValueIWant, it just returns NULL.
The ValueIWant element tag name is in the default namespace (http://someURL.com), because this namespace is bound to the empty prefix by its parent element:
<ns1:someBody xmlns='http://someURL.com' ...
However in the query there seems to be no default namespace, so that the name test ValueIWant does not match the ValueIWant tag.
Since it is the same URL that is bound to the ns1 prefix, perhaps prefixing ValueIWant with ns1 in your XPath expression would solve the problem?
Alternatively, you could add a
declare default element namespace "http://someURL.com";
in your query. But using the ns1 prefix should normally work as well, because when comparing names ("QNames"), only the namespace and the local name matter, the prefix is irrelevant.

Having long strings in MySql stored procedures

Is there a way of enabling a long strings to be put onto multiple lines so that when viewed on screen or printed the code is easier to read?
Perhaps I could be clearer.
Have a stored procedure with lines like
IF ((select post_code REGEXP '^([A-PR-UWYZ][A-HK-Y]{0,1}[0-9]{1,2} [0-9][ABD-HJLNP-UW-Z]{2})|([A-PR-UWYZ][0-9][A-HJKMPR-Y] [0-9][ABD-HJLNP-UW-Z]{2})|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRV-Y]) [0-9][ABD-HJLNP-UW-Z]{2})$') = 0)
Would like to be able to modify the string so that I can view it within 80 character width. Anybody got any ideas of how to do this.
PS: It is the regular expression for UK postcodes
For example,
-- a very long string in one block
set my_str = 'aaaabbbbcccc';
can be also written as
-- a very long string, as a concatenation of smaller parts
set my_str = 'aaaa' 'bbbb' 'cccc';
or even better
-- a very long string in a readable format
set my_str = 'aaaa'
'bbbb'
'cccc';
Note how the spaces and end of line between the a/b/c parts are not part of the string itself, because of the placement of quotes.
Also note that the string data here is concatenated by the parser, not at query execution time.
Writing something like:
-- this is broken
set my_str = 'aaaa
bbbb
cccc';
produces a different result.
See also
http://dev.mysql.com/doc/refman/5.6/en/string-literals.html
Look for "Quoted strings placed next to each other are concatenated to a single string"
You could split it up into the front and back components of the postcode and then dump the whole lot into a UDF.
This will keep the ugliness in one place and means you'll only have to make changes to one block of code when/if Royal Mail decide to change the format of UK postcodes ;-)
Something like this should do the trick:
DELIMITER $$
CREATE FUNCTION `isValidUKPostcode`(candidate varchar(255)) RETURNS BOOLEAN READS SQL DATA
BEGIN
declare back varchar(3);
declare front varchar(10);
declare v_out boolean;
set back = substr(candidate,-3);
set front = substr(candidate,1,length(candidate)-3);
set v_out = false;
IF (back REGEXP '^[0-9][ABD-HJLNP-UW-Z]{2}$'= 1) THEN
CASE
WHEN front REGEXP '^[A-PR-UWYZ][A-HK-Y]{0,1}[0-9]{1,2} $' = 1 THEN set v_out = true;
WHEN front REGEXP '^[A-PR-UWYZ][0-9][A-HJKMPR-Y] $' = 1 THEN set v_out = true;
WHEN front REGEXP '^[A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRV-Y] $' = 1 THEN set v_out = true;
END CASE;
END IF;
return v_out;
END