Is this SQL possible in Sequelize? - sql

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"'],
})

Related

Sequelize - Subselect association to filter parent model

I've recently started to use Sequelize 6. Currently trying to express the following query unsuccessfully.
SELECT u.*
FROM users u
JOIN requests r
ON u.id = r.user_id
WHERE (r.status = true AND r.created_at IN (SELECT
MAX(r.created_at)
FROM users u
INNER JOIN requests r ON r.user_id = u.id
GROUP BY u.id))
GROUP BY u.id, r.id
What I have tried so far:
const result = await User.findAll({
offset: page,
subQuery: false,
limit: rowsPerPage,
where: {
'$requests.status$': true,
},
include: [{
as: 'requests',
model: Request,
separate: true,
required: true,
order: [['createdAt', 'desc']],
limit: 1, // problem is here -> error: missing FROM-clause entry for table "requests"
}],
});
In other words, I'm trying to load the parent model only when the association's status value is true on the last record, and return each individual user row alongside with its latest association.
Regarding the limit problem on my comment, this thread might be useful
https://github.com/sequelize/sequelize/issues/11617
I had to change original SQL a little bit to work with Sequelize because a certain combination of required, where didn't work well.
So, I went off from your description.
I'm trying to load the parent model only when the association's status
value is true on the last record
If I understand this correctly, I think you can use having query to achieve this.
const result = await User.findAll({
offset: page,
subQuery: false,
limit: rowsPerPage,
include: [{
as: 'requests',
model: Request,
required: true
}],
having: {
'$requests.createdAt$': [sequelize.fn('MAX', sequelize.col('`requests`.`createdAt`'))],
'$requests.status$': true
},
group: ['users.id'],
});
This will produce a following SQL. Please try this SQL and let me know if this is what you are looking for or not.
SELECT ... FROM `users` AS `users`
INNER JOIN `requests` AS `requests`
ON `users`.`id` = `requests`.`userId`
GROUP BY `users`.`id`
HAVING `requests`.`createdAt` IN (MAX(`requests`.`createdAt`)) AND `requests`.`status` = 1
LIMIT 0, 5;

i am getting count as 1 instead of 0 when i did groupby using sequelize in nodejs

I have two tables "project" and "company". I have created their models as 'Project' and 'Company' in sequelize.
Project table contains
project_id
company_id
project_name
1
1
project1
2
2
project2
3
2
project3
Company table contains
company_id
company_name
1
xyz
2
test
3
abc
my requirement is to get all companies in the Company table and the no of projects they have.
i have wrote the query in sequelize as
const result = await Company.findAll({
attributes: [ ['company_id', 'id'] ,'company_name', [Sequelize.fn('COUNT', 'Project.project_id'), 'no_of_projects'] ],
include: [{ model: Project, attributes: [] }],
group: ['company_id'],
order: [
[sortBy, sortOrder]
],
offset: index,
limit: limit,
subQuery: false
})
but i am getting no_of_projects as 1 instead of 0 for company "abc" as it doesn't have any project. I need to get no_of_projects as 0 if there is no project for the company. i am new to sql and sequelize. can anyone please help me in solving this. thanks in advance.
these are the associations
Company.associate = function(models) {
Company.hasMany(models.Project, {
foreignKey: "company_id"
})
}
Project.associate = function(models) {
Project.belongsTo(models.Company, {
foreignKey: 'company_id',
})
}
I have found the mistake in my sequelize query.
Instead of
[Sequelize.fn('COUNT', 'Project.project_id')
It worked when I replace that with
[Sequelize.fn('COUNT', Sequelize.col('project_id'))
I need to specify that as a column to sequelize otherwise it is considering empty string as a value. That's why I got count as 1 instead of 0.
If you want SQL then you can use following:
select c.company_id, c.company_name, count(p.project_id) as num_of_project
from company c left join project p on p.comapny_id = c.company_id
group by c.company_id, c.company_name

SQL JOIN values to ARRAY to create another ARRAY field

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;

find using model association

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

Sequelize js get count of associated model

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
});