Nested aggregation with `jsonb_agg` - sql

SELECT
s."firstName",
jsonb_agg(
DISTINCT jsonb_build_object(
'yearId',
y.id,
'classes',
(
SELECT
jsonb_agg(
jsonb_build_object(
'classId',
c.id
)
)
FROM
classes AS c
WHERE
y.id = cy."yearId"
AND c.id = cy."classId"
AND s.id = cys."studentId"
)
)
) AS years
FROM
users AS s
LEFT JOIN "classYearStudents" AS cys ON cys."studentId" = s.id
LEFT JOIN "classYears" AS cy ON cy.id = cys."classYearId"
LEFT JOIN "years" AS y ON y.id = cy."yearId"
GROUP BY
s.id
SQL Output
firstName | years
-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Jarrell | [{"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039", "classes": [{"classId": "2590b596-e894-4af5-8ac5-68d109eee995"}]}, {"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039", "classes": [{"classId": "fe4a11f2-5f38-4f7a-bbce-609bc7ad8f99"}]}]
Kevon | [{"yearId": "7f5789b5-999e-45e4-aba4-9f45b29a69ef", "classes": [{"classId": "c8cda7d1-7321-443c-b0ad-6d18451613b5"}]}, {"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039", "classes": [{"classId": "2590b596-e894-4af5-8ac5-68d109eee995"}]}, {"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039", "classes": [{"classId": "fe4a11f2-5f38-4f7a-bbce-609bc7ad8f99"}]}]
Antone | [{"yearId": "7f5789b5-999e-45e4-aba4-9f45b29a69ef", "classes": [{"classId": "c8cda7d1-7321-443c-b0ad-6d18451613b5"}]}, {"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039", "classes": [{"classId": "2590b596-e894-4af5-8ac5-68d109eee995"}]}, {"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039", "classes": [{"classId": "fe4a11f2-5f38-4f7a-bbce-609bc7ad8f99"}]}]
(3 rows)
The problem
What I wanted was for the years with the same ID to be merged together and have multiple classes per year id. As you can see bd5b69ac-6638-4d3e-8a52-94c24ed9a039 on the first row (Jarell) has two entries in the year's column array with each having one class.
Current JSON output
[
{
"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039",
"classes": [{ "classId": "2590b596-e894-4af5-8ac5-68d109eee995" }]
},
{
"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039",
"classes": [{ "classId": "fe4a11f2-5f38-4f7a-bbce-609bc7ad8f99" }]
}
]
Desired output
[
{
"yearId": "bd5b69ac-6638-4d3e-8a52-94c24ed9a039",
"classes": [
{ "classId": "2590b596-e894-4af5-8ac5-68d109eee995" },
{ "classId": "fe4a11f2-5f38-4f7a-bbce-609bc7ad8f99" }
]
}
]
Is this possible?

Hard to say without exact definition of underlying table and and objective of the query.
You need two levels of aggregation in any case. And you can probably largely simplify:
SELECT sub.id, sub."firstName"
, jsonb_agg(jsonb_build_object('yearId', sub."yearId"
, 'classes', sub.classes)) AS years
FROM (
SELECT s.id, s."firstName", cy."yearId"
, jsonb_agg(jsonb_build_object('classId', cy."classId")) AS classes
FROM users s
LEFT JOIN "classYearStudents" cys ON cys."studentId" = s.id
LEFT JOIN "classYears" cy ON cy.id = cys."classYearId"
GROUP BY s.id, cy."yearId"
) sub
GROUP BY sub.id, sub."firstName";
Not sure if and where you need DISTINCT in this query.
I kept the user ID in the result, as first names are hardly unique.
Don't use CaMeL-case identifiers with Postgres if you can avoid it. See:
Are PostgreSQL column names case-sensitive?

Related

is there a way to extract duplicated row value in sql as the key/grouping value?

I have following two tables
users
id | name
1 | john
2 | ada
events
id | content | userId
1 | 'applied' | 1
2 | 'interviewed| 1
What would be the query that returns data in the following shape:
[
{name:'john', events:[{id:1, content:'applied'},{id:2, content:'interviewed'}]}
]
I have tried to run following queries
attempt 1
select events.id, content, users.name
from events
left join users
on users.id=events.userId
where events.userId = ?
but it return duplicated value for the name as following
[
{
"id": 1,
"content": "ronaldo",
"name": "Norman Zboncak"
},
{
"id": 2,
"content": "messi",
"name": "Norman Zboncak"
},
{
"id": 3,
"content": "messi",
"name": "Norman Zboncak"
}
]
attempt 2
I tried to use group_concat but apparently you cannot pas multiple arguments into it so couldn't get the result in the desired shape
You must do a LEFT join of users to events and aggregate with SQLite's JSON Functions:
SELECT json_object(
'name', u.name,
'events', json_group_array(json_object('id', e.id, 'content', e.content))
) result
FROM users u LEFT JOIN events e
ON e.userId = u.id
WHERE u.id = 1 -- remove this line to get results for all users
GROUP BY u.id;
See the demo.

PostgreSQL - How to return array of objects with nested array using joins

I know basic PostgreSQL usage, but I'm looking to return data formatted in a specific way. The current query below lists all team names with their division name. I basically need to list all teams UNDER their respective division. I believe I need some sort of SELECT DISTINCT call but I am absolutely lost on what to correct terms to search for. Can anyone point me in the right direction? Is it best to do it in the Postgres query or should it be handled on the server (after the current response is returned)?
Minimal examples below, Teams and Divisions tables are basically set up as id and name
Pivot table
id | team_id | season_id | division_id
--------------------------------------
1 | 3 | 1 | 3
2 | 4 | 1 | 3
3 | 5 | 1 | 3
4 | 6 | 1 | 3
Query to list all teams and their division name
SELECT t.name AS team_name, d.name AS division_name FROM team_season_division tsd
JOIN teams t ON t.id = tsd.team_id
JOIN divisions d ON d.id = tsd.division_id
WHERE tsd.season_id = 1;
Current response
[
{
team_name: 'synthesize onlines',
division_name: 'A1',
},
{
team_name: 'array arrays',
division_name: 'A1',
},
{
team_name: 'quantify matrixs',
division_name: 'B1',
}
]
Example response desired
[
{
division_name: 'A1',
teams_in_division: [
{ name: 'synthesize onlines' },
{ name: 'array arrays' },
{ name: 'mobile microchips' }
]
},
{
division_name: 'B1',
teams_in_divisions: [
{ name: 'quantify matrixs' },
{ name: 'matrix matrixs' },
{ name: 'hack hacks' },
{ name: 'bluetooth generates' },
{ name: 'override protocols' }
]
}
]
You can use aggregation. I would suggest putting the team list in an array of json(b) objects, using jsonb_agg() and jsonb_build_object():
select
d.name as division_name
jsonb_agg(jsonb_build_object('name', t.name)) as teams_in_division
from team_season_division tsd
inner join teams t on t.id = tsd.team_id
inner join divisions d on d.id = tsd.division_id
where tsd.season_id = 1
group by d.division_id, d.name;
If you want the results as a single json array, you can add another level of aggregation:
select jsonb_agg(
jsonb_build_object(
'division_name', division_name,
'teams_in_division', teams_in_division
)
) res
from (
select
d.name as division_name
jsonb_agg(jsonb_build_object('name', t.name)) as teams_in_division
from team_season_division tsd
inner join teams t on t.id = tsd.team_id
inner join divisions d on d.id = tsd.division_id
where tsd.season_id = 1
group by d.division_id, d.name
) t;

Extract complex nested JSON array in Presto

I have a complex JSON object like this:
{
"item_detail": [
{
"itemid": "4702385896",
"modelid": "8307307322",
"quantity": "1"
},
{
"itemid": "3902478595",
"modelid": "8306561848",
"quantity": "1"
},
{
"itemid": "3409528897",
"modelid": "10922686275",
"quantity": "1"
},
{
"itemid": "4702385896",
"modelid": "8307307323",
"quantity": "1"
}
],
"shopid": "128449080"
},
{
"item_detail": [
{
"itemid": "7906381345",
"modelid": "9745718882",
"quantity": "1"
},
{
"itemid": "6710792892",
"modelid": "11474621623",
"quantity": "1"
}
],
"shopid": "36121097"
}
]
I am struggling in extracting all (itemid, shopid) into rows in Presto. My wanted outcomes are:
itemid | shopid
-----------+-------
4702385896 | 128449080
3902478595 | 128449080
3409528897 | 128449080
4702385896 | 128449080
7906381345 | 36121097
6710792892 | 36121097
I have used CROSS JOIN UNNEST and TRANSFORM to get the result with no luck. Does anyone have a solution for this?
So many thanks in advance!
Use json_extract with cast to array and unnest, like this:
presto:default> SELECT
-> json_extract_scalar(item_detail, '$.itemid') itemid,
-> json_extract_scalar(shopping, '$.shopid') shopid
-> FROM t
-> CROSS JOIN UNNEST(CAST(my_json AS array(json))) AS x(shopping)
-> CROSS JOIN UNNEST(CAST(json_extract(shopping, '$.item_detail') AS array(json))) AS y(item_detail)
-> ;
->
itemid | shopid
------------+-----------
4702385896 | 128449080
3902478595 | 128449080
3409528897 | 128449080
4702385896 | 128449080
7906381345 | 36121097
6710792892 | 36121097
(6 rows)
(verified on Presto 327)
BTW if any of the arrays may be empty or missing, I recommend using LEFT JOIN UNNEST ... ON true instead of CROSS JOIN UNNEST (this requires a decent Presto version):
SELECT
json_extract_scalar(item_detail, '$.itemid') itemid,
json_extract_scalar(shopping, '$.shopid') shopid
FROM t
LEFT JOIN UNNEST(CAST(my_json AS array(json))) AS x(shopping) ON true
LEFT JOIN UNNEST(CAST(json_extract(shopping, '$.item_detail') AS array(json))) AS y(item_detail) ON true;

SQL JSON Help and Query Help needed

Good day Stackoverflow,
I have found myself in a bit of a weird query situation. You see, I have to come up with a stored procedure that returns a data set like so;
[
"TestCase1": {
"SetText": {
"Element":"Username"
"Value":"123456"
},
"SetText": {
"Element":"Username",
"Value":"Admin"
},
"OnClick": {
"Element":"SubmitButton"
},
"Login": {
"Username":"admin",
"Password":"123456"
}
}
]
As you can see the data set is held together by the TestCase1 which is in reference to a Table Column Name. Test.Name
Follow by, the function name holding multiple objects.
Each function has its Keys be the parameter name and the keys values are the parameters values
So in this example, Test Case 1 has 4 actions
SetText("Username", "123456")
SetText("Password", "Admin")
OnClick("SubmitButton")
Login("Admin","123456")
The problem is; the query list below I can't alter it to the way I have the JSON above
SELECT TD.ID, F.[Name] AS [Function], P.[Name] AS [Parameter], E.[Name] AS [Data] FROM [QA].[TestData] TD
LEFT JOIN QA.[Parameter] P ON P.ID = TD.ParameterID
INNER JOIN QA.[Function] F ON F.ID = P.FunctionID
INNER JOIN QA.[XREF_Parameter_Element] XPE ON (P.ParameterTypeID = 1) AND (XPE.ID = TD.DataID)
INNER JOIN QA.[Element] E ON E.ID = XPE.ElementID
UNION ALL
SELECT TD.ID, F.[Name] AS [Function], P.[Name] AS [Parameter], XPV.[Value] AS [Data] FROM [QA].[TestData] TD
LEFT JOIN QA.[Parameter] P ON P.ID = TD.ParameterID
INNER JOIN QA.[Function] F ON F.ID = P.FunctionID
INNER JOIN QA.[XREF_Parameter_Value] XPV ON (P.ParameterTypeID <> 1) AND (XPV.ID = TD.DataID)
ORDER BY TD.ID
FOR JSON PATH, ROOT('TestCase1')
Here is what the query above turns out to be
{
"TestCase1":[
{
"ID":1,
"Function":"SetText",
"Parameter":"Element",
"Data":"Username"
},{
"ID":2,
"Function":"SetText",
"Parameter":"Value",
"Data":"123456"
},{
"ID":3,
"Function":"SetText",
"Parameter":"Element",
"Data":"Username"
},{
"ID":4,
"Function":"SetText",
"Parameter":"Value",
"Data":"Admin"
},{
"ID":5,
"Function":"OnClick",
"Parameter":"Element",
"Data":"SubmitButton"
},{
"ID":6,
"Function":"Login",
"Parameter":"Username",
"Data":"Admin"
},{
"ID":7,
"Function":"Login",
"Parameter":"Password",
"Data":"123456"
}
]
}
Is anyone able to help me out. I am more than willing to share contact information to be more clear. This is just ripping my brain apart.

Format nested JSON object from Postgres query

I want to get a JSON object formatted similar to this:
{
"username": "USERNAME",
"teamname": "TEAMNAME",
"logs": [
{
"log": {
"log_id": 29,
"person_id": 3,
"activity_id": 3,
"shoe_id": null,
"logdate": "2016-11-29",
"distance": null,
"activitytime": null,
"sleep": null,
"heartrate": null,
"logtitle": null,
"description": null
},
"activity": "Swim",
"comments": {
"comment_id": 1,
"description": "This is a comment",
"person_id": 1,
"log_id": 29
}
}]
}
Currently I have everything formatted correctly except the comments. Here is the SQL query I am using:
SELECT p.username, t.teamname, json_agg(json_build_object('log', l.*, 'comments', c.*, 'activity', a.activity)) as logs
FROM person_tbl p
INNER JOIN log_tbl l ON p.person_id = l.person_id
INNER JOIN activity_tbl a ON l.activity_id = a.activity_id
INNER JOIN comment_tbl c ON c.log_id = l.log_id
INNER JOIN person_team_tbl pt ON p.person_id = pt.person_id
INNER JOIN team_tbl t on t.team_id = pt.team_id
WHERE t.team_id = 5
AND l.logdate > NOW()::date - 7
GROUP BY p.username, t.teamname
ORDER BY p.username
I'm having trouble getting the comments of each log. Right now, it is returning every comment and repeating the logs (they are not associated).
Also, how could I get this query to return the username and teamname when everything else is null (like when there are no logs in the past week)?
Without an SQLfiddle we do not know what your data (and structure) is so it is difficult to answer your question.
For the NULL case - please modify the WHERE clause like this (deliberately not using COALESCE)
WHERE t.team_id = 5 AND (l.logdate IS NULL OR l.logdate > NOW()::date - INTERVAL '7 day')