TSQL how to merge two Json arrays - json

How to merge new array elements into an existing Json property?
Our two json objects look like this and we want to append the items of the 2nd json object to the 1st json object. (See desired result)
Is there a way to achieve this with JSON_MODIFY?
The microsoft documentation doesn't really show any example of multiple elements being merged into the already existing array. Only a single element. But we have a list of multiple elements that need to be merged.
Edit:
JSON_MODIFY(#json1, 'append $.Items', JSON_QUERY(#json2)) seems to create new array brackets instead of merging the items into the array.
Json Object 1:
DECLARE #json1 NVARCHAR(MAX) = '{
"id": 1,
"Items": [
{
"id": 1,
"name" : "Item #1"
},
{
"id": 2,
"name" : "Item #2"
}
]
}'
Json Object 2:
DECLARE #json2 NVARCHAR(MAX)='{
"Items": [
{
"id": 3,
"name": "Item #3"
},
{
"id": 4,
"name": "Item #4"
}
]
}'
Desired Result:
{
"id": 1,
"Items": [
{
"id": 1,
"name": "Item #1"
},
{
"id": 2,
"name": "Item #2"
},
{
"id": 3,
"name": "Item #3"
},
{
"id": 4,
"name": "Item #4"
}
]
}

It's Cumbersome but possible to achieve with SQL Server's built it JSON support.
First, set proper sample data (Please save us this step in your future questions):
DECLARE #Json1 nvarchar(max) =
'{
"id": 1,
"Items": [
{
"id": 1,
"name" : "Item #1"
},
{
"id": 2,
"name" : "Item #2"
}
]
}',
#Json2 nvarchar(max) =
'{
"Items": [
{
"id": 3,
"name": "Item #3"
},
{
"id": 4,
"name": "Item #4"
}
]
}';
Then, wrap a union all query containing openjson and json_query for each one of the variables with a common table expression:
With cteArray as
(
SELECT *
FROM OPENJSON(JSON_QUERY(#Json1, '$.Items'))
WITH(
Id int '$.id',
Name varchar(100) '$.name'
)
UNION ALL
SELECT *
FROM OPENJSON(JSON_QUERY(#Json2, '$.Items'))
WITH(
Id int '$.id',
Name varchar(100) '$.name'
)
)
The result of that union all query is this:
Id Name
1 Item #1
2 Item #2
3 Item #3
4 Item #4
Then, select the id from the first json using json_value, and add a subquery to select everything from the cte with for json path. Add another for json path and specify without_array_wrapper to the outer query:
SELECT JSON_VALUE(#Json1, '$.id') As id,
(
SELECT *
FROM cteArray
FOR JSON PATH
) as Items
FOR JSON PATH,
WITHOUT_ARRAY_WRAPPER
The final result:
{
"id": "1",
"Items": [{
"Id": 1,
"Name": "Item #1"
}, {
"Id": 2,
"Name": "Item #2"
}, {
"Id": 3,
"Name": "Item #3"
}, {
"Id": 4,
"Name": "Item #4"
}
]
}
You can see a live demo on Db<>Fiddle

Your problem can be solved by
SELECT dbo.udf_native_json_merge(#json1,#json2,null)
We faced similar issues trying to merge JSONs in MS SQL. We also wanted it to be recursive and allow us to define a strategy for arrays like "union", "concat" and "replace".
Our solution for JSON manipulations like merge, JSON path expressions and more is open source and available # Github
Feel free to use, comment and contribute so we can further improve JSON methods for MS SQL.

Not pretty but this will merge text and object elements. I'm sure it isn't bullet proof. Offered only as a potential solution.
Declare #json1 nvarchar(max) = '{"id":1,"messages":[{"type":"Info","text":"message1"},{"type":"Info","text":"message2"}]}'
Declare #json2 nvarchar(max) = '{"id":1,"messages":["justPlanText",{"type":"Info","text":"message3"},{"type":"Info","text":"message4"}]}'
select #json1 = case
when isjson(m.value) = 1 then
json_modify(#json1,'append $.messages',json_query(m.value))
else
json_modify(#json1,'append $.messages',m.value)
end
from openjson(json_query(#json2,'$.messages')) m
select #json1
Results for #json1:
{"id":1,"messages":[{"type":"Info","text":"message1"},{"type":"Info","text":"message2"},"justPlanText",{"type":"Info","text":"message3"},{"type":"Info","text":"message4"}]}

Related

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

Retrieving nested json information from a postgresql table - no current index

I'd like to retrieve nested json information from a jsonb field (sub_table) in a postgresql table (prices).
I'm able to retrieve the json using the command:
with jsontable as (
SELECT "sub_table"
FROM "prices"
WHERE "Scenario" = 'A' AND "data_type" = 'new'
)
SELECT * from jsontable
This returns a json table like this:
{
"0": {
"Name": "CompX",
"Price": 10,
"index": 1,
"Date": "2020-01-09T00:00:00.000Z"
},
"1": {
"Name": "CompY",
"Price": 20,
"index": 1,
"Date": "2020-01-09T00:00:00.000Z"
},
"2": {
"Name": "CompX",
"Price": 19,
"index": 2,
"Date": "2020-01-10T00:00:00.000Z"
}
}
I want to return all data relating to Name = "CompX" but can't get the query to work.
I've tried to follow the examples here, but can't work it out. Do I need to reindex in some way?
You can return extracted JSONB data row-wisely by using jsonb_each function with filtering out by (j.value -> 'Name')::text = '"CompX"' condition :
SELECT j.value
FROM prices p
CROSS JOIN jsonb_each(sub_table) AS j(e)
WHERE (j.value -> 'Name')::text = '"CompX"'
Demo

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;

Add an element at the beginning of a JSON using JSON_MODIFY

I am trying to build a JSON from the following table
name | flag
------+------
foo | fail
bar | pass
using the query,
DECLARE #JSONDATA nvarchar(MAX) = (SELECT [name], [flag]
FROM test
FOR JSON AUTO, ROOT('students'))
SET #JSONDATA = JSON_MODIFY(#JSONDATA, '$.class','10')
The generated JSON here is
{
"students": [
{
"name": "foo",
"flag": "fail"
},
{
"name": "bar",
"flag": "pass"
}
],
"class": "10"
}
I need to the class element at the very first node of the JSON. Is there any way, using JSON_MODIFY ?
Fiddle
At a loss forcing a sequence via modify.
Perhaps an alternative
Select class=10
,students = (SELECT [name], [flag] FROM test FOR JSON AUTO)
For JSON path, without_array_wrapper
Returns
{
"class": 10,
"students": [{
"name": "foo",
"flag": "fail"
}, {
"name": "bar",
"flag": "pass"
}]
}
EDIT- Updated SELECT as suggested by GSerg

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