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

几个参考链接:

替代方法:

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