aggregate function in sequelize - node.js

i need a query which has aggregate function in include. i want to get count of ticket sell in each events
i tried code below
const data = await Items.paginate({
page: offset.value,
paginate: limit.value,
where: itemType,
include:[
{model:ItemTypes},
{model:ItemPayment,
attributes: [[db.sequelize.fn('sum', db.sequelize.col('ItemPayments.numberOfTickets')), 'total']],
group: ["ItemPayments.itemId"],
}],
raw:true
});
but it gives me an error like below
In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'Items.id'; this is incompatible with sql_mode=only_full_group_by

Add separate : true and try to execute the query again
{
model:ItemPayment,
attributes: [[db.sequelize.fn('sum', db.sequelize.col('ItemPayments.numberOfTickets')), 'total']],
group: ["ItemPayments.itemId"],
separate : true // <--------- HERE
}

Related

How to write a count like this in Sequelize?

SELECT COUNT(id), age FROM `red_cross_volunteers` GROUP BY age;
Simply use Sequelize.fn in attributes option and group option while calling findAll:
const stats = await RedCrossVolunteers.findAll({
attributes: [[Sequelize.fn('COUNT', Sequelize.col('id')), 'age_count'], 'age'],
group: ['age']
})

can I pass an argument to a virtual schema populate in mongoose 5.7.12?

I want to pass an argument to the virtual populate a define in my schema to match a query based on the argument passed, my current code is :
PostSchema.virtual('liked_by_me',{
ref:'likes',
localField:'likes',
foreignField:'_id',
justOne:false,
count: true,
})
and I am calling it from my controller :
PostModel.find().limit(limit).
skip(page*limit).
sort(
{created_at:-1}
)
.populate('created_by_user','nickname -_id')
.populate('likes_count')
.populate({
path:'liked_by_me',
match: { user: req.user._id },
count: true
}).exec()
but instead is there a way to pass the user_id to the liked_by_me populate and query based on the
user id inside the schema?.

And condition on Join table on the same field multiple times in Sequelize NodeJS?

I have one table called Users. One user can be present on multiple Email Group.
User Table
id, name
1, Sudhir Roy
2, Rahul Singh
Email Group Table
id, emailType, userID
1, Promotional, 1
2, Advertisement, 2
3, Advertisement, 1
Need to get all users based on Email Type. (Email type is dynamic and send in array form like ["Promotional", "Advertisement"]
How can we find users based on the emailGroup.
["Advertisement"] return [1, 2] from user table
["Advertisement", "Promotional"] return [1] from user table.
I tried to use [Op.and] and [Op.in]. Both are not working.
Here is my sample code.
const user = User.findAll({
where: {
"$emailGroup.emailType$": {
[Op.in]: emailGroup // Array from client
},
},
include: {
model: EmailGroup
}
})
It works well when the email group array is single but not working when we try to find more than one email group.
You can also add where clauses to the include object.
The resulting findAll would then become:
const user = User.findAll({
include: [
{
model: EmailGroup,
where: {
emailType$: {
[Op.in]: emailGroup, // Array from client
},
},
},
],
});
As shown here in the docs: https://sequelize.org/v5/manual/querying.html#relations---associations

Joining same table multiple times with Sequelize

I have the following models:
const User = Sequelize.define('user', {
login: Sequelize.DataTypes.STRING,
password: Sequelize.DataTypes.STRING,
is_manager: Sequelize.DataTypes.BOOLEAN,
notes: Sequelize.DataTypes.STRING
});
const Bike = Sequelize.define('bike', {
model: Sequelize.DataTypes.STRING,
photo: Sequelize.DataTypes.BLOB,
color: Sequelize.DataTypes.STRING,
weight: Sequelize.DataTypes.FLOAT,
location: Sequelize.DataTypes.STRING,
is_available: Sequelize.DataTypes.BOOLEAN
});
const Rate = Sequelize.define('rate', {
rate: Sequelize.DataTypes.INTEGER
});
Rate.belongsTo(User);
User.hasMany(Rate);
Rate.belongsTo(Bike);
Bike.hasMany(Rate);
And I'd like to select bikes with their average rates, plus rates of the current user for each bike:
Bike.findAll({
attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
},
include: [{
model: Rate,
attributes: []
}, {
model: Rate,
attributes: ['rate'],
include: [{
model: User,
attributes: [],
where: {
login: req.user.login
}
}]
}],
group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
})
It constructs the following query: SELECT [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available], AVG([rates].[rate]) AS [rate_avg], [rates].[id] AS [rates.id], [rates].[rate] AS [rates.rate] FROM [bikes] AS [bike] LEFT OUTER JOIN [rates] AS [rates] ON [bike].[id] = [rates].[bikeId] LEFT OUTER JOIN ( [rates] AS [rates] INNER JOIN [users] AS [rates->user] ON [rates].[userId] = [rates->user].[id] AND [rates->user].[login] = N'user' ) ON [bike].[id] = [rates].[bikeId] GROUP BY [bike].[id], [bike].[model], [bike].[photo], [bike].[color], [bike].[weight], [bike].[location], [bike].[is_available];
And fails: SequelizeDatabaseError: The correlation name 'rates' is specified multiple times in a FROM clause.
How do I write the query right? I need Sequelize to assign another alias to the rates table used in the 2nd join (and add its columns to the GROUP BY clause, but that's the next step).
You can do multiple inner joins with same table by adding extra same association with that model but with a different alias that is as: 'alias1' , as: 'alias2' ,... - all this existing with the same model + same type of association.
Also posted this solution at github issue: https://github.com/sequelize/sequelize/issues/7754#issuecomment-783404779
E.g. for Chats that have many Receiver
Associations (Duplicating for as many needed)
Chat.hasMany(Receiver, {
// foreignKey: ...
as: 'chatReceiver',
});
Chat.hasMany(Receiver, {
// foreignKey: ...
as: 'chatReceiver2',
});
Now you are left to include associated model multiple times all with different alias so it does not gets overridden.
So you can use them in query as below:
Chat.findAll({
attributes: ["id"],
include: [{
required: true,
model: Receiver,
as: 'chatReceiver', // Alias 1
attributes: [],
where: { userID: 1 }, // condition 1
}, {
required: true,
model: Receiver,
as: 'chatReceiver2', // Alias 2
attributes: [],
where: { userID: 2 }, // condition 2 as needed
}]
});
Solution :
Bike.findAll({
attributes: {include: [[Sequelize.fn('AVG', Sequelize.col('rates.rate')), 'rate_avg']],
},
include: [{
model: Rate,
attributes: []
}, {
model: Rate,
required : false , // 1. just to make sure not making inner join
separate : true , // 2. will run query separately , so your issue will be solved of multiple times
attributes: ['rate'],
include: [{
model: User,
attributes: [],
where: {
login: req.user.login
}
}]
group : [] // 3. <------- This needs to be managed , so please check errors and add fields as per error
}],
group: Object.keys(Bike.rawAttributes).map(key => 'bike.' + key) // group by all fields of Bike model
})
NOTE : READ THE COMMENTS
Sequelize doesn't support including through the same association twice (see here, here, and here). At the model level, you can define 2 different associations between Bike and Rate, but having to change the model, adding new foreign keys etc, is a very hacky solution.
Incidentally, it wouldn't solve your other problem, which is that you're grouping only by Bike but then want to select the user's rate. To fix that, you'd also have to change your grouping to include the user rates. (Note that if a user has more than 1 rate per bike, that might also create some inefficiency, as the rates for the bike are averaged repeatedly for each of the user's rates.)
A proper solution would be using window functions, first averaging the rates per bike and then filtering out all the rates not belonging to the logged in user. Might look something like this:
SELECT *
FROM (
SELECT bike.*,
users.login AS user_login,
AVG (rates.rate) OVER (PARTITION BY bike.id) AS rate_avg
FROM bike
INNER JOIN rates ON rates.bikeId = bike.id
INNER JOIN users ON rates.userId = users.id
)
WHERE user_login = :req_user_login
Unfortunately, as far as I'm aware sequelize doesn't currently support subqueries in the FROM clause and using window functions in this way, so you'd have to fall back to a raw query.

Sequelize - Join with multiple column

I like to convert the following query into sequelize code
select * from table_a
inner join table_b
on table_a.column_1 = table_b.column_1
and table_a.column_2 = table_b.column_2
I have tried many approaches and followed many provided solution but I am unable to achieve the desired query from sequelize code.
The max I achieve is following :
select * from table_a
inner join table_b
on table_a.column_1 = table_b.column_1
I want the second condition also.
and table_a.column_2 = table_b.column_2
any proper way to achieve it?
You need to define your own on clause of the JOIN statement
ModelA.findAll({
include: [
{
model: ModelB,
on: {
col1: sequelize.where(sequelize.col("ModelA.col1"), "=", sequelize.col("ModelB.col1")),
col2: sequelize.where(sequelize.col("ModelA.col2"), "=", sequelize.col("ModelB.col2"))
},
attributes: [] // empty array means that no column from ModelB will be returned
}
]
}).then((modelAInstances) => {
// result...
});
Regarding #TophatGordon 's doubt in accepted answer's comment: that if we need to have any associations set up in model or not.
Also went through the github issue raised back in 2012 that is still in open state.
So I was also in the same situation and trying to setup my own ON condition for left outer join.
When I directly tried to use the on: {...} inside the Table1.findAll(...include Table2 with ON condition...), it didn't work.
It threw an error:
EagerLoadingError [SequelizeEagerLoadingError]: Table2 is not associated to Table1!
My use case was to match two non-primary-key columns from Table1 to two columns in Table2 in left outer join. I will show how and what I acheived:
Don't get confused by table names and column names, as I had to change them from the original ones that I used.
SO I had to create an association in Table1(Task) like:
Task.associate = (models) => {
Task.hasOne(models.SubTask, {
foreignKey: 'someId', // <--- one of the column of table2 - SubTask: not a primary key here in my case; can be primary key also
sourceKey: 'someId', // <--- one of the column of table1 - Task: not a primary key here in my case; can be a primary key also
scope: {
[Op.and]: sequelize.where(sequelize.col("Task.some_id_2"),
// '=',
Op.eq, // or you can use '=',
sequelize.col("subTask.some_id_2")),
},
as: 'subTask',
// no constraints should be applied if sequelize will be creating tables and unique keys are not defined,
//as it throws error of unique constraint
constraints: false,
});
};
So the find query looks like this :
Task.findAll({
where: whereCondition,
// attributes: ['id','name','someId','someId2'],
include: [{
model: SubTask, as: 'subTask', // <-- model name and alias name as defined in association
attributes: [], // if no attributes needed from SubTask - empty array
},
],
});
Resultant query:
One matching condition is taken from [foreignKey] = [sourceKey]
Second matching condition is obtained by sequelize.where(...) used in scope:{...}
select
"Task"."id",
"Task"."name",
"Task"."some_id" as "someId",
"Task"."some_id_2" as "someId2"
from
"task" as "Task"
left outer join "sub_task" as "subTask" on
"Task"."some_id" = "subTask"."some_id"
and "Task"."some_id_2" = "subTask"."some_id_2";
Another approach to achieve same as above to solve issues when using Table1 in include i.e. when Table1 appears as 2nd level table or is included from other table - say Table0
Task.associate = (models) => {
Task.hasOne(models.SubTask, {
foreignKey: 'someId', // <--- one of the column of table2 - SubTask: not a primary key here in my case; can be primary key also
sourceKey: 'someId', // <--- one of the column of table1 - Task: not a primary key here in my case; can be a primary key also
as: 'subTask',
// <-- removed scope -->
// no constraints should be applied if sequelize will be creating tables and unique keys are not defined,
//as it throws error of unique constraint
constraints: false,
});
};
So the find query from Table0 looks like this : Also the foreignKey and sourceKey will not be considered as we will now use custom on: {...}
Table0.findAll({
where: whereCondition,
// attributes: ['id','name','someId','someId2'],
include: {
model: Task, as: 'Table1AliasName', // if association has been defined as alias name
include: [{
model: SubTask, as: 'subTask', // <-- model name and alias name as defined in association
attributes: [], // if no attributes needed from SubTask - empty array
on: {
[Op.and]: [
sequelize.where(
sequelize.col('Table1AliasName_OR_ModelName.some_id'),
Op.eq, // '=',
sequelize.col('Table1AliasName_OR_ModelName->subTask.some_id')
),
sequelize.where(
sequelize.col('Table1AliasName_OR_ModelName.some_id_2'),
Op.eq, // '=',
sequelize.col('Table1AliasName_OR_ModelName->subTask.some_id_2')
),
],
},
}],
}
});
Skip below part if your tables are already created...
Set constraints to false, as if sequelize tries to create the 2nd table(SubTask) it might throw error (DatabaseError [SequelizeDatabaseError]: there is no unique constraint matching given keys for referenced table "task") due to following query:
create table if not exists "sub_task" ("some_id" INTEGER, "some_id_2"
INTEGER references "task" ("some_id") on delete cascade on update
cascade, "data" INTEGER);
If we set constraint: false, it creates this below query instead which will not throw unique constraint error as we are referencing non-primary column:
create table if not exists "sub_task" ("some_id" INTEGER, "some_id_2" INTEGER, "data" INTEGER);

Resources