Sequelize - N:M Association Count Number of Included Model with Condition - node.js

I have two models: Articles and Tags. These models are associated to each other by belongsToMany, so an article may have many tags, and tags can be used by many articles.
models
Articles.belongsToMany(Tags, {
foreignKey: 'articleId',
as: 'tags',
});
Tags.belongsToMany(Articles, {
foreignKey: 'tagId',
as: 'articles',
});
When querying, I need to get articles that has searchTag as tag, but get all tag information associated to article.
What I tried is:
const articles = await Articles.findAll({
include: [{
model: Tags,
as: 'tags',
where: {
tag: searchTag,
},
attributes: ['id', 'tag'],
required: true,
}],
where: {
// querying condition
},
});
but this code gets the matching tag only, which fails to get all tags that belongs to the article.
Another solution I thought was to check if any tag that tag=searchTag exists in tags, but I have no idea how to fit this condition into sequelize object. What is the proper way to fit this condition into sequelize object? Or any other way to solve this problem other than getting all ids of articles that has searchedTag and get all articles based on the ids?

You have to query for your searchTag in the top where clause.
const articles = await Articles.findAll({
include: [{
model: Tags,
as: 'tags',
// where: {
// tag: searchTag,
// },
attributes: ['id', 'tag'],
required: true,
}],
where: {
$tags.id$': searchTag // <-- New
},
});
By querying inside the include part, you get only the filtered tags like you described in your post.

Related

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
});

Multiple Sequelize Associations

I have a few associations between models like so:
Patient.hasMany(Transaction, {
as: 'transactions',
foreignKey: 'patient_id',
sourceKey: 'id'
});
Transaction.belongsTo(Patient, {
as: 'patient',
foreignKey: 'patient_id',
targetKey: 'id'
});
Transaction.hasMany(Transaction, {
as: 'transactions',
foreignKey: 'item_to_pay',
sourceKey: 'id'
});
I'm writing a query to get a list of transactions that belong to a patient and include the models associated with them:
Transaction.findAll({
where: {
patient_id: 1
},
include: [{
model: Patient,
as: 'patient',
attributes: ['first_name']
}, {
model: Transaction,
as: 'transactions',
include: [{
model: Patient,
as: 'patient',
attributes: ['first_name']
}]
}],
});
However when the result returns, it does not include the second nested patient model as I expect it to.
I've also tried writing a different query to see if I can get the desired result:
Transaction.findAll({
where: {
patient_id: 1
},
include: [{
model: Transaction,
as: 'transactions',
include: [{
model: Patient,
as: 'patient',
attributes: ['first_name']
}]
}],
});
But this query errors out and returns 'Unknown column 'patient.id' in field list'.
Does anyone see something wrong with my query or associations? Is there something I'm not understanding about the associations here?
I ended up changing my original query since I couldn't figure it out at the time. I stumbled into the same issue recently and realized the issue came from how I named my associations. I named the transaction.hasMany association as transactions which ended up confusing MySQL with the query that was generated because one of the tables is named transactions.
TLDR, don't name your associations the same name as any of your table names.

Sequelize: query to include a parent model's data if it has associated children; don't return the child model's data

In my node.js app using sequelize with a postgresql database I have defined three models: users, baskets, and items. The users model has many baskets and baskets has many items.
I am trying to write a query to return the users and baskets only, without the items, if the basket contains one or more items. This query is getting me the data I need (users and baskets if the basket has items), but the problem with it is that it is also giving me all the items.
return await models.User.findAll({
include: [{
model: models.Basket,
include: [{
model: models.Item,
required: true
}],
}],
});
If you do not need anything from items table,
return await models.User.findAll({
include: [{
model: models.Basket,
include: [{
attributes: [], // <----- Add this line
model: models.Item,
required: true
}],
}],
});

Sequelize - query with or operator and model association

With Sequelize, I have two models with many to many association : User and Category.
I want to get all categories that belongs to the current user, and also categories with a certain property, but I don't understand how, with only one query...
I'm using the Op.or operator, according to the documentation, and the $Model.attribute$ syntax for associated model (seen here).
let categories = await models.category.findAll({
where: {
[Op.or]: [
{ someCategoryProperty: true },
{ '$User.id$': req.currentUser.id },
],
},
include: [{
model: models.user,
as: 'User',
}],
});
The operator works if I add 2 conditions about the Category model, but how to add a condition on the association ?
I finally found the tips :
Actually, $Model.attribute$ wasn't the good pattern, $database_table_name.attribute$ is the good one.
With the '$..$' syntax, we must use the database table name, and not the model.
If my model is called user, Sequelize set the database name users !
So this code works :
let categories = await models.category.findAll({
where: {
[Op.or]: [
{ someCategoryProperty: true },
{ '$users.id$': req.currentUser.id },
],
},
include: [{
model: models.user,
}],
});
Thanks

Sequelize Nested Association with Two Tables

I have a scenario where I am trying to query a parent table (document) with two associated tables (reference & user) that do not have a relationship to each other, but do have a relationship with the parent table. In SQL, this query would look like such and correctly outputs the data I am looking for:
select *
from `document`
left join `user`
on `document`.`user_id` = `user`.`user_id`
left join `reference`
on `document`.`reference_id` = `reference`.`reference_id`
where `user`.`organization_id` = 1;
However, associations that are nested have to relate in hierarchical order in order for the query to work. Since the nested associations are not related to each other I get an association error. How can I avoid this error? Would required: false have any influence on this?
models.Document.findAll({
order: 'documentDate DESC',
include: [{
model: models.User,
where: { organizationId: req.user.organizationId },
include: [{
model: models.Reference,
}]
}],
})
Error:
Unhandled rejection Error: reference is not associated to user!
Associations:
Document:
associate: function(db) {
Document.belongsTo(db.User, {foreignKey: 'user_id'}),
Document.belongsTo(db.Reference, { foreignKey: 'reference_id'});;
}
Reference:
associate: function(db){
Reference.hasMany(db.Document, { foreignKey: 'reference_id' });
}
Should I just chain queries instead?
If you want to replicate your query (as closely as possibly) use the following query. Keep in mind that the where on the User include will only serve to remove matches on Document.user_id where the User.organization_id does not match, but the Document will still be returned. If you want to omit Documents where the User.organization_id does not match use required: true.
User <- Document -> Reference
models.Document.findAll({
// this is an array of includes, don't nest them
include: [{
model: models.User,
where: { organization_id: req.user.organization_id }, // <-- underscored HERE
required: true, // <-- JOIN to only return Documents where there is a matching User
},
{
model: models.Reference,
required: false, // <-- LEFT JOIN, return rows even if there is no match
}],
order: [['document_date', 'DESC']], // <-- underscored HERE, plus use correct format
});
The error is indicating that the User model is not associated to the Reference model, but there are only definitions for the Document and Reference models in your description. You are joining these tables in your query with the include option, so you have to make sure they are associated. You don't technically need the foreignKey here either, you are specifying the default values.
Add Reference->User association
associate: function(db) {
// belongsTo()? maybe another relationship depending on your data model
Reference.belongsTo(db.User, {foreignKey: 'user_id'});
Reference.hasMany(db.Document, { foreignKey: 'reference_id' });
}
It also looks like you probably set underscored: true in your model definitions, so your query should reflect this. Additionally if you want to perform a LEFT JOIN you need to specify required: false on the include, otherwise it is a regular JOIN and you will only get back rows with matches in the included model. You are also using the wrong order format, it should be an array of values, and to sort by model.document_date DESC you should use order: [['document_date', 'DESC']].
Proper query arguments
models.Document.findAll({
order: [['document_date', 'DESC']], // <-- underscored HERE, plus use correct format
include: [{
model: models.User,
where: { organization_id: req.user.organization_id }, // <-- underscored HERE
required: false, // <-- LEFT JOIN
include: [{
model: models.Reference,
required: false, // <-- LEFT JOIN
}]
}],
});
If you are still having trouble, try enabling logging by setting logging: console.log in your Sequelize connection, that will show you all the queries it is running in your console.
It seems to me that your problem might be the associations, trying to link back to your primary key on Documents instead of the columns 'user_id' and 'reference_id'. You didn't post the table attributes so I might have understood this wrong.
Association on documents are ok.
Documents
associate: function(db) {
Document.belongsTo(db.User, {foreignKey: 'user_id'}), //key in documents
Document.belongsTo(db.Reference, { foreignKey: 'reference_id'}); //key in documents
}
User
associate: function(db) {
User.belongsTo(db.Document, {
foreignKey: 'id', //Key in User
targetKey: 'user_id' //Key in Documents
}),
}
Reference
associate: function(db) {
Reference.belongsTo(db.Document, {
foreignKey: 'id', //Key in reference
targetKey: 'reference_id' //Key in Documents
}),
}
Also
For debbuging consider using logging so you can see the queries.
var sequelize = new Sequelize('database', 'username', 'password', {
logging: console.log
logging: function (str) {
// do your own logging
}
});

Resources