Create json object from tree structure in Postgresql - json

I have a nested tree data structure of groups and layers that need to be generated from a couple of joined database tables and then be stored as JSON in a PostgreSQL (version 12.8) database. Groups can contain layer objects, but also (sub)groups. The desired JSON output (generated via SQL and/or function) would look like shown below.
[{
"title": "Folder 1",
"type": "group",
"folded": false,
"layers": [{
"title": "Layer 1",
"type": "WMS",
"visible": true
},
{
"title": "Folder 2",
"type": "group",
"folded": true,
"layers": [{
"title": "Layer 2",
"type": "WMS",
"visible": true
},
{
"title": "Layer 3",
"type": "WMS",
"visible": true
},
{
"title": "Folder 3",
"type": "group",
"folded": true,
"layers": [{
"title": "Layer 4",
"type": "WMS",
"visible": false
},
{
"title": "Layer 5",
"type": "WMS",
"visible": true
}
]
}
]
}
]
}]
These are the database tables and sample data:
CREATE TABLE IF NOT EXISTS jtest.folders
(
id serial,
item_id integer,
title text,
folded boolean NOT NULL
);
CREATE TABLE IF NOT EXISTS jtest.layers
(
id serial,
item_id integer,
title text,
type text,
visible boolean
);
CREATE TABLE IF NOT EXISTS jtest."connect"
(
id serial,
item_id integer,
child_id integer,
layer boolean DEFAULT true
);
--data
INSERT INTO jtest.folders(id, item_id, title, folded) VALUES
(1,1,'Folder 1',false),(2,2,'Folder 2', true),(3,3,'Folder 3', true);
INSERT INTO jtest.layers(item_id,id,title,type,visible) VALUES
(1,4,'Layer 1','WMS',true),(2,5,'Layer 2','WMS',true),(3,6,'Layer 3','WMS',true),(4,7,'Layer 4','WMS',true),(5,8,'Layer 5','WMS',true);
INSERT INTO jtest.connect (item_id, child_id, layer) VALUES
(1,4,true),(1,2,false),(2,5,true),(2,6,true),(2,3,false),(3,7,true),(3,8,true);
I partially succeeded to generate a JSON output for a simple folder-layer tree using queries like the one below, but could not figure out how to correctly handle folders nested into a list of layers (aka sub folders).
SELECT jsonb_agg(sub)
from (
SELECT f.id, f.folded,f.item_id,f.title, to_jsonb(array_agg(l.*)) as layers
FROM jtest.folders f
JOIN jtest.connect c ON f.item_id = c.item_id
JOIN jtest.layers l ON l.item_id = c.child_id
where c.layer is true
GROUP BY f.id,f.folded,f.item_id,f.title
ORDER BY f.item_id ) sub;
Any ideas or examples on how to solve this ?

Related

How to update entire JSON object in JSONB postgres column except 1 field

for example I have a table:
CREATE TABLE fruit(id bigint, data jsonb);
and a row for example is:
1,
{
"type": "pinapple",
"store1": {
"first_added": "<some timestamp>",
"price": "10",
"store_id": "1",
"comments": "some comments..."
},
"store2": {
"first_added": "<some timestamp>",
"price": "11",
"store_id": "2",
"comments": "some comments..."
},
.... more stores
}
In case of update I have the fruit id and store data :
1,
"store1": {
"price": "12",
"store_id": "1",
"comments": "some comments...V2"
}
I want to update entire store object in fruit entry (for store1), except the first_added field.
Any idea how I can accomplish it via JSONB operators or functions?
Thanks
You can use
UPDATE fruit
SET data = data || jsonb_set($1::jsonb, '{store1,first_added}', data#>'{store1,first_added}')
WHERE id = 1;
(online demo)
where the parameter $1 is set to the value {"store1": {"price": "12", "store_id": "1", "comments": "some comments...V2"}}.
Or if you need the key to be dynamic, use
UPDATE fruit
SET data = jsonb_set(data, ARRAY[$2::text], jsonb_set($1::jsonb, '{first_added}', data->$2->'first_added'))
WHERE id = 1;
(online demo)
You can use the jsonb_set function to change the desired element, then use the jsonb_build_object function to create a new dataset, then concatenate the data with the || operator to keep the rest of the data(first_added,...)
update table1
set data = jsonb_set(data, '{store1}', jsonb_build_object('first_added', data->'store1'->'first_added', 'price', 12, 'store_id', 1, 'comments', 'some comments...V2'))
where id = 1;
Demo in DBfiddle

how to extract data from a json file into table using sql query

How can I import data from JSON file into a table using SQL? I have a problem while using SQL that I am not able to read "value" "key" as they are reserved word.
Here is part of the SQL query and part of the JSON file:
DECLARE #JSON_table VARCHAR(MAX)
SELECT #JSON_table = BulkColumn
FROM OPENROWSET (BULK 'C:\Users\dbjx1038\Desktop\sql\inventory.json', SINGLE_CLOB) import
SELECT ISJSON(#JSON_table)
SELECT #JSON_table AS 'JSON Text'
SELECT * FROM OPENJSON (#JSON_table)
SELECT *
FROM OPENJSON (#JSON_table, '$.header.columns')
WITH (title VARCHAR(50),
[property] VARCHAR(50),
minWidth int,
dataType VARCHAR(50),
hideable VARCHAR(10))
SELECT *
FROM OPENJSON (#JSON_table, '$.header.fields')
WITH (property VARCHAR(50),
dataType VARCHAR(50),
display VARCHAR(50))
SELECT *
FROM OPENJSON (#JSON_table, '$.data')
WITH (deviceType VARCHAR(50),
validity VARCHAR(50),
chasisNumber NVARCHAR(50),
serialNumber NVARCHAR(50))
{
"columns": [
{
"title": "Impacted Entities",
"property": "values_short_display",
"minWidth": 250,
"dataType": "jsonArray"
},
{
"title": "Impacted Entities Details",
"property": "values",
"hideable": true,
"visible": false,
"minWidth": 250,
"dataType": "jsonArray"
},
{
"title": "Severity",
"property": "severity",
"display": "iconAndText",
"iconProperty": "severity",
"hideable": false,
"icon": [
{
"key": "Minor",
"value": "images/event_minor.png"
},
{
"key": "Medium",
"value": "images/event_other.png"
}
],
"minWidth": 70,
"dataType": "string"
},
{
"title": "Alarm Name",
"property": "rule_name_display",
"hideable": false,
"minWidth": 160,
"dataType": "string"
},
{
"title": "Cleared Date & Time",
"property": "cleared_time",
"displayFormat": "DD MMM YYYY h:mm:ss A z",
"inputFormat": "unix-time",
"minWidth": 200,
"dataType": "date"
}
],
"fields": [
{
"property": "values_short_display",
"dataType": "jsonArray"
},
{
"property": "uuid",
"dataType": "string"
},
{
"property": "cleared_by",
"dataType": "string"
}
],
I want to make table that contains this data in columns and how can I read an item "entry_time' as the dash causes an error
As to the first part of your question, here is how to extract the values for key and value...
select title, [property], display, iconProperty, hideable, minWidth, dataType, [key], [value]
from openjson(#JSON_table, '$.header.columns') with (
title varchar(50),
[property] varchar(50),
display varchar(50),
iconProperty varchar(50),
hideable bit,
icon nvarchar(max) as json, --<<-- NOTE: *N*varchar(max) as json
minWidth int,
dataType varchar(50)
) j
outer apply openjson(icon) with (
[key] varchar(50),
[value] varchar(100)
) i;
Note that this returns two rows for Severity because its icon contains Minor and Medium keys:
title
property
display
iconProperty
hideable
minWidth
dataType
key
value
Impacted Entities
values_short_display
250
jsonArray
Impacted Entities Details
values
True
250
jsonArray
Severity
severity
iconAndText
severity
False
70
string
Minor
images/event_minor.png
Severity
severity
iconAndText
severity
False
70
string
Medium
images/event_other.png
Alarm Name
rule_name_display
False
160
string
Cleared Date & Time
cleared_time
200
date

How do you use JSON_QUERY with null json array inside of json object?

SELECT JSON_query([json], '$') from mytable
Returns fine the contents of [json] field
SELECT JSON_query([json], '$.Guid') from mytable
Returns null
SELECT JSON_query([json], '$.Guid[1]') from mytable
Returns null
I've also now tried:
SELECT JSON_query([json], '$[1].Guid')
SELECT JSON_query([json], '$[2].Guid')
SELECT JSON_query([json], '$[3].Guid')
SELECT JSON_query([json], '$[4].Guid')
and they all return null
So I'm stuck as to figuring out how create the path to get to the info. Maybe SQL Server json_query can't handle the null as the first array?
Below is the string that is stored inside of the [json] field in the database.
[
null,
{
"Round": 1,
"Guid": "15f4fe9d-403c-4820-8e35-8a8c8d78c33b",
"Team": "2",
"PlayerNumber": "78"
},
{
"Round": 1,
"Guid": "8e91596b-cc33-4ce7-bfc0-ac3d1dc5eb67",
"Team": "2",
"PlayerNumber": "54"
},
{
"Round": 1,
"Guid": "f53cd74b-ed5f-47b3-aab5-2f3790f3cd34",
"Team": "1",
"PlayerNumber": "23"
},
{
"Round": 1,
"Guid": "30297678-f2cf-4b95-a789-a25947a4d4e6",
"Team": "1",
"PlayerNumber": "11"
}
]
You need to follow the comments below your question. I'll just summarize them:
Probably the most appropriate approach in your case is to use OPENJSON() with explicit schema (the WITH clause).
JSON_QUERY() extracts a JSON object or a JSON array from a JSON string and returns NULL. If the path points to a scalar JSON value, the function returns NULL in lax mode and an error in strictmode. The stored JSON doesn't have a $.Guid key, so NULL is the actual result from the SELECT JSON_query([json], '$.Guid') FROM mytable statement.
The following statements provide a working solution to your problem:
Table:
SELECT *
INTO Data
FROM (VALUES
(N'[
null,
{
"Round": 1,
"Guid": "15f4fe9d-403c-4820-8e35-8a8c8d78c33b",
"Team": "2",
"PlayerNumber": "78",
"TheProblem": "doesn''t"
},
{
"Round": 1,
"Guid": "8e91596b-cc33-4ce7-bfc0-ac3d1dc5eb67",
"Team": "2",
"PlayerNumber": "54"
},
{
"Round": 1,
"Guid": "f53cd74b-ed5f-47b3-aab5-2f3790f3cd34",
"Team": "1",
"PlayerNumber": "23"
},
{
"Round": 1,
"Guid": "30297678-f2cf-4b95-a789-a25947a4d4e6",
"Team": "1",
"PlayerNumber": "11"
}
]')
) v (Json)
Statements:
SELECT j.Guid
FROM Data d
OUTER APPLY OPENJSON(d.Json) WITH (
Guid uniqueidentifier '$.Guid',
Round int '$.Round',
Team nvarchar(1) '$.Team',
PlayerNumber nvarchar(2) '$.PlayerNumber'
) j
SELECT JSON_VALUE(j.[value], '$.Guid')
FROM Data d
OUTER APPLY OPENJSON(d.Json) j
Result:
Guid
------------------------------------
15f4fe9d-403c-4820-8e35-8a8c8d78c33b
8e91596b-cc33-4ce7-bfc0-ac3d1dc5eb67
f53cd74b-ed5f-47b3-aab5-2f3790f3cd34
30297678-f2cf-4b95-a789-a25947a4d4e6

Update JSON object MySQL

I want to update a JSON object in MySQL.
TABLE
id (int-11, not_null, auto_inc)
labels (json)
JSON Beautify
[
{
"tagname": "FOO",
"category": "CAT_1",
"isnew": "no",
"isdeleted": "no"
},
{
"tagname": "BAR",
"category": "CAT_2",
"isnew": "yes",
"isdeleted": "no"
}
]
I want to add a new TAG element (JSON OBJECT) next to the existing objects, but without SELECTing first the field and updating all the field with text.
I have Google a lot but I can not understand yet the MySQL's JSON treatment. I have just learned how to insert data like this:
INSERT INTO `table_name`(
`id` ,
`labels`
)
VALUES(
null ,
JSON_ARRAY
(
JSON_OBJECT
(
"tagname", "FOO",
"category", "CAT_1",
"isnew", "no",
"isdeleted", "no"
),
JSON_OBJECT
(
"tagname", "BAR",
"category", "CAT_2",
"isnew", "yes",
"isdeleted", "no"
)
)
);
You could use JSON_ARRAY_APPEND:
UPDATE tab
SET labels = JSON_ARRAY_APPEND(labels, '$',
JSON_OBJECT
(
"tagname", "BARX",
"category", "CAT_3",
"isnew", "yes",
"isdeleted", "no"
)
)
WHERE ID = 1;
DB-Fiddle.com demo

Flatten nested JSON structure in PostgreSQL

I'm trying to write a Postgres query that will output my json data in a particular format.
JSON data structure
{
user_id: 123,
data: {
skills: {
"skill_1": {
"title": "skill_1",
"rating": 4,
"description": 'description text'
},
"skill_2": {
"title": "skill_2",
"rating": 2,
"description": 'description text'
},
"skill_3": {
"title": "skill_3",
"rating": 5,
"description": 'description text'
},
...
}
}
}
This is how I need the data to be formatted in the end:
[
{
user_id: 123,
skill_1: 4,
skill_2: 2,
skill_3: 5,
...
},
{
user_id: 456,
skill_1: 1,
skill_2: 3,
skill_3: 4,
...
}
]
So far I'm working with a query that looks like this:
SELECT
user_id,
data#>>'{skills, "skill_1", rating}' AS "skill_1",
data#>>'{skills, "skill_2", rating}' AS "skill_2",
data#>>'{skills, "skill_3", rating}' AS "skill_3"
FROM some_table
There has to be a better way to go about writing my query. There are 400+ rows and 70+ skills. My above query is a little crazy. Any guidance or help would be greatly appreciated.
Some things to note:
Users rated themselves on 70+ skills
Each skill object has the same structure
Each user rated themselves on the exact same set of skills
db<>fiddle
I expanded your test data to (note the array around all users):
[{
"user_id": 123,
"data": {
"skills": {
"skill_1": {
"title": "skill_1",
"rating": 4,
"description": "description text"
},
"skill_2": {
"title": "skill_2",
"rating": 2,
"description": "description text"
},
"skill_3": {
"title": "skill_3",
"rating": 5,
"description": "description text"
}
}
}
},
{
"user_id": 456,
"data": {
"skills": {
"skill_1": {
"title": "skill_1",
"rating": 1,
"description": "description text"
},
"skill_2": {
"title": "skill_2",
"rating": 3,
"description": "description text"
},
"skill_3": {
"title": "skill_3",
"rating": 4,
"description": "description text"
}
}
}
}]
The query:
SELECT
jsonb_pretty(jsonb_agg(user_id || skills)) -- E
FROM (
SELECT
json_build_object('user_id', user_id)::jsonb as user_id, -- D
json_object_agg(skill_title, skills -> skill_title -> 'rating')::jsonb as skills
FROM (
SELECT
user_id,
json_object_keys(skills) as skill_title, -- C
skills
FROM (
SELECT
(datasets -> 'user_id')::text as user_id,
datasets -> 'data' -> 'skills' as skills -- B
FROM (
SELECT
json_array_elements(json) as datasets -- A
FROM (
SELECT '/* the JSON data; see db<>fiddle */'::json
)s
)s
)s
)s
GROUP BY user_id
ORDER BY user_id
)s
A Make all array elements ({user_id: '42', data: {...}}) one row each
B First column safe the user_id. The cast to text ist necessary for the GROUP BY later which cannot group JSON output. For the second column extract the skills data of the user
C Extract the skill titles for using them as keys in (D.1).
D.1 skills -> skill_title -> 'rating' extracts the rating value from each skill
D.2 json_object_agg aggregates the skill_titles and each corresponding rating value into one JSON object; grouped by the user_id
D.3 json_build_object makes the user_id a JSON object again
E.1 user_id || skills aggregates the two json object into one
E.2 jsonb_agg aggregates these json objects into an array
E.3 jsonb_pretty makes the result looking pretty.
Result:
[{
"skill_1": 4,
"skill_2": 2,
"skill_3": 5,
"user_id": "123"
},
{
"skill_1": 1,
"skill_2": 3,
"skill_3": 4,
"skill_4": 42,
"user_id": "456"
}]