'Sequelize js how to get average (aggregate) of associated model

I am trying to get average rating of an associated Model "Rating" of Model "User" using sequelize.

sequelize.sync({logging: false}).then(()=>{
  return Model.Rating.findAll({
    attributes: [[Sequelize.fn('avg', Sequelize.col('stars')),'rating']]
  })
}).then(res => {
  res = res.map(r => r.get())
  console.log(res);
})

I get correct response when trying directly from "Rating" Model:

[ { rating: '3.5000000000000000' } ]

However, when trying to do the same through the association of "User", I get separate values instead of getting average.

sequelize.sync({logging: false}).then(()=>{
  return Model.User.findOne({
    where: {id: 7},
    include : [{
      model: Model.Rating, as: 'seller_rating',
      attributes: [[Sequelize.fn('avg', Sequelize.col('stars')),'rating']]
    }],
    attributes: {
      exclude: ['password']
    },
    group: ['seller_rating.id', 'user.id'],
  })
}).then(res => {
  res = res.get()
  res.seller_rating = res.seller_rating.map(r => r.get())
  console.log(res)
})

I had to add "seller_rating.id" and "user.id" in group as sequelize was throwing error otherwise.

{
  id: 7,
  email: '[email protected]',
  createdAt: 2020-01-20T09:07:47.101Z,
  updatedAt: 2020-01-21T08:58:52.036Z,
  seller_rating: [
    { rating: '4.0000000000000000' },
    { rating: '3.0000000000000000' }
  ]
}

Following are the models for User and Rating User:

let User = sequelize.define('user', {
    email: {type: Sequelize.STRING, unique: true, allowNull: false},
    password : {type: Sequelize.STRING, },
})

Rating:

let Rating = sequelize.define('rating', {
    seller_id: {
        type: Sequelize.INTEGER,
        references: {model: User, key: 'id'},
        unique: 'rateObject'
    },
    buyer_id: {
        type: Sequelize.INTEGER,
        references: {model: User, key: 'id'},
        unique: 'rateObject'
    },
    stars: {
        type: Sequelize.INTEGER,
        validate: {
            min: 1,
            max: 5
        },
        allowNull: false
    }
})

Rating.belongsTo(User,{ onDelete: 'cascade', foreignKey: 'seller_id'})
Rating.belongsTo(User,{ onDelete: 'cascade', foreignKey: 'buyer_id'})
User.hasMany(Rating, { foreignKey: 'seller_id', as: 'seller_rating'})
User.hasMany(Rating, { foreignKey: 'buyer_id', as: 'buyer_rating'})


Solution 1:[1]

This solution should work for you:

Model.User.findOne({
  where: { id: 7 },
  attributes: [
    [Sequelize.fn('AVG', Sequelize.col('seller_rating.stars')), 'avgRating'],
  ],
  include: [
    {
      model: Model.Rating,
      as: 'seller_rating',
      attributes: [],
    },
  ],
  raw: true,
  group: ['User.id'],
}).then((res) => console.log(res));

Solution 2:[2]

Solution that worked for me:

const product = await Product.findOne({
  where: {id: 1},
  include: [
    {
      model: Rating, //including ratings array
      as: 'ratings',
      attributes: [], //but making it empty
    },
  ],
  attributes: {
    include: [ // this adds AVG attribute to others instead of rewriting
      [sequelize.fn('AVG', sequelize.col('ratings.rating')), 'avgRating'],
    ],
  },
  group: ['Product.id'],
});

result:

{
  "id": 1,
  "name": "product",
  "price": 50,
  "createdAt": "2022-04-21T11:32:56.666Z",
  "updatedAt": "2022-04-21T11:32:56.666Z",
  "categoryId": 1,
  "avgRating": "2.7500000000000000"
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Adrian Mole
Solution 2 Nickers