Sequelize - ORDER BY count of associations - node.js

I have two models, Company and Employee. The association is like Company hasMany Employee. Employee belongsTo Company.
I have to list all the Companies ordered by count of associations it have in the descending order.
My function is like following,
function get() {
return Company.findAndCountAll({
where: { status: Active },
include: [
{
model: Employee,
as: 'employees'
}
],
}).then((companies) => {
return companies
})
}
What can I give in order so that I can get list of companies based on count of associations(Employees) in descending order.
Please help me out in this...
I have tried as below. but it is throwing error like invalid reference to FROM-clause entry for table companies.
function get() {
return Company.findAndCountAll({
where: { status: Active },
include: [
{
model: Employee,
as: 'employees'
}
],
attributes: [
[Sequelize.literal('(SELECT COUNT(*) FROM employees WHERE employees.company_id = companies.id)'), 'employeesCount']
],
order: [[Sequelize.literal('employeesCount'), 'DESC']],
}).then((companies) => {
return companies
})
}
where companies is the table name.

You can try this.
Please replace your fieldName with sortField
function get() {
return Company.findAndCountAll({
where: { status: Active },
include: [
{
model: Employee,
as: 'employees'
}
],
order: [[sortField, 'DESC']]
}).then((companies) => {
return companies
})
}

Related

sequelize join up the tables

Working on an already setup db and also new to sequelize. I have like four tables
customer
library
books
excerpt
excerpt has redundant book_id from books. books has redundant library_id from library, and library has redundant customer_id from customer.
They were not declared foreign in db but its kinda acting the foreign key type and I make the association when I hit the orm functions.
My question:
I have a customer_id and book_id, and I have to fetch excerpt records based on book_id but have to go top the db to match the customer_id as well. (for multi tenancy)
the flow is: excerpt> book_id - books > library_id - library > customer_id - customer
I have written this code but its not working
async read_book_id(customer_id, book_id) {
const excerpts = await this.model.findAll({ // this.model being the excerpt model
where: { book_id: book_id},
include: [
{
model: this.db.Books,
association:
this.model.belongsTo(this.db.Books, {
foreignKey: 'book_id',
}),
where: { book_id: book_id},
include: [
{
model: this.db.Library,
association: this.model.belongsTo(this.db.Library, {
foreignKey: 'library_id',
}),
where: { customer_id: customer_id },
},
],
},
],
});
Basically this is something extended from this another code i wrote which is working for me. If I have to check only one level above thats working fine for me e.g
// reading books based on library_id
async read(customer_id, library_id) {
const books= await this.model.findAll({
where: {
library_id: library_id,
},
include: [
{
model: this.db.Library,
association: this.model.belongsTo(this.db.Library, {
foreignKey: 'library_id',
}),
where: { customer_id: customer_id },
},
],
});
}
This works fine for me.
Can you please tell how to run the first code block?
Assuming you already registered all associations just like I suggested in the comments above you need to indicate the correct models in include options along with correct conditions:
const excerpts = await this.model.findAll({ // this.model being the excerpt model
where: { book_id: book_id},
include: [
{
model: this.db.Books,
include: [
{
model: this.db.Library,
where: { customer_id: customer_id },
required: true
},
],
},
],
});```

Sequelize join two tables on id

I have a table of book users and a table of movie users. I'm trying to return a list of the top 100 movie viewers, along with their book profile information. I want to join on ids, but I can't seem to find the right syntax.
This is what I've tried:
const mostActiveMovieWatchers = await MovieWatchers.findAll({
order: [
['moviesWatched', 'DESC'],
],
limit: '100',
include: [{
model: BookReaders,
where: {
userId: '$MovieWatchers.id$'
},
required: true
}]
});
I've also seen examples where the where clause looks something like this where: ['userId = id']
Before join tables you need create association:
BookReaders.hasMany(MovieWatchers, { foreignKey: 'bookId' });
MovieWatchers.belongsTo(BookReaders, { foreignKey: 'bookId' });
Then, you can use the include option in a find or findAll method call on the MovieWatchers model to specify that you want to include the associated BookReaders data:
MovieWatchers.findAll({
include: [
{
model: BookReaders,
},
],
}).then((movies) => {
// array of movies including books
});

how to set order on include aggregate function alias in sequelize?

Below is my code
export async function getMany(page: number, recordsPerPage: number, condition: any = {}, order: any, attributes: string[] = [], other: object = {}) {
try {
let { count, rows }: any = await User.findAndCountAll({
attributes: {
include: [[sequelize.literal('(SELECT SUM(reputation) FROM scores where scores.user_id = User.id)'), 'reputation']],
exclude: attributes,
},
where: condition,
distinct: true,
include: [
{
model: Skill,
as: 'skills',
attributes: ['skill'],
through: { attributes: [] },
},
],
order: order,
offset: page,
limit: recordsPerPage,
...other,
logging: console.log,
});
return { count, rows };
} catch (e) {
return false;
}
}
I want to set order by reputation field which is alias of sum function column. i want my data in highest to lowest reputation.
You can simply replace the order property with the given snippet. Worked for me.
order: [[sequelize.literal('table alias name goes here'), 'DESC']]

Get the count of Associated tables using sequelize

In my Case Account is a parent table Account that has many Members, an Account has many Groups, Account has many Roles. So I tried getting a count of each table as follows.
Account.findAll({
subQuery: false,
where: {id: id},
attributes: {
include: ['*',
[Sequelize.fn("COUNT", Sequelize.col("Members.id")), "usersCount"],
[Sequelize.fn("COUNT", Sequelize.col("Groups.id")), "groupsCount"],
[Sequelize.fn("COUNT", Sequelize.col("Roles.id")), "rolesCount"]]
},
include: [
{
model: Member, attributes: [], where: {accountId: id}
},
{
model: Group, attributes: [], where: {accountId: id}
},
{
model: Role, attributes: [], where: {accountId: id}
}
],
group: ['Account.id']
})
.then(data => {
let result = data.map(item => accountObj(item));
return next(null, result[0]);
})
.catch(err => dbHelper.handleError(err, next));
It's giving me an incorrect count. Also tried various other options I found on stack overflow. But not found any solution for the above query.
You either need to separate this query into three ones to count each child model record count separately OR remove include and group options and replace Sequelize.fn with Sequelize.literal with subqueries to count child records.
Account.findAll({
where: {id: id},
attributes: {
include: ['*',
[Sequelize.literal("(SELECT COUNT(*) from members as m where m.account_id=account.id)", "usersCount"],
[Sequelize.literal("(SELECT COUNT(*) from groups as g where g.account_id=account.id)", "groupsCount"],
[Sequelize.literal("(SELECT COUNT(*) from roles as r where r.account_id=account.id)", "rolesCount"],
]
}
})

Get all associated records given one match

I have two models with a one to many relation. Given that below query returns a record of ModelA (lets say it has id 1) with 3 associated ModelB (1, 2 and 3).
If I were to replace [1,2,3] in the query to just [1] it would still return the same ModelA record (with id 1) but only with the one associated ModelB (of id 1). How can I modify this query so it returns all three associated ModelB records?
ModelA.findAll({
include: [{
model: JoinTableModel,
where: {
modelB_ID: {
[Op.in]: [1,2,3]
}
},
include: [ModelB]
}]
})
Model definitions like so.
db.ModelA.hasMany(db.JoinTableModel, { foreignKey: 'modelA_ID' })
db.JoinTableModel.belongsTo(db.ModelA, { foreignKey: 'modelA_ID' })
db.ModelB.hasMany(db.JoinTableModel, { foreignKey: 'modelB_ID' })
db.JoinTableModel.belongsTo(db.ModelB, { foreignKey: 'modelB_ID' })
You can try something like this:
JoinTableModel.findAll({
where: {
modelB_ID: {
[Op.in]: [1]
}
},
include: [{
model: ModelA,
include: [{
model: JoinTableModel,
include: [ModelB]
}]
}]
})
If you add an association like this:
ModelA.belongsToMany(ModelB, { through: JoinTableModel })
then you can simplify the above query to this one:
JoinTableModel.findAll({
where: {
modelB_ID: {
[Op.in]: [1]
}
},
include: [{
model: ModelA,
include: [ModelB]
}]
})
For anyone with a similar issue as me, what I ended up doing that works for my case is modifying my initial query like this.
ModelA.findAll({
where: {
id: sequelize.literal(`
ModelA.id
IN (
SELECT modelA_ID FROM JoinTableModel
WHERE modelB_ID IN (1,2,3)
GROUP BY modelA_ID
HAVING COUNT(*) = 3
)`)
},
include: [{
model: JoinTableModel,
include: [ModelB]
}]
})
This way I could easily replace IN (1,2,3) and COUNT(*) 3 with IN (1) and COUNT(*) 1 and it would still work as intended and it doesn't break any other part of the query.
I'm still curious if anyone could solve this without using sequelize.literal in any way or if there is a more efficient way of doing it.

Resources