How to get nested relationship entities with nested subqueries SQL? - sql

I have these 4 tables: Scorecard, Section, Topic, Answer.
Each scorecard can have many sections. Each Section can have many topics and each Topic can have many answers.
I am looking to retrieve the data in the following form. Get all sections where scorecard id is equal to to a certain id i do this by left joining and it works so far. Then for each section I want to get of all it's topics and answers as well.
=> Expected Output
[{section1,
topics:[{topicName, answers:[{answerName}]}]},
{section2,
topics:[{topicName, answers:[{answerName}]}]}]
I made adjustments to my query and joint the tables correctly but the output data is not correctly organized as I was It to be.
Query:
SELECT sec."id",
sec."name" AS "sectionName",
to_json(topics) as "topics"
FROM "scorecard" sc
LEFT JOIN "section" sec ON sc."id" = sec."scorecardId"
LEFT JOIN (SELECT tp.*, to_json(answers.*) as "answers"
FROM "topic" tp
LEFT JOIN (SELECT ans."name" as "answerName", ans."topicId"
FROM "answer" ans) answers ON answers."topicId" = tp."id"
) topics
ON topics."sectionId" = sec."id"
WHERE sc."id" = $1
The current output:
[ id: 85,
sectionName: 'Consultation',
topics: {
id: 109,
name: 'Was the information correct?',
answers: [{ answerName: 'Yes', topicId: 109 }, answers: { answerName: 'NO', topicId: 109 }
]
},
id: 85,
sectionName: 'Consultation',
topics: {
id: 109,
name: 'Was the information correct?',
answers: { answerName: 'NO', topicId: 109 }
}]
My expected output:
[ {id: 85,
sectionName: 'Consultation',
topics: [{
id: 109,
name: 'Was the information correct?',
answers: [{ answerName: 'Yes', topicId: 109 },
{ answerName: 'NO', topicId: 109 }]
}]
} ]
I have also tried JOINING all entites as such:
SELECT sec."id",
sec."name" AS "sectionName",
tp."name" AS topics, ans."name" AS answers
FROM "scorecard" sc
LEFT JOIN "section" sec ON sc."id" = sec."scorecardId"
LEFT JOIN "topic" tp ON tp."sectionId" = sec."id"
LEFT JOIN "answer" ans ON ans."topicId"= tp."id"
WHERE sc."id" = $1
Output is not organized as it should be.
[
{
id: 91,
sectionName: 'Politeness',
topics: 'Was the agent polite with the client?',
answers: 'No'
},
{
id: 91,
sectionName: 'Politeness',
topics: 'Was the agent polite with the client?',
answers: 'Not everytime'
},
{
id: 91,
sectionName: 'Politeness',
topics: 'Was the agent polite with the client?',
answers: 'Yes'
}
]

You can join all four tables to get your result.
SELECT sec."id",
sec."name" AS "sectionName",
tp."name" AS topics, ans."name" AS answers
FROM "scorecard" sc
LEFT JOIN "section" sec ON sc."id" = sec."scorecardId"
LEFT JOIN "topic" tp on tp."sectionId" = sec."id"
LEFT JOIN "answer" ans on ans."topicId"=tp."id"
WHERE sc."id" = 30
Thanks #Luuk for creating the dbfiddle link.
select json_agg(x) AS result
from
(
SELECT sec."id",
sec."name" AS "sectionName",
json_agg(topics) as "topics"
FROM "scorecard" sc
LEFT JOIN "section" sec ON sc."id" = sec."scorecardId"
LEFT JOIN (SELECT tp.id,tp.name,tp."sectionId", json_agg(answers.*) as "answers"
FROM "topic" tp
LEFT JOIN (SELECT ans."name" as "answerName", ans."topicId"
FROM "answer" ans) answers ON answers."topicId" = tp."id"
group by tp.id,tp.name,tp."sectionId"
) topics
ON topics."sectionId" = sec."id"
WHERE sc."id" = 85
group by sec."id", sec."name"
)x
Output:
result
[{"id":85,"sectionName":"Consulation","topics":[{"id":109,"name":"Was the information correct?","sectionId":85,"answers":[{"answerName":"Yes","topicId":109}, <br> {"answerName":"NO","topicId":109}]}]}]
*db<>fiddle here149)

You are missing a JOIN condition to the table topics
SELECT sec."id",
sec."name" AS "sectionName",
topics
FROM "scorecard" sc
LEFT JOIN "section" sec ON sc."id" = sec."scorecardId"
LEFT JOIN (SELECT tp.*
FROM "topic" tp
WHERE tp."sectionId" = sec."id"
(SELECT tp.*
FROM "topic" tp
WHERE tp."sectionId" = sec."id"
)
) as topics ON topics.??? = ??? -- Here you should enter the relation
-- from topics to your section.
WHERE sc."id" = 30
EDIT:
With you (current) input I creates this DBFIDDLE
The output:
[
{
"id": 85,
"sectionName": "Consulation",
"topics": {
"id": 109,
"name": "Was the information correct?",
"sectionId": 85,
"answers": {
"answerName": "Yes",
"topicId": 109
}
}
},
{
"id": 85,
"sectionName": "Consulation",
"topics": {
"id": 109,
"name": "Was the information correct?",
"sectionId": 85,
"answers": {
"answerName": "NO",
"topicId": 109
}
}
}
]

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.

Put variable inside json_extract_path_text in postgresql query

I have following select:
select json_extract_path_text(rules, 'amount', '5', 'percentage')
from promotion_rules
Sample from JSON looks like this:
{
"amount": {
"1": {
"percentage": 1
},
"2": {
"percentage": 3
},
"3": {
"percentage_below_eq": 5,
"percentage_above": 10,
"price": 20
},
"4": {
"percentage_below_eq": 10,
"percentage_above": 15,
"price": 20
}
}
}
I want to use values from other queries/tables/cte inside above json_extract function instead of '5' (or achieve exact effect), how it can be done?
Here's the part of code and fiddle with full data, I can't put it all here because stack tells me that my post i mostly code.
with percentages as (select pr.*, json_object_keys(rules->'amount')::INT as amount
from
promotion_rules pr
where id = 1
)
select
o.id as order_id,
json_extract_path_text(rules, 'amount', o.products_no, 'percentage') as percentage --it doesn't work this way, either with brackets
from orders o
join percentages p on p.amount = o.products_no
https://www.db-fiddle.com/f/oSQ3eW2G3kHgr3xvpHLw9Q/0
json_extract_path expects a list of text parameters.
If you want to use a column that's not text you need to cast it:
json_extract_path_text(rules, 'amount', o.products_no::text, 'percentage')

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.

SQL Inner join with For JSON hierarchy

I'm trying to join two SQL tables with inner join and then return them as JSON from my procedure.
My select statement is:
SELECT
#CustomerAddressesJSON =
(SELECT
Address.AddressID, Address.CustomerID,
Address.AddressTypeID, Address.IsPrimary,
CountryID, StateID, CountyID, DistrictID,
StreetID, StreetNumber, PostalCode,
AdditionalInformation, AddressImageID,
CreatedOn, CreatedBy
FROM
[sCustomerManagement].[tCustomerAddresses] Address
INNER JOIN
[sCustomerManagement].[tAddresses] AddressDetails ON Address.AddressID = AddressDetails.AddressID
WHERE
CustomerID = #CustomerID
FOR JSON AUTO)
and the result is like this:
"customerAddressesJSON": "[ {
"AddressID": 1,
"CustomerID": 1,
"AddressTypeID": "T",
"IsPrimary": true,
"AddressDetails": [
{
"CountryID": 1,
"StateID": 1,
"CountyID": 1,
"DistrictID": 1,
"StreetID": 1,
"StreetNumber": "125",
"PostalCode": "1000",
"AdditionalInformation": "Metro Sofia",
"CreatedOn": "2017-10-24T11:46:20.1933333",
"CreatedBy": 24
}
]
}, {
"AddressID": 2,
"CustomerID": 1,
"AddressTypeID": "T",
"IsPrimary": true,
"AddressDetails": [
{
"CountryID": 1,
"StateID": 1,
"CountyID": 1,
"DistrictID": 1,
"StreetID": 1,
"StreetNumber": "125",
"PostalCode": "1000",
"AdditionalInformation": "Metro Sofia",
"CreatedOn": "2017-10-24T11:46:20.1933333",
"CreatedBy": 24
}
]
}
The problem is that I don't want the information in the array AddressDetails to be nested. Is it possible the information there to be outside, so I can receive 2 flat objects, without nested information ?
Thanks
Consider using the PATH mode with dot syntax and map all fields to Address as discussed in docs.
SELECT
#CustomerAddressesJSON =
(SELECT
a.AddressID AS 'Address.AddressID', a.CustomerID AS 'Address.CustomerID',
a.AddressTypeID AS 'Address.AddressTypeID', a,IsPrimary AS 'Address.IsPrimary',
d.CountryID AS 'Address.CountryID', d.StateID AS 'Address.StateID',
d.CountyID AS 'Address.CountyID', d.DistrictID AS 'Address.DistrictID',
d.StreetID As 'Address.StreetID', d.StreetNumber AS 'Address.StreetNumber',
d.PostalCode AS 'Address.PostalCode',
d.AdditionalInformation AS 'Address.AdditionalInformation',
d.AddressImageID AS 'Address.AddressImageID',
d.CreatedOn AS 'Address.CreatedOn', d.CreatedBy AS 'Address.CreatedBy'
FROM
[sCustomerManagement].[tCustomerAddresses] a
INNER JOIN
[sCustomerManagement].[tAddresses] d ON a.AddressID = d.AddressID
WHERE
a.CustomerID = #CustomerID
FOR JSON PATH)
Alternatively, use a derived table:
SELECT
#CustomerAddressesJSON =
(SELECT m.*
FROM
(SELECT a.AddressID, a.CustomerID, a.AddressTypeID, a,IsPrimary,
d.CountryID, d.StateID, d.CountyID, d.DistrictID,
d.StreetID, d.StreetNumber, d.PostalCode,
d.AdditionalInformation, d.AddressImageID,
d.CreatedOn, d.CreatedBy
FROM
[sCustomerManagement].[tCustomerAddresses] a
INNER JOIN
[sCustomerManagement].[tAddresses] d ON a.AddressID = d.AddressID
WHERE
a.CustomerID = #CustomerID
) AS m
FOR JSON AUTO)

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')