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

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

Related

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 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 Avoid Sequelize JS Associations Including both the Foreign Key and the Included Object

How can I avoid showing both the foreignKey that sequelize creates and the eagerly fetched object through includes?
I have the following model structure:
FormEntry:
owner: User
createdBy: User
modifiedBy: User
formEntryData: [FormEntryData]
I modeled it after reading through SequelizeJS docs and came up with the following:
const User = sequelize.define('user', {
id: {
type: Sequelize.BIGINT(20),
field: 'user_id',
primaryKey: true
},
emailAddress: {
type: Sequelize.STRING(256),
field: 'email_address'
}
}, {
tableName: 'users',
timestamps: false
});
const FormEntryData = sequelize.define('formEntryData', {
id: {
type: Sequelize.BIGINT(20),
field: 'id',
primaryKey: true
},
entryId: {
type: Sequelize.BIGINT(20),
field: 'entry_id'
},
...
}, {
tableName: 'formEntryData',
timestamps: false
});
const FormEntry = sequelize.define('formEntry', {
id: {
type: Sequelize.BIGINT(20),
field: 'entry_id',
primaryKey: true
},
...
}, {
tableName: 'formEntries',
timestamps: false
});
I then need to create the associations to tie the models together and after a lot of trial and error I came up with the following:
FormEntry.hasMany(FormEntryData, {foreignKey: 'entry_id', as: 'FormEntryData'});
FormEntry.belongsTo(User, {foreignKey: 'created_by', as: 'CreatedBy'});
FormEntry.belongsTo(User, {foreignKey: 'modified_by', as: 'ModifiedBy'});
FormEntry.belongsTo(User, {foreignKey: 'owner', as: 'Owner'});
I then was able to query the data by doing the following:
FormEntry.findByPrimary(1472280, {
include: [
{
model: FormEntryData,
as: "FormEntryData"
},
{
model: User,
as: "CreatedBy"
},
{
model: User,
as: "Owner"
},
{
model: User,
as: "ModifiedBy"
}
]
})
Unfortunately, my results seem kind of repetitive as it seems to be including both the foreign key and the object that is eagerly fetched.
{
"id": 1472280,
...
"created_by": 26508, <-- repetitive (don't want this)
"modified_by": 26508, <-- repetitive (don't want this)
"owner": null, <-- repetitive (don't want this)
"FormEntryData": [
{
"id": 27164476,
"entryId": 1472280, <-- repetitive (but I want this one)
...
"entry_id": 1472280 <-- repetitive (don't want this)
},
...
],
"CreatedBy": { <-- repetitive (but I want this one)
"id": 26508,
"emailAddress": "swaraj.kler#greywallsoftware.com"
},
"Owner": null, <-- repetitive (but I want this one)
"ModifiedBy": { <-- repetitive (but I want this one)
"id": 26508,
"emailAddress": "swaraj.kler#greywallsoftware.com"
}
}
You need to exclude specified fields from the query
FormEntry.findByPrimary(1472280, {
include: [
{
model: FormEntryData,
as: "FormEntryData",
attributes: { exclude: ['entry_id'] }
},
{
model: User,
as: "CreatedBy"
},
{
model: User,
as: "Owner"
},
{
model: User,
as: "ModifiedBy"
}
],
attributes: { exclude: ['owner', 'created_by', 'modified_by'] }
})

Categories