Need help in json grouping query - mysql

My current code:
create table Messages (
id int,
userId int,
message varchar(255)
);
insert into Messages
values
(1, 1, 'Message u1_1'),
(2, 1, 'Message u1_2'),
(3, 2, 'Message u2_1'),
(4, 2, 'Message u2_2'),
(5, 3, 'Message u3_1')
;
select
json_object('Messaging info',
json_object(
'id', userId,
'user', json_object(
'messages', json_arrayagg(
json_object(
'message', message,
'msgId', id
)
)
)
)
)
from Messages
where userId in (1, 2)
group by userId
This query returns 2 records that I cannot group into one json object.
In other words: i want to merge this
{"Messaging info": {"id": 1, "user": {"messages": [{"msgId": 1, "message": "Message u1_1"},{"msgId": 2, "message": "Message u1_2"}]}}}
and this
{"Messaging info": {"id": 2, "user": {"messages": [{"msgId": 3, "message": "Message u2_1"}, {"msgId": 4, "message": "Message u2_2"}]}}}
into
{
"Messaging info": {
"id": 1,
"user": {
"messages": [
{
"msgId": 1,
"message": "Message u1_1"
},
{
"msgId": 2,
"message": "Message u1_2"
}
]
},
"id": 2,
"user": {
"messages": [
{
"msgId": 3,
"message": "Message u2_1"
},
{
"msgId": 4,
"message": "Message u2_2"
}
]
}
}
}
Tried to play with json_arrayagg at topmost of query, but got a grouping error.
https://www.db-fiddle.com/f/rjmkqmM5AvvPDAXbzrTBNY/1
Thanks in advance.

You need to do json_arrayagg in the outer query
SELECT json_object('Messaging info',json_arrayagg(ad))
FROM(
select
json_object(
'id', userId,
'user', json_object(
'messages', json_arrayagg(
json_object(
'message', message,
'msgId', id
)
)
)
) as ad
from Messages
https://www.db-fiddle.com/f/rjmkqmM5AvvPDAXbzrTBNY/2

Related

How to return result of a join into a single property in a Postgres query?

Suppose the following,
CREATE SCHEMA IF NOT EXISTS my_schema;
CREATE TABLE IF NOT EXISTS my_schema.my_table_a (
id serial PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS my_schema.my_table_b (
id serial PRIMARY KEY,
my_table_a_id BIGINT REFERENCES my_schema.my_table_a (id) NOT NULL
);
INSERT INTO my_schema.my_table_a VALUES
(1);
INSERT INTO my_schema.my_table_b VALUES
(1, 1),
(2, 1),
(3, 1);
If I run the following query,
SELECT
ta.*,
tb as tb
FROM my_schema.my_table_a ta
LEFT JOIN my_schema.my_table_b tb
ON ta.id = tb.my_table_a_id;
Then the result is,
[
{
"id": 1,
"tb": {
"id": 1,
"my_table_a_id": 1
}
},
{
"id": 1,
"tb": {
"id": 2,
"my_table_a_id": 1
}
},
{
"id": 1,
"tb": {
"id": 3,
"my_table_a_id": 1
}
}
]
How can I get it to work like this:
[
{
"id": 1,
"tb": [
{
"id": 1,
"my_table_a_id": 1
},
{
"id": 2,
"my_table_a_id": 1
},
{
"id": 3,
"my_table_a_id": 1
}
]
}
]
SELECT
ta.*,
ARRAY_AGG(tb) AS tb
FROM my_schema.my_table_a ta, my_schema.my_table_b tb
GROUP BY ta.id
ORDER BY ta.id;
Example https://www.db-fiddle.com/f/5i97YZ6FMRY48pZaJ255EJ/0

How to generate nested nth level JSON object in T-SQL?

I have below data
against below query
declare #t table
(
Id int identity,
name varchar(50),
rootid int,
level int
);
insert into #t(name, rootid, level)
values
('Home', 0, 0)
,('Transaction', 0, 0)
, ('Settings', 0, 0)
,('Purchase Request', 2, 1)
,('Purchase Order', 2, 1)
,('Inventory', 2, 1)
,('Payment Advice', 2, 1)
,('Setup', 3, 1)
,('Budget', 3, 1)
,('CRC', 3, 1)
,('Create PR', 4, 3);
select * from #t;
Desire output:
[{
"Id": 1,
"name": "Home",
"rootid": 0,
"level": 0
}, {
"Id": 2,
"name": "Transaction",
"rootid": 0,
"level": 0,
"children": [{
"Id": 4,
"name": "Purchase Request",
"rootid": 2,
"level": 1,
"children": [{
"Id": 11,
"name": "Create PR",
"rootid": 4,
"level": 3
}]
}, {
"Id": 5,
"name": "Purchase Order",
"rootid": 2,
"level": 1
}, {
"Id": 6,
"name": "Inventory",
"rootid": 2,
"level": 1
}, {
"Id": 7,
"name": "Payment Advice",
"rootid": 2,
"level": 1
}]
}, {
"Id": 3,
"name": "Settings",
"rootid": 0,
"level": 0,
"children": [{
"Id": 8,
"name": "Setup",
"rootid": 3,
"level": 1
}, {
"Id": 9,
"name": "Budget",
"rootid": 3,
"level": 1
}, {
"Id": 10,
"name": "CRC",
"rootid": 3,
"level": 1
}]
}]
Also Tried #Iptr answer:
;WITH result (id, name, rootId, parent, Level) AS
(
SELECT id,
name,
RootId,
Id as Parent,
0 as Level
FROM #t
WHERE RootId= 0
UNION ALL
SELECT t.id,
t.Name,
t.RootId,
r.Parent,
r.Level + 1
FROM #t t
INNER JOIN result r ON r.id = t.RootId
)
SELECT t.*, json_query(nullif(c.children, '[{}]')) as children
FROM #t as t
outer apply (
select
(
select r.*
from result as r
where r.parent = t.Id
and r.level > 0
order by r.id
for json auto
) as children
) as c
where t.level = 0
order by t.Level
for json auto;
Output
[{
"Id": 1,
"name": "Home",
"rootid": 0,
"level": 0
}, {
"Id": 2,
"name": "Transaction",
"rootid": 0,
"level": 0,
"children": [{
"id": 4,
"name": "Purchase Request",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 5,
"name": "Purchase Order",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 6,
"name": "Inventory",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 7,
"name": "Payment Advice",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 11,
"name": "Create PR",
"rootId": 4,
"parent": 2,
"Level": 2
}]
}, {
"Id": 3,
"name": "Settings",
"rootid": 0,
"level": 0,
"children": [{
"id": 8,
"name": "Setup",
"rootId": 3,
"parent": 3,
"Level": 1
}, {
"id": 9,
"name": "Budget",
"rootId": 3,
"parent": 3,
"Level": 1
}, {
"id": 10,
"name": "CRC",
"rootId": 3,
"parent": 3,
"Level": 1
}]
}]
Above query is not returning nth json child objects, let say if I have nth level of menu items, Parent have multiple Childs and Childs have multiple Childs like treeview.
Tried #Naveen Arora answer:
select ID,name,'' as id,'' as name from Navigations where id not in (select rootid from Navigations) and rootid=0
union
select B.id,B.name,A.id,A.name from Navigations A join Navigations B on A.rootid=B.id
FOR JSON AUTO;
But output
[{
"ID": 1,
"name": "Home",
"id": 0,
"name": ""
}, {
"ID": 2,
"name": "Transaction",
"id": 4,
"name": "Create PR"
}, {
"ID": 2,
"name": "Transaction",
"id": 5,
"name": "Generate PO"
}, {
"ID": 2,
"name": "Transaction",
"id": 6,
"name": "Create Receipt"
}, {
"ID": 2,
"name": "Transaction",
"id": 7,
"name": "Create Issue Request"
}, {
"ID": 2,
"name": "Transaction",
"id": 8,
"name": "Create Issue Note"
}, {
"ID": 2,
"name": "Transaction",
"id": 9,
"name": "Approve Payment Advice"
}, {
"ID": 3,
"name": "Settings",
"id": 11,
"name": "Navigation Management"
}, {
"ID": 11,
"name": "Navigation Management",
"id": 12,
"name": "Navigation & Form Mapping"
}]
Above output it's not include Childs node. Like in Settings I have Navigation Management -> Navigation & Form Mapping
If the sql server version is 2016 or newer than 2016 then you can use FOR JSON PATH.
Assuming that results are stored in test table. This is just to give you an idea how you can do this, may not give you the exact output but you can change it as per your requirement.
SELECT
t.Id AS 'Id',
t.Name AS 'Name',
children = (
SELECT A.id,A.name from test A join test B on A.rootid=B.id
FOR JSON PATH
)
FROM Test t
FOR JSON PATH;
And if it is older than 2016 then you may refer this.
declare #t table
(
Id int identity,
name varchar(50),
rootid int,
level int
);
insert into #t(name, rootid, level)
values
('Home', 0, 0),('Transaction', 0, 0), ('Settings', 0, 0),
('Create PR', 2, 1), ('Generate PO', 2, 1), ('Create Receipt', 2, 1), ('Create Issue Request', 2, 1), ('Create Issue Note', 2, 1), ('Approve Payment Advice', 2, 1),
('Navigation Management', 3, 1), ('Navigation & Form Mapping', 3, 1);
select * from #t;
;WITH result (id, name, rootId, parent, Level) AS
(
SELECT id,
name,
RootId,
Id as Parent,
0 as Level
FROM #t
WHERE RootId= 0
UNION ALL
SELECT t.id,
t.Name,
t.RootId,
r.Parent,
r.Level + 1
FROM #t t
INNER JOIN result r ON r.id = t.RootId
)
SELECT t.*, json_query(nullif(c.children, '[{}]')) as children
FROM #t as t
outer apply (
select
(
select r.*
from result as r
where r.parent = t.Id
and r.level > 0
order by r.id
for json auto
) as children
) as c
where t.level = 0
order by t.Level
for json auto;
SELECT t.*, json_query(nullif(c.children, '[{}]')) as children
FROM #t as t
outer apply (
select
(
select r.*
from #t as r
where r.rootid = t.Id
and r.level > 0
order by r.id
for json auto
) as children
) as c
where t.level = 0
order by t.Level
for json auto;
My apology for late posting my answer. But I really appreciate the efforts of #Iptr and #NaveenArora answer on my post. After I do some brain storming on my case I've finally found the way to do it.
Create this function:
create function [dbo].[fnUDFCreateJSON](#currentId int)
returns varchar(max)
begin
declare #json nvarchar(max)
IF #currentId <> 0
BEGIN
set #json =
(
select [ID], [Name], CSSClass, RouteURL, json_query(dbo.fnUDFCreateJSON([ID])) as SubNavigation
from dbo.Navigations
where RootId = #currentId
for json auto
);
END
ELSE
BEGIN
set #json =
(
select [ID], [Name], CSSClass, RouteURL, '' as SubNavigation from dbo.Navigations where RootId = 0
for json auto
);
END
return #json
end
and call it by using stored procedure:
CREATE PROCEDURE [dbo].[spGetStartupNavigations]
AS
BEGIN
SELECT
(SELECT
ID, Name, CSSClass, RouteURL,
JSON_QUERY (dbo.fnUDFCreateJSON(ID)) AS SubNavigation
FROM
dbo.Navigations
WHERE
RootId = 0
FOR JSON AUTO) AS Navigation
END
That's it.

json_agg() with pseudo row

CREATE TABLE content
(
code INTEGER NOT NULL
, nameid TEXT NOT NULL
)
INSERT INTO content(code, nameid) VALUES
( 0, 'Support')
, ( 1, 'Adrenaline')
, ( 2, 'Aquapark')
;
Need select all content as JSON array-object named 'content' with pseudo row with code = '' and nameid = 'All'
What i did:
1) Simple select
SELECT
json_build_object('content', (SELECT json_agg(json_build_object(
'code', s.code::TEXT
, 'nameid', s.nameid::TEXT
))
FROM content s
)
);
Result is
{
"content": [{
"code": "0",
"nameid": "Support"
},
{
"code": "1",
"nameid": "Adrenaline"
},
{
"code": "2",
"nameid": "Aquapark"
}
]
}
All fine, but how to add pseudo row?
2) Best i can did
SELECT
json_build_object('content', json_build_array(
json_build_object(
'code', ''
, 'nameid', 'All'::TEXT),
(SELECT json_agg(json_build_object(
'code', s.code::TEXT
, 'nameid', s.nameid::TEXT
))
FROM content s
))
);
Result is
{
"content": [{
"code": "",
"nameid": "All"
},
[{
"code": "0",
"nameid": "Support"
},
{
"code": "1",
"nameid": "Adrenaline"
},
{
"code": "2",
"nameid": "Aquapark"
}
]
]
}
And we have array in array, as in sql as in json, but i can not understand how to combine json_agg with pseudo row.
Use to_json() and union all:
select jsonb_build_object('content', json_agg(to_json))
from (
select to_json(c)
from (select '' as code, 'All' as nameid) c
union all
select to_json(c)
from content c
) s
Output:
{
"content": [
{
"code": "",
"nameid": "All"
},
{
"code": 0,
"nameid": "Support"
},
{
"code": 1,
"nameid": "Adrenaline"
},
{
"code": 2,
"nameid": "Aquapark"
}
]
}
The version with an alias in the derived table:
select jsonb_build_object('content', json_agg(codes))
from (
select to_json(c) as codes
from (select '' as code, 'All' as nameid) c
union all
select to_json(c)
from content c
) s

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"
}]

For JSON results

Sorry for the basic of this question, I just cannot wrap my head around this one.
I need the output from SQL Server to look like this.
In a little more human readable format:
var data = [
{
name: '2017', id: -1,
children: [
{ name: '01-2017', id: 11 },
{ name: '02-2017', id: 12 },
{ name: '03-2017', id: 13 },
{ name: '04-2017', id: 14 },
{ name: '05-2017', id: 15 },
]
},
{
name: '2018', id: -1,
children: [
{ name: '01-2018', id: 6 },
{ name: '02-2018', id: 7 },
{ name: '03-2018', id: 8 },
{ name: '04-2018', id: 9 },
{ name: '05-2018', id: 10 },
]
}
];
This is a snapshot of the data:
The group I will be working with is userid = 1.
My first thought was to use a cursor to loop through all the distinct reportYear for userid = 1, then a select based on the year and the userid to fill in the sub-query.
There has to be a way without using a cursor.
You can achieve the desired output joining your table to a query that extracts all the years to be used at the top level elements and then generating the json using FOR JSON AUTO:
declare #tmp table (monthlyReportID int, userID int, reportMonth int, reportYear int)
insert into #tmp values
( 6, 1, 1, 2018),
( 7, 1, 2, 2018),
( 8, 1, 3, 2018),
( 9, 1, 4, 2018),
(10, 1, 5, 2018),
(11, 1, 1, 2017),
(12, 1, 2, 2017),
(13, 1, 3, 2017),
(14, 1, 4, 2017),
(15, 1, 5, 2017)
select years.[name], children.[name], children.[id] from
(
select distinct reportYear as [name] from #tmp
) as years
left join
(
select monthlyReportID as [id]
,right('0' + cast(reportMonth as varchar(2)),2) + '-' + cast(reportYear as varchar(4)) as [name]
,reportYear as [year]
from #tmp
) as children
on children.[Year] = years.[name]
for json auto
I omitted the ID field because in your desired output it is always set to -1 and I was not able to understand the logic behind it.
Nonetheless you should be able to easily edit the script above to obtain the value you need.
Here are the results:
[
{
"name": 2017,
"children": [
{"name": "01-2017", "id": 11},
{"name": "02-2017", "id": 12},
{"name": "03-2017", "id": 13},
{"name": "04-2017", "id": 14},
{"name": "05-2017", "id": 15}
]
},
{
"name": 2018,
"children": [
{"name": "01-2018", "id": 6},
{"name": "02-2018", "id": 7},
{"name": "03-2018", "id": 8},
{"name": "04-2018", "id": 9},
{"name": "05-2018", "id": 10}
]
}
]