Select last value from Json array - json

I need to get the last item in an array from a JSON format. I have this JSON.
#json =
N'{
"solution": "xxxxxxxxxxxxxxxxxxxxx",
"options": [
{
"choice_id": 205073,
"choice": "aaaa"
},
{
"choice_id": 205074,
"choice": "bbbb"
},
{
"choice_id": 205075,
"choice": "cccc"
},
{
"choice_id": 205076,
"choice": "dddd"
}
],
}'
And I would like to get
#json =
N'{
"solution": "xxxxxxxxxxxxxxxxxxxxx",
"options": {
"choice_id": 205076,
"choice": "dddd"
}
}
How can I achieve that result?

You can try this
DECLARE #json NVARCHAR(MAX) =
N'{
"solution": "xxxxxxxxxxxxxxxxxxxxx",
"options": [
{
"choice_id": 205073,
"choice": "aaaa"
},
{
"choice_id": 205074,
"choice": "bbbb"
},
{
"choice_id": 205075,
"choice": "cccc"
},
{
"choice_id": 205076,
"choice": "dddd"
}
]
}';
SELECT TOP 1
A.solution
,JSON_QUERY(B.[value]) AS options
FROM OPENJSON(#json) WITH (solution nvarchar(max), options NVARCHAR(MAX) AS JSON) A
CROSS APPLY OPENJSON(A.options) B
ORDER BY B.[key] DESC
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;
The result
{"solution":"xxxxxxxxxxxxxxxxxxxxx"
,"options":{
"choice_id": 205076,
"choice": "dddd"
}
}
Some explanation
FROM OPENJSON will dive into your JSON and find the top-level elements. We fetch solution as is and we state AS JSON for options to specify, that this value will be treated as JSON later on.
The CROSS APPLY will call OPENJSON again, but this time against the options and we will get an array back. The column key is the ordinal position within the array, so we can use TOP 1 and ORDER BY key DESC to get the last element in the array.
Before we can re-assemble this query to a JSON we have to wrap B.value with JSON_QUERY(). Otherwise we would see escaped characters like \t or \r\n within the JSON. The reason: Without this, the JSON-string would not be taken as a JSON-string but as any other string and would be packed into the result as one single text value.

You can either shred with OPENJSON and re-assemble the document FOR JSON (see #digital.aaron's answer), or use JSON_MODIFY, like this:
declare #json nvarchar(max) =
N'{
"solution": "xxxxxxxxxxxxxxxxxxxxx",
"options": [
{
"choice_id": 205073,
"choice": "aaaa"
},
{
"choice_id": 205074,
"choice": "bbbb"
},
{
"choice_id": 205075,
"choice": "cccc"
},
{
"choice_id": 205076,
"choice": "dddd"
}
]
}'
declare #options nvarchar(max) = (
select top 1 value
from openjson(#json,'$.options')
order by [key] desc
);
set #json = json_modify(#json, '$.options', json_query(#options))
select #json
Outputs
{
"solution": "xxxxxxxxxxxxxxxxxxxxx",
"options": {
"choice_id": 205076,
"choice": "dddd"
}
}
Note that the JSON_QUERY function is also used for parsing a string into a JSON fragment. Without that, the value in #options would be inserted as a single string value, instead of a json object.

First, your JSON string is malformed. You need to remove the comma after the closing array bracket.
So let's declare our variable:
DECLARE #json NVARCHAR(MAX) = N'{
"solution": "xxxxxxxxxxxxxxxxxxxxx",
"options": [
{
"choice_id": 205073,
"choice": "aaaa"
},
{
"choice_id": 205074,
"choice": "bbbb"
},
{
"choice_id": 205075,
"choice": "cccc"
},
{
"choice_id": 205076,
"choice": "dddd"
}
]
}'
Now you can use a combination of OPENJSON() and FOR JSON to get the last record in the array.
SELECT jsonfield = CAST((
SELECT TOP 1
j.solution
,o.choice_id
,o.choice
FROM
OPENJSON(#json)
WITH
(
solution VARCHAR(MAX) '$.solution'
,options NVARCHAR(MAX) '$.options' AS JSON
) j
CROSS APPLY
OPENJSON(options)
WITH
(
choice_id INT '$.choice_id'
,choice VARCHAR(4) '$.choice'
) o
ORDER BY o.choice_id DESC
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) AS NVARCHAR(MAX))
EDIT:
Or you can use this query, if you don't want to order by any of the node values:
SELECT jsonfield = CAST((
SELECT TOP 1
j.solution
--,o.*
,c.choice_id
,c.choice
FROM OPENJSON(#json)
WITH
(
solution VARCHAR(MAX) '$.solution'
,options NVARCHAR(MAX) '$.options' AS JSON
) j
CROSS APPLY OPENJSON(options) o
CROSS APPLY OPENJSON(o.Value)
WITH (
choice_id int '$.choice_id',
choice varchar(4) '$.choice'
) c
ORDER BY [key] DESC
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) AS NVARCHAR(MAX))

Related

Using SQL JSON_MODIFY to add/update a dictionary value

Is it possible to use JSON_MODIFY to update a dictionary value of <int,string>?
If my JSON is this:
{
"items": {
"1": "xxx",
"2": "xxx",
"3": "xxx"
}
}
I'm unable to add/update using the following:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.', #Id), #Value)
I get the error: JSON path is not properly formatted. Unexpected character '1' is found at position 10. obviously because property names can't be numeric. I can workaround the error using a <string,string> dictionary as follows:
{
"items": {
"i1": "xxx",
"i2": "xxx",
"i3": "xxx"
}
}
Then the following works fine:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.i', #Id), #Value)
However, now I'm tasked with extra work after deserializing the JSON to get the numeric ID I actually want. It's just a minor inconvenience, but I was hoping for a possible solution to keep my <int,string> dictionary. Any ideas?
I suppose I could use a dictionary like this <string,object> and simply ignore the keys when deserialized:
{
"items": {
"i1": { "id": 1, "name": "xxx" },
"i2": { "id": 1, "name": "xxx" },
"i3": { "id": 1, "name": "xxx" }
}
}
And then use
UPDATE MyTable SET MyJsonColumn = JSON_MODIFY(MyJsonColumn, CONCAT('$.items.i', #Id), JSON_QUERY(CONCAT('{"id":', #Id, ',"name":"', #Value, '"}')))
I'm also not opposed to using an array as such:
{
"items": [
{ "id": 1, "name": "xxx" },
{ "id": 2, "name": "xxx" },
{ "id": 3, "name": "xxx" }
]
}
Unfortunately, I don't know a way to update/add when in this format, and the add/update has to be done in a single UPDATE statement.
I'm hoping for some magic formula to continue using my <int,string> dictionary.
Thanks to #Charlieface, the solution is quite simple. You can navigate to numeric dictionary keys via double quotes:
DECLARE #Id INT = 1
DECLARE #Value NVARCHAR(50) = 'zzz'
UPDATE MyTable
SET MyJsonColumn = JSON_MODIFY(ISNULL(MyJsonColumn, '{"items":{}}'), CONCAT('$.items."', #Id, '"'), #Value)

Can't get access to data from nested json's array

How to retrieve values from employment_types (type, salary) and skills (name, level) arrays and show them in columns? I tried with employment_types and it doesn't work not to mention skills:
declare #json nvarchar(max)
set #json = '[
{
"title": "IT Admin",
"experience_level": "mid",
"employment_types": [
{
"type": "permanent",
"salary": null
}
],
"skills": [
{
"name": "Security",
"level": 3
},
{
"name": "WIFI",
"level": 3
},
{
"name": "switching",
"level": 3
}
]
},
{
"title": "Lead QA Engineer",
"experience_level": "mid",
"employment_types": [
{
"type": "permanent",
"salary": {
"from": 7000,
"to": 13000,
"currency": "pln"
}
}
],
"skills": [
{
"name": "Embedded C",
"level": 4
},
{
"name": "Quality Assurance",
"level": 4
},
{
"name": "C++",
"level": 4
}
]
}
]';
SELECT *
FROM OPENJSON(#JSON, '$.employment_types')
WITH
(
type nvarchar(50) '$.type',
salary varchar(max) '$.salary'
)
There are almost 7000 records and I'd like to show mentioned above columns from all of them.
It's hard to know exactly what you want, given that both employment_types and skills are arrays. But assuming employment_types always has only one element, you could do something like this
SELECT
j1.title,
j1.experience_level,
j1.employment_type,
salary = j1.salary_currency + ' ' + CONCAT(j1.salary_from, ' - ', j1.salary_to),
j2.name,
j2.level
FROM OPENJSON(#JSON)
WITH (
title nvarchar(100),
experience_level nvarchar(10),
employment_type nvarchar(50) '$.employment_types[0].type',
salary_from int '$.employment_types[0].salary.from',
salary_to int '$.employment_types[0].salary.to',
salary_currency char(3) '$.employment_types[0].salary.currency',
skills nvarchar(max) AS JSON
) j1
CROSS APPLY OPENJSON(j1.skills)
WITH
(
name nvarchar(50),
level int
) j2
db<>fiddle
Since we are pulling data directly from the root object, we don't need a JSON path argument. OPENJSON will automatically break out an array into separate rows. If you just wanted employment_types, you could go directly to that with a path argument.
employment_types[0] means to only get the first element of the array. If you want all the elements, you will need another OPENJSON
Note the use of AS JSON for skills, this means that the entire JSON array is pulled out, and can then be pushed through another call to OPENJSON

Get Children nodes in json string using sql stored procedure?

I have a JSON string passed into stored procedure as follows,
{"config": {
"site": "Internal",
"library": "test-library",
"folderHierarchy": {
"name": "folder1",
"children": {
"name": "folder2",
"children": {
"name": "folder3"
}
}
},
"meta_data": [{
"meta_text": "date-created",
"meta_value": "2020-04-17"
}, {
"meta_text": "date-modified",
"meta_value": "2020-04-17"
}]
}}
I need to get folder names inside folderHierarchy node , it can have many number or folders inside as children.
First I tried to get folderHierarchy as a JSON string and iterate through that JSON string.
DECLARE #CONFIG VARCHAR(MAX) = '{"config": {
"site": "Internal",
"library": "test-library",
"folderHierarchy": {
"name": "folder1",
"children": {
"name": "folder2",
"children": {
"name": "folder3"
}
}
},
"meta_data": [{
"meta_text": "date-created",
"meta_value": "2020-04-17"
}, {
"meta_text": "date-modified",
"meta_value": "2020-04-17"
}]
}}'
DECLARE #folderHierarchy VARCHAR(MAX);
SET #folderHierarchy = JSON_QUERY(#CONFIG, '$.config.folderHierarchy');
SELECT #folderHierarchy;
SELECT *
FROM openjson(#folderHierarchy)
WITH(
name VARCHAR(max) '$.name',
childern NVARCHAR(MAX) '$.children' AS JSON
) AS P
OUTER APPLY OPENJSON(P.childern)
WITH (name NVARCHAR(MAX) '$.name');
But this give me the first and second folder names.
Can anyone help me to get all the folder names, the main thing is I haven't control about number of child item in a folder.
I mean folder1 can have folder called folder2, and folder 2 can have folder called folder 3, and folder3 can have folder called folder4 etc.. etc..
You could use a Recursive CTE for this. The default settings on SQL Server will allow you to recurse up to 100 levels deep (see MAXRECUSRION)...
declare #Config nvarchar(max) =
N'{"config": {
"site": "Internal",
"library": "test-library",
"folderHierarchy": {
"name": "folder1",
"children": {
"name": "folder2",
"children": {
"name": "folder3"
}
}
},
"meta_data": [{
"meta_text": "date-created",
"meta_value": "2020-04-17"
}, {
"meta_text": "date-modified",
"meta_value": "2020-04-17"
}]
}}';
with JsonFolders as (
select
1 as level,
name,
children
from openjson(#Config, N'$.config.folderHierarchy') with (
name nvarchar(max) N'$.name',
children nvarchar(max) N'$.children' as json
)
union all
select
1 + level,
Child.name,
Child.children
from JsonFolders JF
cross apply openjson(JF.children) with (
name nvarchar(max) N'$.name',
children nvarchar(max) N'$.children' as json
) Child
)
select * from jsonFolders;

How can I loop through array of json in SQL Server?

I want to loop through data to get taggedEntityName and taggedEntityId:
{
"data": [
{
"taggedEntityName": "Organization",
"taggedEntityId": [
145642,
145625
],
"index": 0
},
{
"taggedEntityName": "Job",
"taggedEntityId": [
221138
],
"index": 1
}
]
}
If you use SQL Server 2016+, you need to use OPENJSON() to parse the input JSON. The structure of the input JSON is always important and in this specific case you need to use OPENSJON() with explicit schema twice:
JSON:
DEClARE #json nvarchar(max) = N'{
"data": [
{
"taggedEntityName": "Organization",
"taggedEntityId": [
145642,
145625
],
"index": 0
},
{
"taggedEntityName": "Job",
"taggedEntityId": [
221138
],
"index": 1
}
]
}'
Statement:
SELECT j1.taggedEntityName, j2.taggedEntityId
FROM OPENJSON(#json, '$.data') WITH (
taggedEntityName varchar(50) '$.taggedEntityName',
taggedEntityId nvarchar(max) '$.taggedEntityId' AS JSON
) j1
CROSS APPLY OPENJSON(j1.taggedEntityId) WITH (
taggedEntityId int '$'
) j2
Result:
taggedEntityName taggedEntityId
Organization 145642
Organization 145625
Job 221138

Querying JSON nested array object in SQL

I have the following JSON and i need to query only the name property values.
DECLARE #j NVARCHAR(4000) = N'{
"ArrayValue":[
{
"name": "XXX",
"value": 10
},
{
"name": "Memory123",
"value": 20
}
]
}'
Following is what i get with OPENJSON()
SELECT value as Name
FROM OPENJSON(#j, '$.ArrayValue')
Whcih will get the following output.
Name
---------------------------------------------------
{ "name": "Memory" }
{ "name": "Memory123" }
However I need the output in the following format.
Name
-----
Memory
Memory123
Is there any simpler way without using replace?
You can define ArrayValue as a JSON column, then use CROSS APPLY to get the name values. Here's an example:
DECLARE #j NVARCHAR(4000) = N'{
"ArrayValue":[
{
"name": "XXX",
"value": 10
},
{
"name": "Memory123",
"value": 20
}
]
}'
SELECT b.name
FROM OPENJSON(#j)
WITH (ArrayValue NVARCHAR(MAX) AS JSON) AS a
CROSS APPLY OPENJSON(a.ArrayValue)
WITH ([name] NVARCHAR(100)) AS b