Using COUNT aggregate function on sequelize adding unwanted id field - javascript

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

Related

Sequelize filter the included tables by where

I am trying to findAll records included nested tables, but did not understand how could I filter included tables by where clause. Here are the words:
const players = await PlayerService.findPlayers({
attributes: { exclude: ['password'] },
include: [
{ all: true },
{
model: Team,
as: 'captainTeams',
attributes: {exclude: ['createdAt', 'updatedAt']}
},
{
model: Team,
as: 'teams',
where: { type: 1 },
required: true,
through: {attributes: []},
attributes: {exclude: ['createdAt', 'updatedAt']}
}
]
})
Here is the result:
If I delete where and required from including Team clause, here is the result:
I would like to filter teams.type=2. Could you help me ?

How I can include array of model?

var user = await db.user.findOne({
attributes: attributes,
include: ['charges',
'availability',
'practice',// 'services',
'education',// 'user_role',
'address',
'user_location',
// 'isAvailableStatus',
{ model: db.user_role, attributes: role_attributes },
{
model: db.user_service, as: 'services',
// ThIS IS MY QUESTION
include : [db.service_group, as : 'group']
//
},
],
where: {id: 0}
});
User -> user_role-> one of user_role # This is working
User -> user_service-> array of service_group # this is not working// my question
How I can include array of model?
You need to modify your code as follows. If you have defined associations properly then you shall get an intended result.
var user = await db.user.findOne({
attributes: {
include: [
'charges',
'availability',
'practice', // 'services',
'education', // 'user_role',
'address',
'user_location'
// 'isAvailableStatus',
]
},
include: [
{ model: db.user_role, attributes: role_attributes },
{
model: db.user_service,
as: 'services',
include: [{ model: db.service_group, as: 'group' }]
}
],
where: { id: 0 }
});
Hope it helps!

Sequelize: How can I prevent virtual columns from included relation from appearing in query result?

I have a query that looks like this:
return GroupMember.findOne({
where: { gid: gid, member_id: uid },
include: [
{
model: User,
as: 'member',
attributes: ['display_name'],
},
{
model: Group,
as: 'group',
attributes: ['name'],
}
]
});
So, for the "member" relation, I am requesting only the 'display_name" column. However, the User model has 3 virtual columns declared in it, and they are always present in the query result even though I asked for only 'display_name'. How do I prevent the virtual columns from being present in the result?
So for excluding virtual columns, you have to use the exclude property the attributes field, so the new query should be like this
return GroupMember.findOne({
where: { gid: gid, member_id: uid },
include: [
{
model: User,
as: 'member',
attributes: { include: ['display_name'], exclude: ['virtual_columne_name1', 'virtual_columne_name2', 'virtual_columne_name3']},
},
{
model: Group,
as: 'group',
attributes: ['name'],
}
]
});

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

Combine model's condition with its association's condition in Sequelize

Is there a way to find models by a condition that applies to its own field or to its association's field?
Given models Model and Association, where each Model has one Association.
const Model = sequelize.define('model', {
name: sequelize.STRING,
});
const Association = sequelize.define('association', {
name: sequelize.STRING,
});
Association.belongsTo(Model);
Model.hasOne(Association);
I want to find all Models, that either has a name equal to "text", or has an Association with a name equal to "text".
So far I come up with the solution with Sequelize.literal, that doesn't look robust enough.
Model.findAll({
attributes: ['id', 'name'],
include: [{
model: Association,
attributes: [],
}],
where: {
$or: [
{ name: 'test' },
Sequelize.literal('association.name = \'test\''),
],
},
});
Is there a better way?
This feature is described in the docs: Top level where with eagerly loaded models.
Model.findAll({
attributes: ['id', 'name'],
include: [{
model: Association,
attributes: [],
}],
where: {
$or: [
{ name: 'test' },
{ '$association.name$': 'test' },
],
},
});

Categories