Sequelize join two tables on id - node.js

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

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 - N:M Association Count Number of Included Model with Condition

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.

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.

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

How to count a group by query in NodeJS Sequelize

In Rails I can perform a simple ORM query for the number of Likes a model has:
#records = Model
.select( 'model.*' )
.select( 'count(likes.*) as likes_count' )
.joins( 'LEFT JOIN likes ON model.id = likes.model_id' )
.group( 'model.id' )
This generates the query:
SELECT models.*, count(likes.*) as likes_count
FROM "models" JOIN likes ON models.id = likes.model_id
GROUP BY models.id
In Node Sequelize, any attempt at doing something similar fails:
return Model.findAll({
group: [ '"Model".id' ],
attributes: ['id', [Sequelize.fn('count', Sequelize.col('"Likes".id')), 'likes_count']],
include: [{ attributes: [], model: Like }],
});
This generates the query:
SELECT
Model.id,
count(Likes.id) AS likes_count,
Likes.id AS Likes.id # Bad!
FROM Models AS Model
LEFT OUTER JOIN Likes
AS Likes
ON Model.id = Likes.model_id
GROUP BY Model.id;
Which generates the error:
column "Likes.id" must appear in the GROUP BY clause or be used in an aggregate function
It's erroneously selecting likes.id, and I have no idea why, nor how to get rid of it.
This sequelize github issue looks totally like your case:
User.findAll({
attributes: ['User.*', 'Post.*', [sequelize.fn('COUNT', 'Post.id'), 'PostCount']],
include: [Post]
});
To resolve this problem we Need to upgrade to latest version of sequelize and include raw = true,
Here is How I had done after lot of iteration and off-course googling.
getUserProjectCount: function (req, res) {
Project.findAll(
{
attributes: ['User.username', [sequelize.fn('COUNT', sequelize.col('Project.id')), 'ProjectCount']],
include: [
{
model: User,
attributes: [],
include: []
}
],
group: ['User.username'],
raw:true
}
).then(function (projects) {
res.send(projects);
});
}
where my reference models are
//user
var User = sequelize.define("User", {
username: Sequelize.STRING,
password: Sequelize.STRING
});
//project
var Project = sequelize.define("Project", {
name: Sequelize.STRING,
UserId:{
type:Sequelize.INTEGER,
references: {
model: User,
key: "id"
}
}
});
Project.belongsTo(User);
User.hasMany(Project);
after migration ORM create 'Users' & 'Projects' table into my postgres server.
Here is SQL Query by ORM
SELECT
"User"."username", COUNT("Project"."id") AS "ProjectCount"
FROM
"Projects" AS "Project"
LEFT OUTER JOIN "Users" AS "User" ON "Project"."UserId" = "User"."id"
GROUP BY
"User"."username";
What worked for me counting column A and grouping by column B
const noListingsPerRetailer = Listing.findAll({
attributes: [
'columnA',
[sequelize.fn('COUNT', sequelize.col('columnB')), 'labelForCountColumn'],
],
group:["columnA"]
});

Resources