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();
}
}
Related
First time posting a question on here.
I am working with PostgreSQL and had a question on how to format data coming from a PostgreSQL DB. For practice, I am building a comment thread app similar to Reddit's. I organized my database in the following way
Post Table
CREATE TABLE Posts (
id serial PRIMARY KEY,
userId int,
header VARCHAR
)
Comment Table
CREATE TABLE Comments (
id serial PRIMARY KEY,
userId int,
commentText VARCHAR,
parentId int,
postId int
)
I want my end data to be an array of objects organized by postId with a comments key / value pair that stores all the comments for that postId (example below).
Should I format my data in this way using postgres queries, run sorting server side, or on client side? AND is this a conventional/efficient way of handling/formatting this kind of data? or am I missing some other way of organizing data for a comment thread?
Im use to working with MongoDb so not sure if my way of wanting to structure the data is due to working with mongoDb.
I would like for my data to look somewhat like this (unless there is a better more efficient way of doing it):
const posts = [
{
postId: 1,
header: 'Post Header',
comments: [
{
commentId: 1,
text: 'comment text',
parentId: null
},
{
commentId: 2,
text: 'comment text',
parentId: 1
}
]
},
{
postId: 2,
header: 'Post Header',
comments: [
{
commentId: 3,
text: 'comment text',
parentId: null
},
{
commentId: 2,
text: 'comment text',
parentId: 3
}
]
},
]
Thank you in advance!!
Postgres has a number of built-in ways to handle JSON: https://www.postgresql.org/docs/9.5/functions-json.html
Something like
SELECT
postID
,json_build_array(
json_build_object('commentId', id, 'text', commentText, 'parentId', parentId)
) as comments
FROM comments
GROUP BY postId
and then just join to the original Posts for metadata
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"'],
})
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'}}
Dialect: postgres
Database version: #latest
Sequelize version: #latest
I'm trying to find out how to use an associate model. I've got 3 models: post, postCity and region. They have the following relation:
postCity (post_id, region_id) associate to post (post_id) and region (region_id). I am using a search function like this:
include: [
{
model: models.postCity,
include:[{model:models.region}],
attributes: [[models.sequelize.fn('count', 'post_id'), 'count']],
}
],
where: {
$or: [
{
"create_by" : {$not: 67}
},
{
// "postCities.region_name":{$iLike: "%Guangazhou2%"}
},
{
"description":{$iLike: "%India%"}
}
]
}
which leads to:
SELECT "post"."post_id", "post"."description", "post"."create_by",
"post"."create_time", "post"."update_time", "post"."country_id",
"postCities"."post_id" AS "postCities.post_id",
"postCities"."region_id" AS "postCities.region_id",
"postCities"."order_no" AS "postCities.order_no",
"postCities.region"."region_id" AS "postCities.region.region_id",
"postCities.region"."region_name" AS "postCities.region.region_name",
"postCities.region"."country_id" AS "postCities.region.country_id",
"postCities.region"."province_id" AS "postCities.region.province_id"
FROM "t_post" AS "post"
LEFT OUTER JOIN "t_post_city" AS "postCities"
ON "post"."post_id" = "postCities"."post_id"
LEFT OUTER JOIN "t_region" AS "postCities.region"
ON "postCities"."region_id" = "postCities.region"."region_id"
WHERE ("post"."create_by" != 67 OR "post"."description" ILIKE '%India%');
When I uncomment "postCities.region_name":{$iLike: "%Guangazhou2%"} then I get this error
column post.postCities.region_name does not exist
I simply like to my query to be like this
... WHERE ("post"."create_by" != 67
OR "post"."description" ILIKE '%India%'
OR "postCities.region_name" ILIKE: "%Guangazhou2%")
Update
I also tried to include [{model:models.region, where:{"region_name":{$iLike: "%Guangazhou2%"}}}] but this doesn't give me the appropriate result.
In order to add condition to included tables, you should wrap condition with $ symbol, like it:
include: [{
model: models.postCity,
include:[{model:models.region}],
attributes: [[models.sequelize.fn('count', 'post_id'), 'count']],
}],
where: {
$or: [{
"create_by" : {$not: 67}
}, {
"$postCities.region.region_name$":{$iLike: "%Guangazhou2%"}
}, {
"description":{$iLike: "%India%"}
}]
}
Say for example I have a discussions table and replies table.
How do I get the count of replies for each discussion record ?
Models.course_discussions.count({
where: { 'course_id': 105 },
include: [
{model: Models.course_discussions_replies, as: 'replies'}
]
})
.then(function (discussions) {
if (!discussions) {
reply(Hapi.error.notFound());
} else {
console.log("91283901230912830812 " , discussions);
}
});
The above code is converted into the following query -
SELECT COUNT(`course_discussions`.`id`) as `count` FROM `course_discussions` LEFT OUTER JOIN `course_discussions_replies` AS `replies` ON `course_discussions`.`id` = `replies`.`discussion_id` WHERE `course_discussions`.`course_id`=105;
The above code gets me count of discussions. But how do I get the count of the replies for each discussion ?
The following sql query works, but how do I write it in the sequelize way ?
SELECT COUNT( `replies`.`discussion_id` ) AS `count`
FROM `course_discussions`
LEFT OUTER JOIN `course_discussions_replies` AS `replies` ON `course_discussions`.`id` = `replies`.`discussion_id`
WHERE `course_discussions`.`course_id` =105
If you need the discussions and the replies count maybe .replies.length should work for you.
Models.course_discussions.findAll(
where: { 'course_id': 105 },
include: [
{model: Models.course_discussions_replies, as: 'replies'}
]
})
.then(function (discussions) {
// each discussion in discussions will have a
// replies array
discussions[0].repies.length // replies for current discussion
});