sequelize count associated table rows - mysql

Using sequelize and mySQL, I have two tables: User and Post.
Relation between two tables is M : N
db.User.belongsToMany(db.Post, { through: "Likes", as: "Liked" });
db.Post.belongsToMany(db.User, { through: "Likes", as: "Likers" });
What I want is getting post with whole likers id and count of whole likers.
I know how to get whole likers like this.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl"],
include: [{
model: User,
as: "Likers",
attributes: ["id"],
through: { attributes: [] },
}]
})
// result
{
"id": 36,
"title": "test",
"imageUrl": "하늘이_1644886996449.jpg",
"Likers": [
{
"id": 13
},
{
"id": 16
}
]
}
And, I also know how to get count of whole likers.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl"],
include: [{
model: User,
as: "Likers",
attributes: [[sequelize.fn("COUNT", "id"), "likersCount"]],
}]
})
// result
{
"id": 36,
"title": "test",
"imageUrl": "하늘이_1644886996449.jpg",
"Likers": [
{
"likersCount": 2
}
]
}
But, I don't know how to get both of them at once.
Check the result when I use both of them.
{
model: User,
as: "Likers",
attributes: ["id", [sequelize.fn("COUNT", "id"), "likersCount"]],
through: { attributes: [] },
}
// result
"Likers": [
{
"id": 13,
"likersCount": 2
}
]
It only shows 1 liker(id: 13)
It must show another liker(id: 16).
What is the problem?

It shows only one because COUNT is an aggregating function and it groups records to count them. So the only way to get both - use a subquery to count records in a junction table while getting records on the other end of M:N relationship.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl",
// you probably need to correct the table and fields names
[Sequelize.literal('(SELECT COUNT(*) FROM Likes where Likes.postId=Post.id)'), 'LikeCount']],
include: [{
model: User,
as: "Likers",
attributes: ["id"],
through: { attributes: [] },
}]
})

Related

getting the values from 3 tables in sequelize using nested includes and sequelize.col as single object

I am new to nodejs as well as sequelize and any kind of ORMs
I wish to get all the values from 3 tables linked together through belongsTo associations
3 tables :
item - [id, itemName, itemCategoryID]
itemCategory - [id, itemCategoryName]
itemRequirement - [id, itemID, quantity, requirementDate, requirementStatusID]
requirementStatus - [id, requirementStatusName]
this is my get api req for getting the item requirements
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{
model: itemCategory,
attributes: [],
},
],
attributes: [
//gets error in this line
[Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",],
//alternatively this line works fine
['itemCategoryID']
],
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
],
})
.then((itemRequirements) => {
console.log(itemRequirements);
res.json(itemRequirements);
});
});
I get error when trying to do a sequelize.col but I am able to get the ID alone if I don't use the sequelize.col in the above code at the mentioned line
code: 'ER_BAD_FIELD_ERROR',
errno: 1054,
sqlState: '42S22',
sqlMessage: "Unknown column 'item.itemCategory.itemCategoryName' in 'field list'",
currently i am getting this if i directly get the id
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"item": {
"itemCategoryID": 1
}
}
]
i wish to get this
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"itemCategoryName":"someCategoryName"
}
]
You should use DB column name in Sequelize.col instead of its field counterpart in a model:
// let's say the model field is itemCategoryName and the column name in a table is item_category_name
Sequelize.col("itemCategory.item_category_name")
To query more than 2 tables using joins in sequelize we will have to use reference the table and column name correctly.
Instead of adding [Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",] as an attribute to the referencing table and to get the response as a single json object without nesting we need to add this [Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"] as the attribute to the table from which you are querying now
below is the edited code which returns json as expected
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{model:itemCategory,attributes:[]},
{model:quantityType,attributes:[]}
],
attributes:[]
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
//actual way of referencing the different tables to get an object without
//nesting
[Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"],
[Sequelize.col("item.quantityType.quantityName"),"quantityTypeName"]
],
})
.then((itemRequirements) => {
console.log(JSON.stringify(itemRequirements,null,2));
res.json(itemRequirements);
});
});
module.exports = router;
output
[
{
"id": 4,
"quantity": 10,
"requiredBy": "2022-02-03T00:00:00.000Z",
"itemName": "choco",
"requirementStatusName": "pending",
"itemCategoryName": "Essence",
"quantityTypeName": "ml"
}
]

Eager load multiple and nested associations

I have three models, A, B and C, where:
A.hasMany(B);
B.belongsTo(A);
B.hasMany(C);
C.belongsTo(B);
I'm querying like this:
await A.findOne({
where: { id: id },
include: [
{
model: B,
},
],
});
How can I return the C objects that belongs to B when querying A?
In the end found the solution in the comments of another question.
I need to pass the include with a array like this [{ all: true, nested: true }], I end up having something like this:
await A.findOne({
where: { id: id },
include: [{ all: true, nested: true }],
});
I didn't tested what happens when it loops and also didn't found the docs about it, if a good soul find it feel free to comment it.
Edit:
Nested includes also works:
await A.findOne({
where: { id: id },
include: [
{
model: B,
include: [
{
model: C,
},
],
},
],
});

Can I get Category with id 1 and all Subcategories of that Category in a single query in Sequelize.js?

I have the following database table:
Table: Categories
Columns: id, name, parent_id
and the following records:
1 / Category1 / 0
2 / Category2 / 0
3 / Subcategory1 / 1
4 / Subcategory2 / 1
So I have 2 Categories - Category1 and Category2 and 2 Subcategories of Category1 - Subcategory1 and Subcategory2.
If the parent_id field is 0, that means the record is a Category and if it is not 0 and has the id of another category, then it is a subcategory of that category.
Right now I'm getting all Categories like this:
Category.findAll({
where: {
'parent_id': 0
}
})
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
but now I also want to somehow include the Subcategories of the Categories as an object property. Right now I'm getting this:
[
{
"id": 1,
"name": "Category1",
"parent_id": 0
}
]
and I want to get something like this:
[
{
"id": 1,
"name": "Category1",
"parent_id": 0,
"subcategories": [
{
"id": 3,
"name": "Subcategory1",
"parent_id": 1,
},
{
"id": 4,
"name": "Subcategory2",
"parent_id": 1,
}
]
}
]
It is similar to eager loading but it is like the model eager loading itself. How can I do this in least queries possible?
You need to use sequelize.define() to create a Model that backs your table, in this case "Category"
// first define your model, you don't have to define the `id` or `parent_id` as they will be created automatically
const Categories = sequelize.define('categories', {
name: {
type: DataTypes.STRING(255),
},
},
{
// use underscores in generated column names
underscored: true,
});
Now create the relationships between parent-<children and child--parent for the Model.
// relate a category to its parent=
Categories.belongsTo(Categories, {
as: 'parent',
foreignKey: 'parent_id',
targetKey: 'id',
});
// relate parent to child categories
Categories.hasMany(Categories, {
as: 'subcategories',
foreignKey: 'parent_id',
});
Now you can use the include option to pass in the Model and specify the as parameter to load the correct relationships. Pass in required: false to use a left join so that results will come back if there are no subcategories.
// ... your code
// now you can include the subcategories and
// pass in the parent_id into the where clause
const category = await Categories.findOne({
include: {
model: Categories,
as: 'subcategories',
required: false,
},
where: {
parent_id: 0,
},
});
// if you know the ID you want is 1...
const category = await Categories.findByPk(1, {
include: {
model: Categories,
as: 'subcategories',
required: false,
},
});
In the reverse direction, from the child to the parent, or in this case both...
// To get a category and its parent and children...
const categoryWithParentAndSubcategories = await Categories.findByPk(123, {
include: [
{
model: Categories,
as: 'parent',
required: false,
},
{
model: Categories,
as: 'subcategories',
required: false,
},
],
});
// you can keep going for multiple levels if you want
// To get a category and its grandparent, parent and children...
const categoryWithParentAndSubcategories = await Categories.findByPk(123, {
include: [
{
model: Categories,
as: 'parent',
required: false,
include: {
model: Categories,
as: 'parent',
required: false,
},
},
{
model: Categories,
as: 'subcategories',
required: false,
},
],
});

How to do where condition on include relation in Loopback

I want to get the result with include relation with where condition on include model.
return this.htcApi.find({
include: [
{
relation: 'nmos',
scope: {
include: 'countries',
},
},
],
where: { name:'Welcome', "nmos.name":'agile'}
});
This where is condition work for name of htc model not for noms module.
I want query like
Select htc.*, nmos.* FROM htc LEFT JOIN nmos ON nmos.id = htc.n_id where htc.name = 'abc' and nmos.name = 'abc';
How can add where condition on the "relation" table?
Simply you need to add where clause in 'scope' object which lies inside the 'include' object. So the code would be like :
return this.htcApi.find({
include: [
{
relation: 'nmos',
scope: {
include: 'countries',
where:{name:'agile'}
},
},
],
where: { name:'Welcome'}
});
In your query, you just need to add the property where within the scope property, like this:
return this.htcApi.find({
include: [
{
relation: 'nmos',
scope: {
include: 'countries',
where: {
and [
{ id: htc.n_id },
{ name: 'abc' },
],
},
},
},
],
where: { name: 'abc' }
});
This should return the htcApi objects named 'abc' with the related nmos objects that have the name 'abc' and the id 'n_id'.

Passing associated table attributes as main table attributes in sequelize

I have a query which is similar to the following.
const TODAY = new Date().setHours(0, 0, 0, 0);
const studentAttendances = await STUDENT_ATTENDANCES.findAll({
where: {
punch_in: { [Op.gt]: TODAY },
},
attributes: ['id', 'student_id', 'arrived_time'],
include: [
{
model: STUDENTS,
attributes: ['name'],
},
],
raw: true,
nest: true,
});
The current output given is an array of objects which look like the following.
{
"id": 1041,
"student_id": 16,
"arrived_time": "2019-05-29T08:29:41.000Z",
"student": {
"name": "Tom"
}
},
Instead of having a nested object as above how do i make the student name itself be an attribute of the main object ? Example as follows.
{
"id": 1041,
"student_id": 16,
"arrived_time": "2019-05-29T08:29:41.000Z",
"student": "Tom"
},
I hope to do this through sequelize without using any JS loops
Something like this should work, assuming your singular model name is "Student":
const studentAttendances = await STUDENT_ATTENDANCES.findAll({
where: {
punch_in: { [Op.gt]: TODAY },
},
attributes: [
[sequelize.col('Student.name'), 'studentName'], // will give you name as 'studentName'
'id', 'student_id', 'arrived_time'
],
include: [
{
model: STUDENTS,
attributes: [], // empty this out
},
]
});
I think you can handle it with pure javascript :
studentAttendances = studentAttendances.get({plain: true})
for(student in studentAttendances){
studentAttendances[student].student = studentAttendances[student].student.name
}