exclude through attributes in sequelize - node.js

I have 2 tables post and tags. I'm using Tag to get all the posts associated with it.
models.Tag.findAll({
attributes: ['tagName'],
include: [
{model: models.Post
attributes: ['content']
through: {
attributes: []
}
}
]
})
The problem is that it selects all the through table attributes in the query.
Although doing include.through.attributes = [] the attributes don't show up in the result query but when I console.log the select query, it's still selecting all the attributes of the through table.
Is there to exclude the through table? it makes groupBy impossible in Postgres, cuz its selecting all the columns automatically.

I don't reproduce on sequelize#6.5.1 sqlite3#5.0.2 with:
#!/usr/bin/env node
// Find all posts by users that a given user follows.
// https://stackoverflow.com/questions/42632943/sequelize-multiple-where-clause
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.' + path.basename(__filename) + '.sqlite',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
});
User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
User.hasMany(Post);
Post.belongsTo(User);
await sequelize.sync({force: true});
// Create data.
const users = await User.bulkCreate([
{name: 'user0'},
{name: 'user1'},
{name: 'user2'},
{name: 'user3'},
])
const posts = await Post.bulkCreate([
{body: 'body00', UserId: users[0].id},
{body: 'body01', UserId: users[0].id},
{body: 'body10', UserId: users[1].id},
{body: 'body11', UserId: users[1].id},
{body: 'body20', UserId: users[2].id},
{body: 'body21', UserId: users[2].id},
{body: 'body30', UserId: users[3].id},
{body: 'body31', UserId: users[3].id},
])
await users[0].addFollows([users[1], users[2]])
const user0Follows = await User.findByPk(users[0].id, {
attributes: [
[Sequelize.fn('COUNT', Sequelize.col('Follows.Posts.id')), 'count']
],
include: [
{
model: User,
as: 'Follows',
attributes: [],
//through: { attributes: [] },
include: [
{
model: Post,
attributes: [],
}
],
},
],
})
assert.strictEqual(user0Follows.dataValues.count, 4);
await sequelize.close();
})();
The prettified generated SELECT is:
SELECT
`User`.`id`,
COUNT(`Follows->Posts`.`id`) AS `count`
FROM
`Users` AS `User`
LEFT OUTER JOIN `UserFollowUser` AS `Follows->UserFollowUser` ON `User`.`id` = `Follows->UserFollowUser`.`UserId`
LEFT OUTER JOIN `Users` AS `Follows` ON `Follows`.`id` = `Follows->UserFollowUser`.`FollowId`
LEFT OUTER JOIN `Posts` AS `Follows->Posts` ON `Follows`.`id` = `Follows->Posts`.`UserId`
WHERE
`User`.`id` = 1;
If I remove the through: { attributes: [] }, then the through attributes appear, so the statement is doing something as expected:
SELECT
`User`.`id`,
COUNT(`Follows->Posts`.`id`) AS `count`,
`Follows->UserFollowUser`.`createdAt` AS `Follows.UserFollowUser.createdAt`,
`Follows->UserFollowUser`.`updatedAt` AS `Follows.UserFollowUser.updatedAt`,
`Follows->UserFollowUser`.`UserId` AS `Follows.UserFollowUser.UserId`,
`Follows->UserFollowUser`.`FollowId` AS `Follows.UserFollowUser.FollowId`
FROM
`Users` AS `User`
LEFT OUTER JOIN `UserFollowUser` AS `Follows->UserFollowUser` ON `User`.`id` = `Follows->UserFollowUser`.`UserId`
LEFT OUTER JOIN `Users` AS `Follows` ON `Follows`.`id` = `Follows->UserFollowUser`.`FollowId`
LEFT OUTER JOIN `Posts` AS `Follows->Posts` ON `Follows`.`id` = `Follows->Posts`.`UserId`
WHERE
`User`.`id` = 1;
so likely this was fixed.

Related

Query with Sequelize

I'm making a sequelize query like this:
const users = await User.findAll({
attributes: ['id', Sequelize.literal(`"firstName" || ' ' || "lastName" as name`)],
where: { status: `[${USER_STATUS.ACTIVE}]`, organizationId: orgId }
});
I got a problem
"organization_users"."status" = ' "[1]" '
I want it to make a query like this:
"organization_users"."status" = '[1]'
without the double quotes. Thank you. I try many way but did not work. Any help is appreciated!
I come to an answer on stackoverflow:
where: { status: { [Op.contains]: Sequelize.literal(`[${USER_STATUS.ACTIVE}]`) }, organizationId: orgId }
but the query is
"organization_users"."status" #> [1]
Maybe this is a bug of sequelize?
You could try storing the value of ${USER_STATUS.ACTIVE} in a variable beforehand and then pass that variable as the status.
const st = "[" + ${USER_STATUS.ACTIVE} + "]";
const users = await User.findAll({
attributes: ['id', Sequelize.literal(`"firstName" || ' ' || "lastName" as name`)],
where: { status: st, organizationId: orgId }
});
I try this and it works:
const users = await User.findAll({
attributes: [
'id',
'firstName',
'lastName',
Sequelize.literal(`"firstName" || ' ' || "lastName" as name`)
],
where: { status: { [Op.contains]: [USER_STATUS.ACTIVE] }, organizationId: orgId },
include: [{ model: Role, as: 'roles', where: { roleName: role } }]
}).map(u => {
return u.dataValues;
});

Sequelize throws Error "Unable to find a valid association for model x" When Ordering By Associated Model

I have a problem with sequelize, when I want to ordering my query result by associated model, sequelize throw this error:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Unable to find a valid association for model, 'productLanguage'
These are my files:
**Context.js **
const Sequelize = require('sequelize');
const sequelize = new Sequelize("postgres://postgres:123456#localhost:5432/sampleDB");
module.exports = {
Sequelize: Sequelize,
sequelize: sequelize
}
User.js
const context = require('../context');
module.exports = context.sequelize.define('user', {
name: context.Sequelize.STRING,
},{
freezeTableName: true
});
Product.js
const context = require('../context');
module.exports = context.sequelize.define('product', {
slug: context.Sequelize.STRING,
price: context.Sequelize.DECIMAL(10,2),
},{
freezeTableName: true
});
ProductLanguage.js
const context = require('../context');
module.exports = context.sequelize.define('productLanguage', {
name: context.Sequelize.STRING,
},{
freezeTableName: true,
timestamps: false
});
Language.js
const context = require('../context');
module.exports = context.sequelize.define('language', {
name: context.Sequelize.STRING,
slug: context.Sequelize.STRING,
},{
freezeTableName: true
});
db.js
var context = require('./context');
var User = require('./models/User'),
Product = require('./models/Product'),
ProductLanguage = require('./models/ProductLanguage'),
Language = require('./models/Language');
// ===================== ASSOCIATIONS =====================
// user 1:m Product
Product.belongsTo(User); // product owner
User.hasMany(Product);
// Product 1:m ProductLanguage m:1 Language
ProductLanguage.belongsTo(Product);
Product.hasMany(ProductLanguage);
ProductLanguage.belongsTo(Language);
Language.hasMany(ProductLanguage);
module.exports = {
Sequelize: context.Sequelize,
sequelize: context.sequelize,
models: {
Product: Product,
User: User,
ProductLanguage: ProductLanguage,
Language: Language
}
}
and finally this is my query
app.get('/', async (req, res, next)=>{
var result = await db.models.User.findAll({
include:[
{
model: db.models.Product,
attributes: ['price'],
include: [
{
model: db.models.ProductLanguage,
attributes: ['name'],
include: [
{
model: db.models.Language,
attributes: ['name'],
}
]
}
]
}
],
order:[
[db.models.ProductLanguage, 'name', 'desc']
],
attributes: ['name']
});
res.send(result);
});
The query work fine without "order" part, so I think the problem should be one on these :
Something is wrong on this part: [db.models.ProductLanguage, 'name', 'desc']
Something is wrong on association definitions
Note: I've searched on youtube and stackoverflow and sequelize documentation over 4 days but nothing found.
I use these dependencies:
"express": "^4.16.2",
"pg": "^6.4.2",
"pg-hstore": "^2.3.2",
"sequelize": "^4.32.2"
I've found the solution.
I must put all associated model into order, so the correct query is:
order:[
[db.models.Product, db.sequelize.models.ProductLanguage, 'name', 'desc']
],
The full query must be:
var result = await db.models.User.findAll({
include:[
{
model: db.models.Product,
attributes: ['price'],
include: [
{
model: db.models.ProductLanguage,
attributes: ['name'],
include: [
{
model: db.models.Language,
attributes: ['name'],
}
]
}
]
}
],
order:[
[db.models.Product, db.sequelize.models.ProductLanguage, 'name', 'desc']
],
attributes: ['name']
});
I hope this will be helpful for others.
Those who still won't get the result, try this syntax -
order:[[{ model: db.models.ProductLanguage, as: 'language_of_product' } , 'name', 'desc']]
In addition to Moradof's answer, it's important to note that if you specify an alias for your included model, then you must also specify the alias in the order statement.
Building on the previous example, we get:
var result = await db.models.User.findAll({
include:[
{
model: db.models.Product,
as: 'include1',
attributes: ['price'],
include: [
{
model: db.models.ProductLanguage,
as: 'include2',
attributes: ['name'],
include: [
{
model: db.models.Language,
attributes: ['name'],
}
]
}
]
}
],
order:[
[{ model: db.models.Product, as: 'include1' },
{ model: db.sequelize.models.ProductLanguage, as: 'include2' },
'name',
'desc']
],
attributes: ['name']
});
Note that because I named the Product as include1 in the include statement, I also had to name it as include1 in the order statement.
order:[
[{ model: db.models.Product, as: 'include1' },
{ model: db.sequelize.models.ProductLanguage, as: 'include2' },
'name',
'desc']
After using this sequlizer is creating this in query. ORDER BY ``.name DESC LIMIT 10;
So how can I pass the table alias before name.

Sequelize query with count in inner join

I am trying to convert this query to sequelize query object what is the right wayto do it?
SELECT families.id, count('answers.familyId') FROM families LEFT JOIN
answers on families.id = answers.familyId WHERE answers.isActive=1 AND
answers.answer=1 GROUP BY families.id HAVING COUNT('answers.familyId')>=6
Let's assume that Family is your families sequelize model and Answer is your answers sequelize model, and sequelize is your Sequelize instance
Family.findAll({
attributes: ['*', sequelize.fn('COUNT', sequelize.col('Answers.familyId'))],
include: [
{
model: Answer,
attributes: [],
where: {
isActive: 1,
answer: 1
}
}
],
group: '"Family.id"',
having: sequelize.where(sequelize.fn('COUNT', sequelize.col('Answers.familyId')), '>=', 6)
}).then((families) => {
// result
});
Useful documentation links:
sequelize.fn()
sequelize.where()
sequelize.col()
You need to use get() on the attribute: aliased count column
There are two important gotchas when reading the aggregates out:
the count only shows up on results if you alias it with attributes as shown by Piotr at https://stackoverflow.com/a/42472696/895245 and as shown at How do I select a column using an alias attributes aliasing has the unexpected effect of requiring you to use .get().
as mentioned at: How does group by works in sequelize? the count comes out as a string in PostgreSQL due to bigint shenanigans, and you need parseInt it
Here's a minimal runnable example where we have posts and users who can like posts, and we want to count how:
how many likes each user has
ignoring likes to post2
considering only users that have 0 or 1 likes in total
The following small improvements are made over Piotr's code:
you likely don't want attributes: ['*' because that selects all columns, and therefore generally includes columns that are neither aggregates nor grouped by, leading to indeterminate behavior in some DBMSs and errors in others. You should just specify the GROUP by column instead, in our case the column is name.
using the slightly cleaner Op.lte rather than the literal '<='
Due to required: false, this first version does a LEFT OUTER JOIN + COUNT(column), see also: https://dba.stackexchange.com/questions/174694/how-to-get-a-group-where-the-count-is-zero
sqlite.js
const assert = require('assert');
const { DataTypes, Op, Sequelize } = require('sequelize');
const sequelize = new Sequelize('tmp', undefined, undefined, Object.assign({
dialect: 'sqlite',
storage: 'tmp.sqlite'
}));
;(async () => {
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(Post, {through: 'UserLikesPost'});
Post.belongsToMany(User, {through: 'UserLikesPost'});
await sequelize.sync({force: true});
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'})
const post1 = await Post.create({body: 'post1'})
const post2 = await Post.create({body: 'post2'})
// Set likes for each user.
await user0.addPosts([post0, post1])
await user1.addPosts([post0, post2])
let rows = await User.findAll({
attributes: [
'name',
[sequelize.fn('COUNT', sequelize.col('Posts.id')), 'count'],
],
include: [
{
model: Post,
attributes: [],
required: false,
through: {attributes: []},
where: { id: { [Op.ne]: post2.id }},
},
],
group: ['User.name'],
order: [[sequelize.col('count'), 'DESC']],
having: sequelize.where(sequelize.fn('COUNT', sequelize.col('Posts.id')), Op.lte, 1)
})
assert.strictEqual(rows[0].name, 'user1')
assert.strictEqual(parseInt(rows[0].get('count'), 10), 1)
assert.strictEqual(rows[1].name, 'user2')
assert.strictEqual(parseInt(rows[1].get('count'), 10), 0)
assert.strictEqual(rows.length, 2)
})().finally(() => { return sequelize.close() });
with:
package.json
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.5.1",
"sqlite3": "5.0.2"
}
}
and Node v14.17.0.
If we wanted the INNER JOIN version excluding 0 counts, we could just remove the required: false, which makes it be the default true. We can also use do a slightly simpler COUNT(*) since there will be no NULLs now:
let rows = await User.findAll({
attributes: [
'name',
[sequelize.fn('COUNT', '*'), 'count'],
],
include: [
{
model: Post,
attributes: [],
through: {attributes: []},
where: { id: { [Op.ne]: post2.id }},
},
],
group: ['User.name'],
order: [[sequelize.col('count'), 'DESC']],
having: sequelize.where(sequelize.fn('COUNT', '*'), Op.lte, 1)
})
assert.strictEqual(rows[0].name, 'user1')
assert.strictEqual(parseInt(rows[0].get('count'), 10), 1)
assert.strictEqual(rows.length, 1)
PostgreSQL support has been broken for several years due to column X must appear in the GROUP BY clause or be used in an aggregate function
The above code should work for PostgreSQL too, but as mentioned at:
https://github.com/sequelize/sequelize/issues/3256
https://github.com/sequelize/sequelize/issues/5481#issuecomment-964387232
there's a bug and it doesn't. The fact that such glaring bugs have persisted for several years make me doubt if I should really be using this ORM.
The workaround is to use both:
raw: true,
includeIgnoreAttributes: false,
Full working example with the workaround:
#!/usr/bin/env node
const assert = require('assert');
const { DataTypes, Op, Sequelize } = require('sequelize');
const sequelize = new Sequelize('tmp', undefined, undefined, Object.assign({
dialect: 'postgres',
host: '/var/run/postgresql',
}));
;(async () => {
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(Post, {through: 'UserLikesPost'});
Post.belongsToMany(User, {through: 'UserLikesPost'});
await sequelize.sync({force: true});
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'})
const post1 = await Post.create({body: 'post1'})
const post2 = await Post.create({body: 'post2'})
// Set likes for each user.
await user0.addPosts([post0, post1])
await user1.addPosts([post0, post2])
let rows = await User.findAll({
attributes: [
'name',
[sequelize.fn('COUNT', '*'), 'count'],
],
raw: true,
includeIgnoreAttributes: false,
include: [
{
model: Post,
where: { id: { [Op.ne]: post2.id }},
},
],
group: ['User.name'],
order: [[sequelize.col('count'), 'DESC']],
having: sequelize.where(sequelize.fn('COUNT', '*'), Op.lte, 1)
})
assert.strictEqual(rows[0].name, 'user1')
assert.strictEqual(parseInt(rows[0].count, 10), 1)
assert.strictEqual(rows.length, 1)
})().finally(() => { return sequelize.close() });
tested on PostgreSQL 13.4, Ubuntu 21.10.
Related
Counting associated entries with Sequelize

Sequelize find by association through manually-defined join table

I know that there is a simpler case described here:
Unfortunately, my case is a bit more complex than that. I have a User model which belongsToMany Departments (which in turn belongsToMany Users), but does so through userDepartment, a manually defined join table. My goal is to get all the users belonging to a given department. First let's look at models/user.js:
var user = sequelize.define("user", {
id: {
type: DataTypes.INTEGER,
field: 'emplId',
primaryKey: true,
autoIncrement: false
},
firstname: {
type: DataTypes.STRING,
field: 'firstname_preferred',
defaultValue: '',
allowNull: false
}
...
...
...
associate: function(models) {
user.belongsToMany(models.department, {
foreignKey: "emplId",
through: 'userDepartment'
});
})
}
...
return user;
Now, a look at models/department.js:
var department = sequelize.define("department", {
id: {
type: DataTypes.INTEGER,
field: 'departmentId',
primaryKey: true,
autoIncrement: true
},
...
classMethods: {
associate: function(models) {
department.belongsToMany(models.user, {
foreignKey: "departmentId",
through: 'userDepartment',
onDelete: 'cascade'
});
}
...
return department;
And finally at models/userDepartment.js:
var userDepartment = sequelize.define("userDepartment", {
title: {
type: DataTypes.STRING,
field: 'title',
allowNull: false,
defaultValue: ''
}
}, {
tableName: 'user_departments'
});
return userDepartment;
So far so good. However, this query:
models.user.findAll({
where: {'departments.id': req.params.id},
include: [{model: models.department, as: models.department.tableName}]
})
Fails with the following error:
SequelizeDatabaseError: ER_BAD_FIELD_ERROR: Unknown column 'user.departments.id' in 'where clause'
Attempting to include userDepartment model results in:
Error: userDepartment (user_departments) is not associated to user!
In short: I have two Sequelize Models with a M:M relationship. They are associated through a manually defined join table (which adds a job title to each unique relationship, i.e., User A is a "Manager" in Department B). Attempting to find Users by Department fails with a bad table name error.
sequelize version "^2.0.5"
Took a couple of hours, but I found my solution:
models.department.find({
where: {id:req.params.id},
include: [models.user]
The problem is that Sequelize won't let you "go out of scope" because it begins each where clause with model_name. So, for example, the where clause was trying to compare user.departments.id when the departments table is only joined as departments.id. Since we're querying on a value of the department (the ID), it makes the most since to query for a single department and return their associated users.
I had a similair problem, but in my case I couldn't switch the tables.
I had to make use of the: sequelize.literal function.
In your case it would look like the following:
models.user.findAll({
where: sequelize.literal("departments.id = " + req.params.id),
include: [{model: models.department, as: models.department.tableName}]
})
I'm not fond of it, but it works.
For anyone still looking for an answer for this, I found one here on Github.
You simply do this:
where: {
'$Table.column$' : value
}
You can also use the auto-generated instance.getOthers() method if you have a class instance
This does potentially mean one extra query. But if the instance is already at hand, this is the most convenient syntax.
Supposing a "user likes post with given score" situation, we can get all the posts that a user likes with:
const user0 = await User.create({name: 'user0'})
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[0].UserLikesPost.score === 1);
assert(user0Likes.length === 1);
Full runnable example:
main.js
const assert = require('assert')
const { DataTypes, Op, Sequelize } = require('sequelize')
const common = require('./common')
const sequelize = common.sequelize(__filename, process.argv[2], { define: { timestamps: false } })
;(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
});
const UserLikesPost = sequelize.define('UserLikesPost', {
UserId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
PostId: {
type: DataTypes.INTEGER,
references: {
model: Post,
key: 'id'
}
},
score: {
type: DataTypes.INTEGER,
},
});
User.belongsToMany(Post, {through: UserLikesPost});
Post.belongsToMany(User, {through: UserLikesPost});
await sequelize.sync({force: true});
// Create some users and likes.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});
// Autogenerated add* methods
// Make some useres like some posts.
await user0.addPost(post0, {through: {score: 1}})
await user1.addPost(post1, {through: {score: 2}})
await user1.addPost(post2, {through: {score: 3}})
// Find what user0 likes.
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[0].UserLikesPost.score === 1);
assert(user0Likes.length === 1);
// Find what user1 likes.
const user1Likes = await user1.getPosts({order: [['body', 'ASC']]})
assert(user1Likes[0].body === 'post1');
assert(user1Likes[0].UserLikesPost.score === 2);
assert(user1Likes[1].body === 'post2');
assert(user1Likes[1].UserLikesPost.score === 3);
assert(user1Likes.length === 2);
// Where on the custom through table column.
// Find posts that user1 likes which have score greater than 2.
// https://stackoverflow.com/questions/38857156/how-to-query-many-to-many-relationship-sequelize
{
const rows = await Post.findAll({
include: [
{
model: User,
where: {id: user1.id},
through: {
where: {score: { [Op.gt]: 2 }},
},
},
],
})
assert.strictEqual(rows[0].body, 'post2');
// TODO how to get the score here as well?
//assert.strictEqual(rows[0].UserLikesPost.score, 3);
assert.strictEqual(rows.length, 1);
}
})().finally(() => { return sequelize.close() });
common.js
const path = require('path');
const { Sequelize } = require('sequelize');
function sequelize(filename, dialect, opts) {
if (dialect === undefined) {
dialect = 'l'
}
if (dialect === 'l') {
return new Sequelize(Object.assign({
dialect: 'sqlite',
storage: path.parse(filename).name + '.sqlite'
}, opts));
} else if (dialect === 'p') {
return new Sequelize('tmp', undefined, undefined, Object.assign({
dialect: 'postgres',
host: '/var/run/postgresql',
}, opts));
} else {
throw new Error('Unknown dialect')
}
}
exports.sequelize = sequelize
package.json
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.5.1",
"sqlite3": "5.0.2"
}
}
tested on PostgreSQL 13.4, Ubuntu 21.04.

Sequelize find based on association

How would I use Sequelize to find all people where a column in the relation satisfies a condition?
An example would be to find all Books whose author's last name is 'Hitchcock'. The book schema contains a hasOne relation with the Author's table.
Edit: I understand how this could be done with a raw SQL query, but looking for another approach
Here's a working sample of how to user Sequelize to get all Books by an Author with a certain last name. It looks quite a bit more complicated than it is, because I am defining the Models, associating them, syncing with the database (to create their tables), and then creating dummy data in those new tables. Look for the findAll in the middle of the code to see specifically what you're after.
module.exports = function(sequelize, DataTypes) {
var Author = sequelize.define('Author', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING
},
lastName: {
type: DataTypes.STRING
}
})
var Book = sequelize.define('Book', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
title: {
type: DataTypes.STRING
}
})
var firstAuthor;
var secondAuthor;
Author.hasMany(Book)
Book.belongsTo(Author)
Author.sync({ force: true })
.then(function() {
return Book.sync({ force: true });
})
.then(function() {
return Author.create({firstName: 'Test', lastName: 'Testerson'});
})
.then(function(author1) {
firstAuthor=author1;
return Author.create({firstName: 'The Invisible', lastName: 'Hand'});
})
.then(function(author2) {
secondAuthor=author2
return Book.create({AuthorId: firstAuthor.id, title: 'A simple book'});
})
.then(function() {
return Book.create({AuthorId: firstAuthor.id, title: 'Another book'});
})
.then(function() {
return Book.create({AuthorId: secondAuthor.id, title: 'Some other book'});
})
.then(function() {
// This is the part you're after.
return Book.findAll({
where: {
'Authors.lastName': 'Testerson'
},
include: [
{model: Author, as: Author.tableName}
]
});
})
.then(function(books) {
console.log('There are ' + books.length + ' books by Test Testerson')
});
}
In the newest version of Sequilize (5.9.0) the method proposed by #c.hill does not work.
Now you need to do the following:
return Book.findAll({
where: {
'$Authors.lastName$': 'Testerson'
},
include: [
{model: Author, as: Author.tableName}
]
});
For documentation!
Check the eager loading section
https://sequelize.org/master/manual/eager-loading.html
For the above answers! You can find it in the doc at the following title
Complex where clauses at the top-level
From the doc:
To obtain top-level WHERE clauses that involve nested columns, Sequelize provides a way to reference nested columns: the '$nested.column$' syntax.
It can be used, for example, to move the where conditions from an included model from the ON condition to a top-level WHERE clause.
User.findAll({
where: {
'$Instruments.size$': { [Op.ne]: 'small' }
},
include: [{
model: Tool,
as: 'Instruments'
}]
});
Generated SQL:
SELECT
`user`.`id`,
`user`.`name`,
`Instruments`.`id` AS `Instruments.id`,
`Instruments`.`name` AS `Instruments.name`,
`Instruments`.`size` AS `Instruments.size`,
`Instruments`.`userId` AS `Instruments.userId`
FROM `users` AS `user`
LEFT OUTER JOIN `tools` AS `Instruments` ON
`user`.`id` = `Instruments`.`userId`
WHERE `Instruments`.`size` != 'small';
For a better understanding of all differences between the inner where option (used inside an include), with and without the required option, and a top-level where using the $nested.column$ syntax, below we have four examples for you:
// Inner where, with default `required: true`
await User.findAll({
include: {
model: Tool,
as: 'Instruments',
where: {
size: { [Op.ne]: 'small' }
}
}
});
// Inner where, `required: false`
await User.findAll({
include: {
model: Tool,
as: 'Instruments',
where: {
size: { [Op.ne]: 'small' }
},
required: false
}
});
// Top-level where, with default `required: false`
await User.findAll({
where: {
'$Instruments.size$': { [Op.ne]: 'small' }
},
include: {
model: Tool,
as: 'Instruments'
}
});
// Top-level where, `required: true`
await User.findAll({
where: {
'$Instruments.size$': { [Op.ne]: 'small' }
},
include: {
model: Tool,
as: 'Instruments',
required: true
}
});
Generated SQLs, in order:
-- Inner where, with default `required: true`
SELECT [...] FROM `users` AS `user`
INNER JOIN `tools` AS `Instruments` ON
`user`.`id` = `Instruments`.`userId`
AND `Instruments`.`size` != 'small';
-- Inner where, `required: false`
SELECT [...] FROM `users` AS `user`
LEFT OUTER JOIN `tools` AS `Instruments` ON
`user`.`id` = `Instruments`.`userId`
AND `Instruments`.`size` != 'small';
-- Top-level where, with default `required: false`
SELECT [...] FROM `users` AS `user`
LEFT OUTER JOIN `tools` AS `Instruments` ON
`user`.`id` = `Instruments`.`userId`
WHERE `Instruments`.`size` != 'small';
-- Top-level where, `required: true`
SELECT [...] FROM `users` AS `user`
INNER JOIN `tools` AS `Instruments` ON
`user`.`id` = `Instruments`.`userId`
WHERE `Instruments`.`size` != 'small';
And that give us a good look how the join's are done!

Resources