How to reference JSON list value with a variable - sql

I am trying to do this:
JSON_VALUE(#jsondata, CONCAT('$.[', #count, '].Name')
such that I can reference a value in my json list using a variable, however the JSON_VALUE function requires a string literal for the 2nd argument (JSON Path).
Is there a way to either make the function read a string as a string literal, or another way to reference a list with a variable?
Thanks.

You can address a list with a variable by destructuring it into a table using OPENJSON.
For example, given:
declare #jsondata nvarchar(max) = '[
{"Name":"zero","Value":"cero"},
{"Name":"one","Value":"uno"},
{"Name":"two","Value":"dos"},
{"Name":"three","Value":"tres"}
]'
declare #count int set #count = 1
declare #val nvarchar(50)
set #val = (select JSON_VALUE(value, '$.Name') from OPENJSON(#JsonData) d where d."key" = #count)
select #count, #val
For a more advanced method you can query JSON with a schema that describes the structure and elements you are interested in:
https://learn.microsoft.com/en-us/sql/relational-databases/json/use-openjson-with-an-explicit-schema-sql-server

Related

SQL Server Xquery sql:variable usage

I need to use a dynamic string for an xquery path but .query/.nodes methods require a literal string as parameter. So I decided to try sql:variable
DECLARE #xmlData XML, #node varchar(max)
SET #xmlData = 'Some XML Here'
SET #node = '/path1/path2/path3'
When I query with
select #xmlData.query('/path1/path2/path3')
It returns the intended result
But when I query with
select #xmlData.query('sql:variable("#node")')
It returns the variable value itself as "/path1/path2/path3"
What is wrong here?
This should do the trick:
select #xmlData.query('/*[local-name()=sql:variable("#node")]')
It matches any node with a wildcard *, but there is an extra predicate that the name has to match the variable
For performance reasons, you should preferably use /text() to get inner text, and use .values to get a single value.
select #xmlData.value('(/*[local-name()=sql:variable("#node")]/text())[1]', 'nvarchar(100)')
sql:variable is used to substitute a single scalar variable into an XPath expression, so can't be used to define a full path. You can use it to test against a single node's name, though, e.g.:
declare #xmlData XML = '<path1><path2><path3>foo</path3></path2></path1>'
select [example1] = #xmlData.query('/path1/path2/path3')
--example1
--<path3>foo</path3>
declare #node1 varchar(max) = 'path1'
declare #node2 varchar(max) = 'path2'
declare #node3 varchar(max) = 'path3'
select [example2] = #xmlData.query('//*[local-name()= sql:variable("#node1")]/*[local-name()= sql:variable("#node2")]/*[local-name()= sql:variable("#node3")]');
--example2
--<path3>foo</path3>

Use xQuery to extract attribute values that also exist in SQL variable

XML structure:
<ns0:message xmlns:ns0='xxx:testing'>
<ns0:field name='AAA'>...</ns0:field>
<ns0:field name='BBB'>...</ns0:field>
<ns0:field name='VVV'>...</ns0:field>
<ns0:field name='CAR'>...</ns0:field>
</ns0:message>
I have SQL that will extract the values of the attributes titled name:
SELECT
( (SELECT ',' + CHAR(13) + P.N.value('#name', 'varchar(max)')
FROM myTable.message_xml.nodes('declare namespace ns0="xxx:testing";
ns0:message/ns0:field[ #name = "AAA" or
#name = "BBB"]') P(N)
FOR XML PATH(''), type).value('substring(text()[1], 3)', 'varchar(max)')) as ATTRIBUTE_VALUES
This returns a column that looks like:
ATTRIBUTE_VALUES
---------------
AAA,
BBB
My problem is that the list of potential attribute values is quite large.
Instead of repeating #name = "AAA" in my query for every attribute value I want to check for, I was hoping I could declare it as a variable like:
DECLARE #ATTRIBUTES VarChar(Max)
SET #ATTRIBUTES = '(AAA,BBB,CAR,XYZ)'
And then just stick the variable in the sql like:
[#name = sql:variable("#ATTRIBUTES")]
but this is not working for any combination of parens,commas,etc I use to build the variable.
You can use contains function.
Declare a variable like so:
DECLARE #ATTRIBUTES VarChar(Max) = '|AAA|BBB|CAR|XYZ|';
And in you query instead of #name = "AAA" use:
contains(sql:variable("#ATTRIBUTES"), concat("|", #name, "|"))

Extracting a single value from a json array in sql server

I am using MS SQL server to get a search result in json format there is only ever 1 row returned in my use case but they designed this as a search tool so you can return more than one value hence the array. The issue I am having is extracting the id value from the array that is returned.
json #response (Array):
{"hits":[{"id":1320172,"email":"xyz#domain.eu","first_name":"IMA","last_name":"TESTERTOO","created":"2018-12-12T11:52:58+00:00","roles":["Learner"],"status":true}],"total":1}
I have tried a number of things but I can't seem to get the path right.
SET #MyUserid = JSON_QUERY(#Reponse, '$.hits[0].id')
SET #MyUserid =JSON_VALUE(#Reponse,'$.hits[0].id')
SET #MyUserid = JSON_QUERY(#Reponse, '$.id')
On most examples I have found the json is not a single line array so I feel like I am missing something there. I'm inexperienced with working with json so any help would be greatly appreciated.
You can try this
DECLARE #json NVARCHAR(MAX)=
N'{"hits":[{"id":1320172,"email":"xyz#domain.eu","first_name":"IMA","last_name":"TESTERTOO","created":"2018-12-12T11:52:58+00:00","roles":["Learner"],"status":true}],"total":1}';
--This will return just one selected value
SELECT JSON_VALUE(#json,'$.hits[0].id')
--This will return the whole everything:
SELECT A.total
,B.*
FROM OPENJSON(#json)
WITH(hits nvarchar(max) AS JSON, total int) A
CROSS APPLY OPENJSON(A.hits)
WITH(id int
,email nvarchar(max)
,first_name nvarchar(max)
,last_name nvarchar(max)
,created nvarchar(max)
,roles nvarchar(max) AS JSON
,[status] bit) B

Extracting nvarchar value from XML in T-SQL: only one character returned

In my T-SQL procedure I'm trying to extract a string value from the XML node using the .value() method, like this:
declare #criteria xml;
set #criteria = N'<criterion id="DocName"><value>abcd</value></criterion>';
declare #val nvarchar;
set #val = #criteria.value('(criterion[#id="DocName"]/value)[1]', 'nvarchar');
select #val;
I expected to get 'abcd' as a result, but I surprisingly got just 'a'.
So, the value method returns only the 1st character of the string. Can anybody tell me, what am I doing wrong? Thanks a lot.
P.S. I'm using MS SQL Server 2012
Don't use nvarchar without size. From documentation:
When n is not specified in a data definition or variable declaration
statement, the default length is 1. When n is not specified with the
CAST function, the default length is 30.
If you don't know exact length, you always can use nvarchar(max):
declare #criteria xml;
set #criteria = N'<criterion id="DocName"><value>abcd</value></criterion>';
declare #val nvarchar(max);
set #val = #criteria.value('(criterion[#id="DocName"]/value)[1]', 'nvarchar(max)');
select #val;
sql fiddle demo

Is it possible to declare a string type without fixed length for filtering on fixed length characters?

Is there a way in sql where I can declare a string type without a fixed length for filtering on a nvarchar field with a fixed length?
For example this is what I have to do now -
declare #input nvarchar(255)
set #input = 'test'
select * from table
where field = #input
This is something along the line of what I want to be able to do -
declare #input string
set #input = 'test'
select * from table
where field = #input
Just declare your variables as nvarchar(4000) or nvarchar(max).
The lengths of the strings do not need to match for the comparison to work, for variable length strings.
This is not true of fixed length strings, but that is another matter entirely.