SQL JOIN values to ARRAY to create another ARRAY field - sql

maybe my searching is poor, but I couldn't find this question or answer anywhere.
Suppose I have a table CLASSROOM like:
[ { teacher_id: T1,
students: [S11, S12, S13]},
{ teacher_id: T2,
students: [S21, S22, S23]}]
The "students" field is an array of student_id's. There is also a table STUDENTS like:
[ { id: S11, name: "Aaron"}, { id: S12, name: "Bob"}, { id: S13, name: "Charlie"},
{ id: S21, name: "Amy"}, { id: S22, name: "Becky"}, { id: S23, name: "Cat"} ]
I want to create the output table which has rows like:
[ { teacher_id: T1,
students: [S11, S12, S13 ],
names: [ "Aaron", "Bob", "Charlie" ] },
{ teacher_id: T2,
students: [S21, S22, S23 ],
names: [ "Amy", "Becky", "Cat" ] } ]
(Yes, this example is silly, but I don't want to bore you with my case.)
I suppose I could FLATTEN the CLASSROOM table, then do a straight join, but my real table is large & complicated enough that I want to avoid it if I can. Is there a better way?
Note: assume students can be in multiple classes. Teachers (teacher_id) are unique.

The idea is to flatten the array and reaggregate. I'm not 100% sure of the syntax in Snowflake, but I think this will work:
select c.*,
(select array_agg(ss.name) within group (order by s.index) as student_names
from table(flatten(input => c.students, mode => 'array')) s join
students ss
on ss.id = s.value
) as names
from classroom c;

Based on https://stackoverflow.com/users/1144035/gordon-linoff, I got the following to work in Snowflake:
create table CLASSROOM (teacher_id VARCHAR, students ARRAY);
insert into CLASSROOM select $1, parse_json($2)
from values ('T1','["S11","S12","S13"]'),('T2','["S21","S22","S23"]');
create table STUDENTS (id VARCHAR, name VARCHAR);
insert into STUDENTS values ('S11','Aaron'),('S12','Bob'),('S13','Charlie'),('S21','Amy'),('S22','Becky'),('S23','Cat');
select teacher_id,
array_agg(s.value::String) as student_ids,
array_agg(ss.name) as student_names
from CLASSROOM, table(flatten(input => CLASSROOM.students, mode => 'array')) s
join STUDENTS ss on ss.id = s.value
group by teacher_id, s.SEQ
order by teacher_id;

Related

SingleStore (MemSQL) How to flatten array using JSON_TO_ARRAY

I have a table source:
data
{ "results": { "rows": [ { "title": "A", "count": 61 }, { "title": "B", "count": 9 } ] }}
{ "results": { "rows": [ { "title": "C", "count": 43 } ] }}
And I want a table dest:
title
count
A
61
B
9
C
43
I found there is JSON_TO_ARRAY function that might be helpful, but got stuck how to apply it.
How to correctly flatten the json array from the table?
I have the following that works on your example but it might help you with the syntax.
In this query I created a table called json_tab with a column called jsondata.
With t as (
select table_col AS title FROM json_tab join TABLE(JSON_TO_ARRAY(jsondata::results::rows)))
SELECT t.title::$title title,t.title::$count count FROM t
I took example from the code snippet to work with Nested Arrays in a JSON Column
https://github.com/singlestore-labs/singlestoredb-samples/blob/main/JSON/Analyzing_nested_arrays.sql
Three options I came up with, which are essentially the same:
INSERT INTO dest
WITH t AS(
SELECT table_col AS arrRows FROM source JOIN TABLE(JSON_TO_ARRAY(data::results::rows))
)
SELECT arrRows::$title as title, arrRows::%count as count FROM t;
INSERT INTO dest
SELECT arrRows::$title as title, arrRows::%count as count FROM
(SELECT table_col AS arrRows FROM source JOIN TABLE(JSON_TO_ARRAY(data::results::rows)));
INSERT INTO dest
SELECT t.table_col::$title as title, t.table_col::%count as count
FROM source JOIN TABLE(json_to_array(data::results::rows)) t;

Nested select is missing required properties when LEFT JOIN

I have 4 database tables:
Questionnaires
Sections
Questions
Answers
I'm trying to get the questionnaire with nested values where Sections, Questions, and Answers are joined to each other. The query itself works but the problem is that the result only contains questions that has at least one answer.
The goal is to have a list of questions with all the answers - whether or not the answer is present.
Questionnaire
id
PrimaryKey
name
String
Sections
id
PrimaryKey
name
String
questionnaire_id
ForeignKey
Questions
id
PrimaryKey
text
String
section_id
ForeignKey
Answers
id
PrimaryKey
text
String
user_id
Number
question_id
ForeignKey
This is the query I'm trying to run:
#Injectable()
class QuestionnaireService {
findOne(id: number, user_id: string) {
return this.questionnaire
.createQueryBuilder('questionnaire')
.leftJoinAndSelect('questionnaire.sections', 'sections')
.leftJoinAndSelect('sections.questions', 'questions')
.leftJoinAndSelect('questions.answers', 'answers')
.where('questionnaire.id = :id', { id })
.andWhere('answers.user_id = :user_id', { user_id })
.getOne();
}
}
At the moment I have 14 questions in the database. And only 2 answers so the result is this:
Questionnaire {
id: 1,
name: 'Default DRI Questionnaire',
sections: [
Section {
id: 1,
name: 'Your security threat concerns',
questions: [ [Question] ]
},
Section {
id: 2,
name: 'About your organization',
questions: [ [Question] ]
}
]
}
So, I'm expecting the 14 questions to be present despite the fact that most of them do not have answers at all.
In Sequelize I've done this with include property but in TypeORM couldn't find the way to do it. 😕
This is the query TypeORM is running:
SELECT "questionnaire"."id" AS "questionnaire_id",
"questionnaire"."name" AS "questionnaire_name",
"sections"."id" AS "sections_id",
"sections"."name" AS "sections_name",
"sections"."questionnaire_id" AS "sections_questionnaire_id",
"questions"."id" AS "questions_id",
"questions"."text" AS "questions_text",
"questions"."section_id" AS "questions_section_id",
"answers"."id" AS "answers_id",
"answers"."user_id" AS "answers_user_id",
"answers"."question_id" AS "answers_question_id",
"answers"."questionnaire_id" AS "answers_questionnaire_id",
FROM "questionnaires" "questionnaire"
LEFT JOIN "sections" "sections" ON "sections"."questionnaire_id" = "questionnaire"."id"
LEFT JOIN "questions" "questions" ON "questions"."section_id" = "sections"."id"
LEFT JOIN "answers" "answers" ON "answers"."question_id" = "questions"."id"
WHERE "questionnaire"."id" = 1
AND "answers"."user_id" = 1
Solution
Thanks to #Andrew
I had to use condition check alongside leftJoinAndSelect.
#Injectable()
class QuestionnaireService {
findOne(id: number, user_id: string) {
return this.questionnaire
.createQueryBuilder('questionnaire')
.leftJoinAndSelect('questionnaire.sections', 'sections')
.leftJoinAndSelect('sections.questions', 'questions')
.leftJoinAndSelect('questions.answers', 'answers', 'answers.user_id = :user_id', { user_id })
.where('questionnaire.id = :id', { id })
.getOne();
}
}

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;

Is this SQL possible in Sequelize?

I've just started with sequelize and am trying to reproduce the below query.
I have the following Model structure: Review, Entity, ReviewThank
Each Entity can have many Reviews, and each Review can have many ReviewThanks.
An attribute of each review is a 'thumbUp' (boolean) rating.
I'm trying to generate the below query to get a 'thankCount' for each review, along with the Entity rating - thumbUpCount and totalCount - for each Review:
SELECT * FROM (
SELECT COUNT("Review"."id") AS "totalCount", "Review"."EntityId", COUNT(CASE WHEN "Review"."thumbUp" THEN 1 END) AS "thumbUpCount"
FROM "Reviews" AS "Review" GROUP BY "Review"."EntityId"
) AS "EntityRatingTable" LEFT JOIN (
SELECT "Review"."id", "Review"."EntityId", "Review"."uid", "Review"."thumbUp", "Review"."caption", COUNT("ReviewThanks"."id") AS "thankCount"
FROM "Reviews" AS "Review" LEFT OUTER JOIN "ReviewThanks" AS "ReviewThanks" ON "Review"."id" = "ReviewThanks"."ReviewId"
WHERE "Review"."UserId" IN (1) GROUP BY "Review"."id"
) AS "ReviewsTable" ON "ReviewsTable"."EntityId" = "EntityRatingTable"."EntityId";
Is it possible to produce this in sequelize? I've got the "ReviewsTable" query working ok, but unsure how (or if possible) I can join this with the "EntityRatingTable"?
This is what I've got so far:
models.Review.findAll({
attributes: {
include: [
[models.sequelize.fn('COUNT', models.sequelize.col('ReviewThanks.id')), 'thankCount'],
[models.sequelize.fn('COUNT', models.sequelize.col('Review.id')), 'reviewCount'],
],
exclude: ["EntityId", "UserId"],
},
include: [
{
model: models.ReviewThank,
attributes: [],
}, {
model: models.Entity,
}
],
group: ['"Review"."id"', '"Entity.id"'],
})

how can i write this query with mongodb? [duplicate]

This question already has answers here:
How do I perform the SQL Join equivalent in MongoDB?
(19 answers)
Closed 6 years ago.
I want to write this query with mongodb
select *
from tab1 a, tab2 c
where a.a_id = 2
and c.c_id = 3
and a.a_id = c.c_fk_account_id_created_by
I tried this code but didn't get a response:
$cursor = $collection->find(array('$and' => array(array("a_id" => 2), array("c_id" => 3))));
I will assume you have two collections, named tab1 and tab2 in the form of
tab1
{
"_id" : ObjectId("58482a97a5fa273657ace535"),
"a_id" : NumberInt(2)
}
tab2
{
"_id" : ObjectId("58482acca5fa273657ace539"),
"c_id" : NumberInt(3),
"c_fk_account_id_created_by" : NumberInt(2)
}
You will need an aggregation query with two steps, first, $lookup to the second table, and second $match on the proper keys. Like this.
db.tab1.aggregate(
[
{
$lookup: {
"from" : "tab2",
"localField" : "a_id",
"foreignField" : "c_fk_account_id_created_by",
"as" : "c"
}
},
{
$match: {
"a_id": 2,
"c.c_id": 3
}
},
]
);
This will give you an output like this
{
"_id" : ObjectId("58482a97a5fa273657ace535"),
"a_id" : NumberInt(2),
"c" : [
{
"_id" : ObjectId("58482acca5fa273657ace539"),
"c_id" : NumberInt(3),
"c_fk_account_id_created_by" : NumberInt(2)
}
]
}
Good luck!
I wrote an article on just this type of query:
MongoDB Aggregation Framework for T-SQL Pros #3: The $lookup Operator
https://www.linkedin.com/pulse/mongodb-aggregation-framework-t-sql-pros-3-lookup-operator-finch
Essentially you are going to bring all documents from your second table into the results of the first table using the $lookup aggregation operator. You can then use the $match and $group operators to filter and aggregate your data.
It will go something like this:
db.tab1.aggregate([
{ $match:
{ "tab1.a_id": 2 }
},
{ $lookup:
{ from: "tab2",
localField: "a_id",
foreignField: "c_fk_account_id",
as: "tab2_results"
}
},
{ $match:
{ "tab2_results.c_id": 3 }
}
]}
The matching joined documents will be added to the base table's document as an array. It acts as a LEFT join in that null values from the remote table are ignored and your base table document is still returned, only missing remote data.
Hope this helps!
Bill
Let's assume tab1 and tab2 have 3 fields each as a_id, aa1, aa2 and c_id, c_fk_account_id_created_by, cc1
The query will be as follows
db.tab1.aggregate([{$match:{a_id:2}},{$lookup:{from:'tab2', localField:'c_fk_account_id_created_by', foreignField:'a_id', as:'ccArray'}},{$unwind:'$ccArray'},
{$project:{a_id:1,aa1:1, aa2:1, c_id:'$ccArray.c_id',c_fk_account_id_created_by:'$ccArray.c_fk_account_id_created_by',cc1:'$ccArray.cc1'}},{$match:{c_id:3}}])
Explanation of the above query:
As MongoDB doesn't allow to match from second table in the aggregation pipeline so we have to unwind the second table array and compare the value
select *
from tab1 a, tab2 c
where a.a_id = 2 ==> {$match:{a_id:2}}
and c.c_id = 3 ==> (Cannot be done at first so it can be acheived as ) ==> {$unwind:'$ccArray'},
{$project:{a_id:1,aa1:1, aa2:1, c_id:'$ccArray.c_id',c_fk_account_id_created_by:'$ccArray.c_fk_account_id_created_by',cc1:'$ccArray.cc1'}},{$match:{c_id:3}}
and a.a_id = c.c_fk_account_id_created_by ==> {$lookup:{from:'tab2', localField:'c_fk_account_id_created_by', foreignField:'a_id', as:'ccArray'}}