56

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

Nathan Kuchta
  • 13,482
  • 2
  • 26
  • 35
futbolpal
  • 1,388
  • 2
  • 13
  • 19

3 Answers3

37

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')
        });
  }
c.hill
  • 3,127
  • 25
  • 31
  • 17
    This did not work for me, I think as of sequelize 2.0, the where clause must be placed in the include array member, such as `include: [{model:..., where:...}]` – Octav Zlatior Apr 09 '15 at 17:22
  • Nevertheless, the answer gave me hope that this is somehow possible so I pursued to finding a solution :) – Octav Zlatior Apr 09 '15 at 17:23
  • 2
    I can confirm that as of Sequelize 2.0 the `where` clause must be inside the `include`, just like: `include: [ { model: ..}, where: { 'Author.lastName: 'Testerson' }]` – Andrey Popov Jun 29 '15 at 12:02
  • @AndreyPopov, but how in this case to find Books by title or by Author.lastName? – Mikalai Jul 09 '15 at 10:12
  • `Book.findAll({ include: [ model: Author, where: { 'lastName': 'Testerson' } ] });` Should be working fine.. :) – Andrey Popov Jul 09 '15 at 14:25
  • Is it better practise to keep all models in one file? Like above? Or put them in separate files? @AndreyPopov – James111 Jan 19 '16 at 05:23
  • I would strongly recommend putting models in different files. Then just load all files in a directory and they will self-initialize :) Otherwise you'll get pretty doomed after a while (thousands of lines in one file) – Andrey Popov Jan 19 '16 at 21:36
  • As some others commented before, the query doesn't work with `where:{'Authors.lastName': 'Testerson'}` outside `include`, as in this answer. Sequelize compiles it as ``[...] AND `Authors.lastName` = 'Testerson' [...]``. – Gustavo Straube Jan 22 '16 at 14:19
  • How would this work if the relationship was a many to many? `Author.belongsToMany(Book)` `Book.belongsToMany(Author)` A second table (something like "BookAuthor") gets created, and syntax like `where: { 'Authors.lastName': 'Testerson' }` doesn't seem to work anymore... – WestleyArgentum Jun 10 '16 at 22:56
  • 1
    you don't need the Authors inside the where clause: ```{ 'lastName': 'Testerson' }``` – eesdil Sep 24 '16 at 09:41
  • 16
    putting `where` inside `include` builds a query like this: `LEFT OUTER JOIN Authors ON Books.authorId = Authors.id AND Authors.lastName = 'Testerson'`, and the result is quite different from `LEFT OUTER JOIN Authors ON Books.authorId = Authors.id ... WHERE Authors.lastName = 'Testerson'`. I'm not sure how to achieve the latter query with sequelize. – elquimista Dec 21 '16 at 17:24
  • how to use the same way when having many to many relations since you have an external table instead of columns to test on ? – Kaki Master Of Time Aug 01 '17 at 12:16
28

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}
    ]
});
Adam
  • 1,470
  • 1
  • 17
  • 13
14

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!

Mohamed Allal
  • 17,920
  • 5
  • 94
  • 97