在 Eager Loading 中计算 AVG 时 Sequelize 返回一个结果
Sequelize returning one result when counting AVG in Eager Loading
我已经尝试解决这个问题一天了,但没有成功。我正在使用 Sequelize With Nodejs 和 MySQL 方言。
我正在查询有影响力的人,同时还根据 InfluencerRating 记录计算他们的平均评分。它们通过 oneToMany 关系关联。
这是我的影响者模态:
const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('influencer', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
display_name: {
type: DataTypes.STRING(255),
allowNull: false
},
link_facebook: {
type: DataTypes.STRING(255),
allowNull: true
},
link_soundcloud: {
type: DataTypes.STRING(255),
allowNull: true
},
link_twitter: {
type: DataTypes.STRING(255),
allowNull: true
},
link_bandcamp: {
type: DataTypes.STRING(255),
allowNull: true
},
link_spotify: {
type: DataTypes.STRING(255),
allowNull: true
},
link_instagram: {
type: DataTypes.STRING(255),
allowNull: true
},
link_deezer: {
type: DataTypes.STRING(255),
allowNull: true
},
link_youtube: {
type: DataTypes.STRING(255),
allowNull: true
},
link_website: {
type: DataTypes.STRING(255),
allowNull: true
},
description_english: {
type: DataTypes.STRING(255),
allowNull: true
},
description_french: {
type: DataTypes.STRING(255),
allowNull: true
},
information_english: {
type: DataTypes.STRING(255),
allowNull: true
},
information_french: {
type: DataTypes.STRING(255),
allowNull: true
},
enabled: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: 0
},
profile_image: {
type: DataTypes.STRING(255),
allowNull: true
},
banner_image: {
type: DataTypes.STRING(255),
allowNull: true
},
admin_public_status: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: 1
}
}, {
sequelize,
tableName: 'influencer',
timestamps: false,
underscored: true,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "id" },
]
},
]
});
};
这是我的 InfluencerRating 模态:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('influencer_rating', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
description: {
type: DataTypes.STRING(255),
allowNull: false
},
influencer_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'influencer',
key: 'id'
}
},
rating: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
sequelize,
tableName: 'influencer_rating',
timestamps: false,
underscored: true,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "id" },
]
},
{
name: "type_id",
using: "BTREE",
fields: [
{ name: "influencer_id" },
]
},
]
});
};
这是我对它们的联想:
influencer_rating.belongsTo(influencer, { as: "influencer", foreignKey: "influencer_id"});
influencer.hasMany(influencer_rating, { as: "influencer_ratings", foreignKey: "influencer_id"});
这是我寻找有影响力的人并计算他们的平均评分的查询:
var influencers = await Influencer.findAll(
{
where: {
[db.Op.and]: [
{
enabled: true
},
{
display_name: {
[db.Op.like]: '%' + filter + '%'
}
},
{
admin_public_status: true
}
],
},
include: [
[
{
model: InfluencerRating,
as: "influencer_ratings",
required: false,
attributes: [[Sequelize.fn('AVG', Sequelize.col('rating')), 'rating']]
}
],
limit: 10,
offset: 0,
group: ['id']
}
);
查询应该返回 2 个影响者对象,但我只得到一个。
如果我删除我的 :
,我会得到想要的行为
attributes: [[Sequelize.fn('AVG', Sequelize.col('rating')), 'rating']]
我已经尝试计算我在 InfluencerRating 之外的平均值,但不包括运气。
对 findAll 进行了一些更改。可以试试看:
var influencers = await Influencer.findAll({
// whenever hasMany used in include, the main query gets converted to subquery
// if limit + offset is applied
subQuery: false, // <----- instructs not to make a sub query
attributes: {
include: [
[Sequelize.fn('AVG', Sequelize.col('influencer_ratings.rating')), 'total_rating']
],
},
where: {
[db.Op.and]: [
{ enabled: true },
{ display_name: { [db.Op.like]: '%' + filter + '%' } },
{ admin_public_status: true }
],
},
include: [ // removed extra nesting of array
{
model: InfluencerRating,
as: "influencer_ratings",
required: false,
attributes: [],
}
],
limit: 10,
offset: 0,
group: [
'id',
// 'influencer_ratings.influencer_id', // add if sql error occurs
],
order: [ // if needed
[Sequelize.literal('total_rating'), 'DESC'],
]
});
几个参考链接:
- 当包含 hasMany/belongsToMany 关系时禁用子查询生成(由 sequelize 完成)- https://github.com/sequelize/sequelize/issues/1756#issuecomment-54748245
- 使用带排序的 SQL 子查询:https://sequelize.org/master/manual/sub-queries.html
替代方法:
var influencers = await Influencer.findAll({
attributes: {
include: [
[Sequelize.literal(`(
SELECT
AVG("influencer_ratings"."rating") AS "avg_rating"
FROM
"influencer" AS "influencer1"
LEFT OUTER JOIN (
"influencer_ratings" AS "influencer_ratings"
) ON "influencer1"."id" = "influencer_ratings"."influencer_id"
WHERE
"influencer1"."id" = "influencer"."id"
GROUP BY
"influencer1"."id"
)`), 'avg_rating'], // now this is just a subquery and will not interfere with other includes
],
},
where: {
[db.Op.and]: [
{ enabled: true },
{ display_name: { [db.Op.like]: '%' + filter + '%' } },
{ admin_public_status: true }
],
},
include: [ // removed extra nesting of array
// influencer_ratings include not needed now
// add other includes as needed
],
limit: 10,
offset: 0,
order: [ // if needed
[Sequelize.literal('avg_rating'), 'DESC'],
]
});
我已经尝试解决这个问题一天了,但没有成功。我正在使用 Sequelize With Nodejs 和 MySQL 方言。
我正在查询有影响力的人,同时还根据 InfluencerRating 记录计算他们的平均评分。它们通过 oneToMany 关系关联。
这是我的影响者模态:
const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
return sequelize.define('influencer', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
display_name: {
type: DataTypes.STRING(255),
allowNull: false
},
link_facebook: {
type: DataTypes.STRING(255),
allowNull: true
},
link_soundcloud: {
type: DataTypes.STRING(255),
allowNull: true
},
link_twitter: {
type: DataTypes.STRING(255),
allowNull: true
},
link_bandcamp: {
type: DataTypes.STRING(255),
allowNull: true
},
link_spotify: {
type: DataTypes.STRING(255),
allowNull: true
},
link_instagram: {
type: DataTypes.STRING(255),
allowNull: true
},
link_deezer: {
type: DataTypes.STRING(255),
allowNull: true
},
link_youtube: {
type: DataTypes.STRING(255),
allowNull: true
},
link_website: {
type: DataTypes.STRING(255),
allowNull: true
},
description_english: {
type: DataTypes.STRING(255),
allowNull: true
},
description_french: {
type: DataTypes.STRING(255),
allowNull: true
},
information_english: {
type: DataTypes.STRING(255),
allowNull: true
},
information_french: {
type: DataTypes.STRING(255),
allowNull: true
},
enabled: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: 0
},
profile_image: {
type: DataTypes.STRING(255),
allowNull: true
},
banner_image: {
type: DataTypes.STRING(255),
allowNull: true
},
admin_public_status: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: 1
}
}, {
sequelize,
tableName: 'influencer',
timestamps: false,
underscored: true,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "id" },
]
},
]
});
};
这是我的 InfluencerRating 模态:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('influencer_rating', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
description: {
type: DataTypes.STRING(255),
allowNull: false
},
influencer_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'influencer',
key: 'id'
}
},
rating: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
sequelize,
tableName: 'influencer_rating',
timestamps: false,
underscored: true,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "id" },
]
},
{
name: "type_id",
using: "BTREE",
fields: [
{ name: "influencer_id" },
]
},
]
});
};
这是我对它们的联想:
influencer_rating.belongsTo(influencer, { as: "influencer", foreignKey: "influencer_id"});
influencer.hasMany(influencer_rating, { as: "influencer_ratings", foreignKey: "influencer_id"});
这是我寻找有影响力的人并计算他们的平均评分的查询:
var influencers = await Influencer.findAll(
{
where: {
[db.Op.and]: [
{
enabled: true
},
{
display_name: {
[db.Op.like]: '%' + filter + '%'
}
},
{
admin_public_status: true
}
],
},
include: [
[
{
model: InfluencerRating,
as: "influencer_ratings",
required: false,
attributes: [[Sequelize.fn('AVG', Sequelize.col('rating')), 'rating']]
}
],
limit: 10,
offset: 0,
group: ['id']
}
);
查询应该返回 2 个影响者对象,但我只得到一个。
如果我删除我的 :
,我会得到想要的行为attributes: [[Sequelize.fn('AVG', Sequelize.col('rating')), 'rating']]
我已经尝试计算我在 InfluencerRating 之外的平均值,但不包括运气。
对 findAll 进行了一些更改。可以试试看:
var influencers = await Influencer.findAll({
// whenever hasMany used in include, the main query gets converted to subquery
// if limit + offset is applied
subQuery: false, // <----- instructs not to make a sub query
attributes: {
include: [
[Sequelize.fn('AVG', Sequelize.col('influencer_ratings.rating')), 'total_rating']
],
},
where: {
[db.Op.and]: [
{ enabled: true },
{ display_name: { [db.Op.like]: '%' + filter + '%' } },
{ admin_public_status: true }
],
},
include: [ // removed extra nesting of array
{
model: InfluencerRating,
as: "influencer_ratings",
required: false,
attributes: [],
}
],
limit: 10,
offset: 0,
group: [
'id',
// 'influencer_ratings.influencer_id', // add if sql error occurs
],
order: [ // if needed
[Sequelize.literal('total_rating'), 'DESC'],
]
});
几个参考链接:
- 当包含 hasMany/belongsToMany 关系时禁用子查询生成(由 sequelize 完成)- https://github.com/sequelize/sequelize/issues/1756#issuecomment-54748245
- 使用带排序的 SQL 子查询:https://sequelize.org/master/manual/sub-queries.html
替代方法:
var influencers = await Influencer.findAll({
attributes: {
include: [
[Sequelize.literal(`(
SELECT
AVG("influencer_ratings"."rating") AS "avg_rating"
FROM
"influencer" AS "influencer1"
LEFT OUTER JOIN (
"influencer_ratings" AS "influencer_ratings"
) ON "influencer1"."id" = "influencer_ratings"."influencer_id"
WHERE
"influencer1"."id" = "influencer"."id"
GROUP BY
"influencer1"."id"
)`), 'avg_rating'], // now this is just a subquery and will not interfere with other includes
],
},
where: {
[db.Op.and]: [
{ enabled: true },
{ display_name: { [db.Op.like]: '%' + filter + '%' } },
{ admin_public_status: true }
],
},
include: [ // removed extra nesting of array
// influencer_ratings include not needed now
// add other includes as needed
],
limit: 10,
offset: 0,
order: [ // if needed
[Sequelize.literal('avg_rating'), 'DESC'],
]
});