Sequelize filtering one to many relation - javascript

I have a User model and Timeoffs model with one to many relation. I want get all the users that doesn't have timeoffs in the specified date. Right now in the code I'm doing the exact opposite of this - getting only the users with timeoffs in the given date. How can I do this in right way? Is it possible to just invert include in that part or should I write a more complex query?
getQuestionHourUsersByDate (date, timeId) {
return UsersModel.findAll({
where: {
[USERS.COLUMNS.QH_STATUS]: true,
},
include: [
{
model: TimeoffsModel,
where: {
[Operator.and]: {
[TIMEOFFS.COLUMNS.PROCEED_STATUS]: true,
[TIMEOFFS.COLUMNS.STATUS]: TimeOffEnums.TIMEOFF_REQUEST_STATUS.APPROVED,
},
[Operator.and]: {
[TIMEOFFS.COLUMNS.START_DATE]: {
[Operator.lte]: date,
},
[TIMEOFFS.COLUMNS.END_DATE]: {
[Operator.gte]: date,
},
},
},
},
{
model: RolesModel,
where: {
[ROLES.COLUMNS.ROLE_GROUP_ID]: 1,
},
through: {
attributes: [],
where: {
[USER_ROLES.COLUMNS.STATE]: true,
},
},
},
{
model: ResponsibilitiesModel,
attributes: [
RESPONSIBILITIES.COLUMNS.NAME,
RESPONSIBILITIES.COLUMNS.RESPONSIBILITY_GROUP_ID,
RESPONSIBILITIES.COLUMNS.COLOR_CODE,
],
where: {
[RESPONSIBILITIES.COLUMNS.RESPONSIBILITY_GROUP_ID]: 2,
},
required: false,
through: {
attributes: [],
where: {
[USER_RESPONSIBILITIES.COLUMNS.ACTIVE]: true,
},
},
},
{
model: LanguagesModel,
attributes: [
LANGUAGES.COLUMNS.CODE,
LANGUAGES.COLUMNS.NAME,
],
},
{
model: UserCalendarsModel,
include: [
{
model: CalendarsModel,
where: {
[CALENDARS.COLUMNS.START_DATE]: date,
[CALENDARS.COLUMNS.ACTIVE]: true,
},
include: [
{
model: QuestionHourSlotsModel,
where: {
[QUESTION_HOUR_SLOTS.COLUMNS.TIME_ID]: timeId,
},
},
],
},
],
},
],
});
}

Unfortunately, I should use Sequelize.literal in where option to add a subquery condition with NOT EXISTS like this:
where: Sequelize.where(Sequelize.literal('NOT EXISTS (SELECT 1 FROM ... WHERE startDate <= #date and endDate >= #date)'), '=', true)
and also to pass a specified date you add to indicate bind option along with main query options like this:
where: {
[USERS.COLUMNS.QH_STATUS]: true,
},
bind: {
date
}

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 ?

Sequelize querying both parent model and included model using [Op.or]

I want to findAll Bookings where the booking has been paid.
A legacy booking has been paid if the paymentAuthorised: boolean attribute on the Booking table is true.
A new booking has been paid if the paymentAuthorised: boolean attribute on the Payment table is true and type: string attribute on the Payment table is 'booking'.
When I perform the following query it returns an error saying payments.paymentAuthorised not found.
const bookings = await db.Booking.findAll({
include: [
{
model: db.Payment,
as: "payments",
},
],
where: {
[Op.or]: [
{
paymentAuthorised: { [Op.eq]: true },
},
{
"$payments.paymentAuthorised$": { [Op.eq]: true },
"$payments.type$": { [Op.eq]: "booking" },
},
],
},
order: [["dateTime", "asc"]],
});
I worked this issue out in the end by logging the generated SQL. You need to add the following parameter: subQuery: false. This is so it generates an SQL query which includes the joins before the where.
const bookings = await db.Booking.findAll({
include: [
{
model: db.Payment,
as: "payments",
},
],
where: {
[Op.or]: [
{
paymentAuthorised: { [Op.eq]: true },
},
{
"$payments.paymentAuthorised$": { [Op.eq]: true },
"$payments.type$": { [Op.eq]: "booking" },
},
],
},
subQuery: false,
order: [["dateTime", "asc"]],
});

Why does sequelize add the COUNT of each model I include together?

I'm trying to get the information about a user and include all the reservation he has done as well as the total sales.
I'm trying to add a before get hook and include both reservations and sales. When I include just one of these it gets the COUNT right, but when I add both, it actually counts reservations and sales and adds them together for each one.
get: [
authenticate("jwt"),
context => {
context.params.sequelize = {
include: [
{
association: "reservacion",
required: false,
attributes: [[Sequelize.fn("COUNT", Sequelize.col("reservacion.id")), "total_res"]]
},
{
association: "venta",
required: false,
attributes: [[Sequelize.fn("COUNT", Sequelize.col("venta.id")), "ventas"]]
}
]
};
return context;
}
],
If you use DISTINCT you should avoid the problem of accumulation of results
get: [
authenticate("jwt"),
context => {
context.params.sequelize = {
include: [
{
association: "reservacion",
required: false,
attributes: [[Sequelize.fn("COUNT", Sequelize.fn("DISTINCT", Sequelize.col("reservacion.id")), "total_res"]]
},
{
association: "venta",
required: false,
attributes: [[Sequelize.fn("COUNT", Sequelize.fn("DISTINCT", Sequelize.col("venta.id"))), "ventas"]]
}
]
};
return context;
}
],

Return fields from different collection Mongoose

Imagine a function that finds users by their name and returns them.
User.aggregate(
[
{ $sort: { userFirstName: 1, userLastName: 1 } },
{
$addFields: {
firstLastName: { $concat: ['$userFirstName', ' ', '$userLastName'] },
lastFirstName: { $concat: ['$userLastName', ' ', '$userFirstName'] }
}
},
{
$match: $match // Set from above with match crit
},
{
$group: {
_id: null,
total: { $sum: 1 },
data: {
$push: {
'_id': '$_id',
'userFirstName': '$userFirstName',
'userLastName': '$userLastName',
'userProfileImage': '$userProfileImage',
'userVihorCategory': '$userVihorCategory'
}
}
}
},
{
$project: {
total: 1,
data: { $slice: ['$data', start, limit] }
}
}
]
).exec((errAgg, results) => {...
This works, it splices them and returns them correctly.
There is another collection that tracks user connections.
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
userConnection: { type: Schema.Types.ObjectId, ref: 'User' },
userConnectionStatus: {
type: String,
enum: ['following', 'blocked', 'requested']
}
}
Eg User: me, userConnection: 'someone', userConnectionStatus: 'following'
What I am trying to achive is to return 2 more fields,
1. My userConnectionStatus to him
2. His userConnectionStatus to me
And not to return users who have blocked me.
What is the best approach when it comes to this DB structure.
Thank you for your time
Preventing blocked users was solved by selecting all blocked users, and adding $nin in match inside aggregate.
For connection status, I have resolved the problem by adding 2 virtual fields to User.
UserMongoSchema.virtual('userConnectionStatus', {
ref: 'UserConnection',
localField: '_id',
foreignField: 'user',
justOne: true
});
UserMongoSchema.virtual('connectionStatus', {
ref: 'UserConnection',
localField: '_id',
foreignField: 'userConnection',
justOne: true
});
And populating them on results
...
.exec((errAgg, results) => {
User.populate(results[0].data, [
{ path: 'userConnectionStatus', match: { userConnection: req.userCode }, select: 'userConnectionStatus' },
{ path: 'connectionStatus', match: { user: req.userCode }, select: 'userConnectionStatus' },
], (errPop, populateResponse) => {
if (errPop) { return next(errPop); }
populateResponse = populateResponse.map((row) => {
row['userConnectionStatus'] = row.userConnectionStatus ? row.userConnectionStatus.userConnectionStatus : null;
row['connectionStatus'] = row.connectionStatus ? row.connectionStatus.userConnectionStatus : null;
return row;
});
...
Looking at the order of actions, I think this won't affect performance since I am running populate only on those matched top X (max 100) results.
I won't mark this as Answer yet. If you have any opinion about if this is bad practice or if there is a better way of doing it, feel free to comment.

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