How can I count inside nested associations in Sequelize? - javascript

I try to count product reviews in nested associations. With the following query.
const user = await User.findOne({
where: {
id: req.query.user
},
attributes: ["id", "name"],
include: [
{
model: Category,
as: "interests",
attributes: ["category_name"],
through: {
attributes: []
},
include: [
{
model: Product,
as: "products",
attributes: {
include: [
[
// How to count product_reviews here?
sequelize.literal(`
(SELECT COUNT(*) FROM product_reviews WHERE productId = Product.id)
`),
"num_reviews"
]
]
},
include: [
{
model: User,
as: "userReviews",
attributes: []
}
]
}
]
}
]
});
In the model definitions I have a belongsTo/haveMany association set up e.g.:
Inside my models
// User model
User.belongsToMany(models.Category, {
through: "user_categories",
as: "interests",
foreignKey: "userId"
});
User.belongsToMany(models.Product, {
through: "user_reviews",
as: "reviews",
foreignKey: "userId"
});
// Category model
Category.hasMany(models.Product, {
foreignKey: "categoryId",
as: "products"
});
// Product model
Product.belongsToMany(models.User, {
through: "product_reviews",
as: "userReviews",
foreignKey: "productId"
});
// Product_reviews model
product_review.belongsTo(models.User, { foreignKey: "userId" });
product_review.belongsTo(models.Product, { foreignKey: "productId" });
How to count product reviews?
Here the result I want.
{
"id": 1,
"name": "John Doe",
"interests": [
{
"category_name": "Toys",
"products": [
{
"id": 1,
"name": "Lorem Ipsum",
"num_reviews": 20 // I need to count # of reviews here
},
...
]
}
]
}
Can anyone explain how to get counting inside nested associated in this case?

I guess you are on the right track and you just need to modify the way of constructing Sequelize literal for reviews count as follows -
sequelize.literal(`
(SELECT COUNT(*) FROM product_reviews WHERE productId = \`products\`.\`id\`)
`)

Related

How can I apply the cluster $in with the where in an array type property using strapi V4

My intention is to be able to apply the $in cluster to the categories property, but it is an array type property. I know that my implementation is not correct, that's why I want some solution to my case.
I will appreciate any solution.
Query Example
where: {
$and: [
categories: {
name: {
$in:["fire"],
},
},
]
},
const entries = await strapi.db
.query("api::data.data")
.findMany({
populate: ["*"],
orderBy: "id",
where: {} // todo
});
Entry Example
{
"id": 1,
"description: "....",
"categories": [
{"id": 1, "name": "fire"},
{"id": 2, "name": "water"}
]
}
After several tests my implementation was functional for an array type property.
where: {
$and: [
categories: {
name: {
$in:["fire"],
},
},
]
},
Can you try once with following code?
const entries = await strapi.db
.query("api::data.data")
.findMany({
populate: ["*"],
orderBy: "id",
where: {
$and: [
categories: {
name: {
$in:["fire"],
},
},
]
},
});

Using COUNT aggregate function on sequelize adding unwanted id field

I am trying to do a COUNT on related models using sequelize 6.21.6 get total number of jobs under each category.
My model looks like:
models.Sector.hasMany(models.Category, {
foreignKey: 'sectorId',
as: 'Categories',
});
models.Category.hasMany(models.Job, {
foreignKey: 'categoryId',
as: 'Jobs',
});
I am running this query with COUNT:
const getSectorsCategories = async () => {
const sectors = await Sector.findAll({
attributes: [
'name'
],
include: [
{
model: Category,
as: 'Categories',
attributes: ['name', 'sectorId',
[sequelize.fn('COUNT', sequelize.col('Categories.Jobs.id')), 'jobCount']
],
include: [
{
model: Job,
as: 'Jobs',
attributes: ['title', 'categoryId'],
},
],
},
],
group: ['Sector.id', 'Categories.id'],
},);
return sectors;
};
With the following SQL:
Executing (default):
SELECT
"Sector"."id",
"Sector"."name",
"Categories"."id" AS "Categories.id",
"Categories"."name" AS "Categories.name",
"Categories"."sectorId" AS "Categories.sectorId",
COUNT("Categories->Jobs"."id") AS "Categories.jobCount",
"Categories->Jobs"."id" AS "Categories.Jobs.id",
"Categories->Jobs"."title" AS "Categories.Jobs.title",
"Categories->Jobs"."categoryId" AS "Categories.Jobs.categoryId"
FROM
"Sectors" AS "Sector"
LEFT OUTER JOIN "Categories" AS "Categories" ON "Sector"."id" = "Categories"."sectorId"
LEFT OUTER JOIN "Jobs" AS "Categories->Jobs" ON "Categories"."id" = "Categories->Jobs"."categoryId"
GROUP BY
"Sector"."id",
"Categories"."id",
"Categories->Jobs"."id";
You notice this field was added by sequelize automatically: "Categories->Jobs"."id" AS "Categories.Jobs.id"
Which now produces this error:
"error": "column \"Categories->Jobs.id\" must appear in the GROUP BY clause or be used in an aggregate function"
Seems the only way to remove this error is by passing in an empty attributes array to Jobs:
include: [
{
model: Job,
as: 'Jobs',
attributes: [],
},
]
Now the aggregate function COUNT works as expected but I don't have any list of job attributes as I wanted.
Is there any workaround for this all-or-nothing approach?
You can try using window function.
const sectors = await Sector.findAll({
attributes: [
'name'
],
include: [
{
model: Category,
as: 'Categories',
attributes: ['name', 'sectorId',
[sequelize.literal('COUNT("Categories->Jobs"."id") OVER (PARTITION BY "Sector"."id", "Categories"."id")'), 'jobCount']
],
include: [
{
model: Job,
as: 'Jobs',
attributes: ['title', 'categoryId'],
},
],
},
],
});

Sequelize.js includes returning incorrect data

I'm making a sample donations app and I want to get all of the donations a user has made, as well as the Causes that donation is connected too.
exports.getUserDonations = (req, res) => {
// Get Donations made by the user by userID
Donation.findAll({
where: {
userID: req.params.id,
},
attributes: ['amount', 'updatedAt', 'causeID'],
include: [{
model: Cause,
as: 'Causes',
}]
})
.then(donations => {
if (donations) {
res.status(200).json(donations);
} else {
res.status(404).send({ error: "No Donations found" });
}
})
.catch(err => {
console.log("Error: ", err)
res.status(500).json(err);
});
}
For some reason I get this kind of result. As you can see the "causeID"(12) and the "Cause.id"(17) fields do not match. My thought is that the where statement above might be overriding something.
{
"amount": 82,
"updatedAt": "2018-11-02T17:04:30.847Z",
"causeID": 12,
"Causes": {
"id": 17,
"userID": 1,
"orgID": null,
"name": "Croation Trip",
"type": "Trip",
"amount": 3000,
"description": "Just taking a trip",
"purpose": "Food, sun, and fun",
"createdAt": "2018-09-20T21:56:37.330Z",
"updatedAt": "2018-09-20T21:56:37.330Z"
}
},
// Most fo the time the cause comes back as null
{
"amount": 10,
"updatedAt": "2018-11-27T01:35:06.061Z",
"causeID": 13,
"Causes": null
},
Here are my associations for each of those
// Cause association
Cause.associate = function(models) {
Cause.belongsTo(models.User, {
as: "Users",
foreignKey: "id"
})
Cause.hasMany(models.Donation, {
as: "Donations",
foreignKey: "causeID"
})
};
// Donation associations
Donation.associate = function(models) {
Donation.belongsTo(models.Cause, {
as: "Causes",
foreignKey: "id"
})
Donation.belongsTo(models.User, {
as: "Users",
foreignKey: "id"
})
};
Looks like you are mismatching some keys. I think your example is joining Donation.Id to Cause.Id - you should confirm by looking at the generated SQL. Here's how to associate keys with different names:
Donation.belongsTo(models.Cause, {
as: "Causes",
{foreignKey: "id", sourceKey: "causeId"}
})
Or test with more Donation attributes:
attributes: ['id', 'userId', 'amount', 'updatedAt', 'causeID'],
Does the donation id match the cause id?

How to query sequelize model by association, but include all associated objects?

Trying to query by all association's attribute, but get all associations
# FAQs: { id: 1, name: 'How to do it?' }, { id: 2, name: 'How to FIX it?' }
# tags: { id: 1, slug: 'api' }, { id: 2, slug: 'beta' }
# taggings: { id: 1, faqId: 1, mainEntityId: 1, mainEntityType: 'faq' }, { id: 2, faqId: 1, mainEntityId: 2, mainEntityType: 'faq' }
const query = { slugs: ['api'] }
const foundFAQs = await this.models.FAQ.findAll({
where: {
'$taggings.tag.slug$': { $in: query.slugs },
},
include: [{
model: this.models.Tagging,
as: "taggings",
include: [{
model: this.models.Tag,
as: 'tag',
}],
}],
})
My model definition:
models.Tagging.belongsTo(models.Tag, { as: 'tag', onDelete: 'cascade' });
models.Tag.hasMany(models.Tagging, { as: 'taggings', onDelete: 'cascade' });
models.Tag.belongsToMany(models.FAQ, { through: models.Tagging, as: 'faqs' });
models.FAQ.hasMany(models.Tagging, { as: 'taggings', onDelete: 'cascade' });
models.FAQ.belongsToMany(models.Tag, { through: models.Tagging, as: 'tags' });
What do you expect to happen?
I want to get all FAQs what has associated TAG api and have all its tags.
Object:
{
id: 1,
name: 'How to do it?',
tags: [
{ id: 1, slug: 'api' },
{ id: 2, slug: 'beta' }
]
}
What is actually happening?
Query returns object:
{
id: 1,
name: 'How to do it?',
tags: [
{ id: 1, slug: 'api' }
]
}
Output
SELECT \"faq\".\"id\", \"faq\".\"name\", \"faq\".\"bankId\", \"faq\".\"priority\",
\"faq\".\"publishedLocales\", \"faq\".\"createdAt\", \"faq\".\"updatedAt\", \"taggings\".\"id\"
AS \"taggings.id\", \"taggings\".\"tagId\" AS \"taggings.tagId\", \"taggings\".\"locked\"
AS \"taggings.locked\", \"taggings\".\"sdkId\" AS \"taggings.sdkId\", \"taggings\".\"guideId\"
AS \"taggings.guideId\", \"taggings\".\"newsId\" AS \"taggings.newsId\", \"taggings\".\"faqId\"
AS \"taggings.faqId\", \"taggings\".\"apiId\" AS \"taggings.apiId\", \"taggings\".\"createdAt\"
AS \"taggings.createdAt\", \"taggings\".\"updatedAt\" AS \"taggings.updatedAt\", \"taggings->tag\".\"id\"
AS \"taggings.tag.id\", \"taggings->tag\".\"name\" AS \"taggings.tag.name\", \"taggings->tag\".\"slug\"
AS \"taggings.tag.slug\", \"taggings->tag\".\"tagType\" AS \"taggings.tag.tagType\", \"taggings->tag\".\"mainEntityId\"
AS \"taggings.tag.mainEntityId\", \"taggings->tag\".\"createdAt\"
AS \"taggings.tag.createdAt\", \"taggings->tag\".\"updatedAt\"
AS \"taggings.tag.updatedAt\" FROM \"faqs\" AS \"faq\" INNER JOIN \"taggings\"
AS \"taggings\" ON \"faq\".\"id\" = \"taggings\".\"faqId\" LEFT OUTER JOIN \"tags\"
AS \"taggings->tag\" ON \"taggings\".\"tagId\" = \"taggings->tag\".\"id\" WHERE \"faq\".\"bankId\" = 'bank.csas'
AND \"taggings->tag\".\"slug\" IN ('faq') ORDER BY \"faq\".\"priority\" DESC;
Dialect: postgres
Dialect version: pg#^6.1.0
Database version: PostgreSQL 10.1
Sequelize version: 4.23.2
Tested with the latest release: No (4.23.2)
Node: 8.6.0
I am not sure if it is a bug or I'm doing something wrong.
Thanks
You're looking to include all related data for each record, but filter those records on that related data. You're going to want to get Sequelize to generate SQL similar to:
SELECT "faq"."id", ....
FROM "faqs" AS "faq"
INNER JOIN "taggings" AS "taggings" ON "faq"."id" = "taggings"."faqId"
LEFT OUTER JOIN "tags" AS "taggings->tag" ON "taggings"."tagId" = "taggings->tag"."id"
LEFT OUTER JOIN "tags" AS "taggings->tagdata" ON "taggings"."tagId" = "taggings->tagdata"."id"
WHERE "faq"."bankId" = 'bank.csas' AND "taggings->tag"."slug" IN ('faq')
ORDER BY "faq"."priority" DESC;
Something like this might work:
const foundFAQs = await this.models.FAQ.findAll({
where: {
'$taggings.tag.slug$': { $in: query.slugs },
},
include: [{
model: this.models.Tagging,
as: "taggings",
include: [{
model: this.models.Tag,
as: 'tag',
},{
model: this.models.Tag,
as: 'tagdata',
}],
}],
})
The idea here is that you're performing a join on which you are filtering, and then another join to get the extra records for the filtered rows.
This worked for me today -- nested includes:
getReferralPlanForThisCode(parent, args, context) {
let {referralCode} = args;
return Promise.resolve()
.then(() => {
let referralPlan = connectors.ReferralPlans.findAll({
include: [{
model: connectors.ReferralCodes,
where: {unique_referral_code: referralCode},
include: [{
model: connectors.epUserData, as: 'referrer',
}],
}],
})
return referralPlan;
})
.then(referralPlan => {
return referralPlan;
})
.catch((err) => {
console.log(err);
});
}

Geting value of a relationship in sequelize js

I'm trying to get the value and returns postgres me that this column does not exist. I am not able to deal in any way.
"column Group.participant.id does not exist"
relationship:
User.belongsToMany(models.group, { as: 'Member', through: { model: models.participant, unique: false}, foreignKey: 'userId', constraints: false});
Group.belongsToMany(models.user, { as: 'Group', through: { model: models.participant, unique: false}, foreignKey: 'groupId', constraints: false });
Participant.belongsTo(models.user, {
foreignKey: "userId", as: "Member"
});
Participant.belongsTo(models.group, {
foreignKey: "groupId", as: "Group"
});
Query/Sequelizejs:
const User = app.db_connect.postgres.models.user;
Group.findAll({
include: [
{ model: User,
as: 'Group',
where: {
"participant.id": idUser
}
// attributes: ['name']
}
]})
.then(result => res.json(result))
.catch(error => {
res.status(412).json({msg: error.message});
});
});
Query generate:
Executing (default): SELECT "group"."id", "group"."name", "group"."isMain", "group"."privacy", "group"."description", "group"."img", "group"."createdAt", "group"."updatedAt", "group"."destroyTime", "group"."categoryId", "Group"."id" AS "Group.id", "Group"."name" AS "Group.name", "Group"."birthday" AS "Group.birthday", "Group"."cpf" AS "Group.cpf", "Group"."email" AS "Group.email", "Group"."img" AS "Group.img", "Group"."status" AS "Group.status", "Group"."phone" AS "Group.phone", "Group"."cellPhone" AS "Group.cellPhone", "Group"."nickName" AS "Group.nickName", "Group"."password" AS "Group.password", "Group"."found" AS "Group.found", "Group"."createdAt" AS "Group.createdAt", "Group"."updatedAt" AS "Group.updatedAt", "Group"."destroyTime" AS "Group.destroyTime", "Group.participant"."id" AS "Group.participant.id", "Group.participant"."function" AS "Group.participant.function", "Group.participant"."createdAt" AS "Group.participant.createdAt", "Group.participant"."updatedAt" AS "Group.participant.updatedAt", "Group.participant"."groupId" AS "Group.participant.groupId", "Group.participant"."userId" AS "Group.participant.userId" FROM "groups" AS "group" INNER JOIN ("participants" AS "Group.participant" INNER JOIN "users" AS "Group" ON "Group"."id" = "Group.participant"."userId") ON "group"."id" = "Group.participant"."groupId" AND ("Group"."destroyTime" IS NULL AND "Group"."participant.id" = 1) WHERE "group"."destroyTime" IS NULL;
Returns without the where: Pastebin
Use just id in where, because in include section you work with a columns of specific model
Group.findAll({
include: [{ model: User, as: 'Group',
where: {
"id": idUser
}
}]})

Categories