Eager loading

When you are retrieving data from the database there is a fair chance that you also want to get associations with the same query - this is called eager loading. The basic idea behind that, is the use of the attribute include when you are calling find or findAll. Lets assume the following setup:

  1. class User extends Model {}
  2. User.init({ name: Sequelize.STRING }, { sequelize, modelName: 'user' })
  3. class Task extends Model {}
  4. Task.init({ name: Sequelize.STRING }, { sequelize, modelName: 'task' })
  5. class Tool extends Model {}
  6. Tool.init({ name: Sequelize.STRING }, { sequelize, modelName: 'tool' })
  7. Task.belongsTo(User)
  8. User.hasMany(Task)
  9. User.hasMany(Tool, { as: 'Instruments' })
  10. sequelize.sync().then(() => {
  11. // this is where we continue ...
  12. })

OK. So, first of all, let's load all tasks with their associated user.

  1. Task.findAll({ include: [ User ] }).then(tasks => {
  2. console.log(JSON.stringify(tasks))
  3. /*
  4. [{
  5. "name": "A Task",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:40.000Z",
  8. "updatedAt": "2013-03-20T20:31:40.000Z",
  9. "userId": 1,
  10. "user": {
  11. "name": "John Doe",
  12. "id": 1,
  13. "createdAt": "2013-03-20T20:31:45.000Z",
  14. "updatedAt": "2013-03-20T20:31:45.000Z"
  15. }
  16. }]
  17. */
  18. })

Notice that the accessor (the User property in the resulting instance) is singular because the association is one-to-something.

Next thing: Loading of data with many-to-something associations!

  1. User.findAll({ include: [ Task ] }).then(users => {
  2. console.log(JSON.stringify(users))
  3. /*
  4. [{
  5. "name": "John Doe",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:45.000Z",
  8. "updatedAt": "2013-03-20T20:31:45.000Z",
  9. "tasks": [{
  10. "name": "A Task",
  11. "id": 1,
  12. "createdAt": "2013-03-20T20:31:40.000Z",
  13. "updatedAt": "2013-03-20T20:31:40.000Z",
  14. "userId": 1
  15. }]
  16. }]
  17. */
  18. })

Notice that the accessor (the Tasks property in the resulting instance) is plural because the association is many-to-something.

If an association is aliased (using the as option), you must specify this alias when including the model. Notice how the user's Tools are aliased as Instruments above. In order to get that right you have to specify the model you want to load, as well as the alias:

  1. User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => {
  2. console.log(JSON.stringify(users))
  3. /*
  4. [{
  5. "name": "John Doe",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:45.000Z",
  8. "updatedAt": "2013-03-20T20:31:45.000Z",
  9. "Instruments": [{
  10. "name": "Toothpick",
  11. "id": 1,
  12. "createdAt": null,
  13. "updatedAt": null,
  14. "userId": 1
  15. }]
  16. }]
  17. */
  18. })

You can also include by alias name by specifying a string that matches the association alias:

  1. User.findAll({ include: ['Instruments'] }).then(users => {
  2. console.log(JSON.stringify(users))
  3. /*
  4. [{
  5. "name": "John Doe",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:45.000Z",
  8. "updatedAt": "2013-03-20T20:31:45.000Z",
  9. "Instruments": [{
  10. "name": "Toothpick",
  11. "id": 1,
  12. "createdAt": null,
  13. "updatedAt": null,
  14. "userId": 1
  15. }]
  16. }]
  17. */
  18. })
  19. User.findAll({ include: [{ association: 'Instruments' }] }).then(users => {
  20. console.log(JSON.stringify(users))
  21. /*
  22. [{
  23. "name": "John Doe",
  24. "id": 1,
  25. "createdAt": "2013-03-20T20:31:45.000Z",
  26. "updatedAt": "2013-03-20T20:31:45.000Z",
  27. "Instruments": [{
  28. "name": "Toothpick",
  29. "id": 1,
  30. "createdAt": null,
  31. "updatedAt": null,
  32. "userId": 1
  33. }]
  34. }]
  35. */
  36. })

When eager loading we can also filter the associated model using where. This will return all Users in which the where clause of Tool model matches rows.

  1. User.findAll({
  2. include: [{
  3. model: Tool,
  4. as: 'Instruments',
  5. where: { name: { [Op.like]: '%ooth%' } }
  6. }]
  7. }).then(users => {
  8. console.log(JSON.stringify(users))
  9. /*
  10. [{
  11. "name": "John Doe",
  12. "id": 1,
  13. "createdAt": "2013-03-20T20:31:45.000Z",
  14. "updatedAt": "2013-03-20T20:31:45.000Z",
  15. "Instruments": [{
  16. "name": "Toothpick",
  17. "id": 1,
  18. "createdAt": null,
  19. "updatedAt": null,
  20. "userId": 1
  21. }]
  22. }],
  23. [{
  24. "name": "John Smith",
  25. "id": 2,
  26. "createdAt": "2013-03-20T20:31:45.000Z",
  27. "updatedAt": "2013-03-20T20:31:45.000Z",
  28. "Instruments": [{
  29. "name": "Toothpick",
  30. "id": 1,
  31. "createdAt": null,
  32. "updatedAt": null,
  33. "userId": 1
  34. }]
  35. }],
  36. */
  37. })

When an eager loaded model is filtered using include.where then include.required is implicitly set totrue. This means that an inner join is done returning parent models with any matching children.

Top level where with eagerly loaded models

To move the where conditions from an included model from the ON condition to the top level WHERE you can use the '$nested.column$' syntax:

  1. User.findAll({
  2. where: {
  3. '$Instruments.name$': { [Op.iLike]: '%ooth%' }
  4. },
  5. include: [{
  6. model: Tool,
  7. as: 'Instruments'
  8. }]
  9. }).then(users => {
  10. console.log(JSON.stringify(users));
  11. /*
  12. [{
  13. "name": "John Doe",
  14. "id": 1,
  15. "createdAt": "2013-03-20T20:31:45.000Z",
  16. "updatedAt": "2013-03-20T20:31:45.000Z",
  17. "Instruments": [{
  18. "name": "Toothpick",
  19. "id": 1,
  20. "createdAt": null,
  21. "updatedAt": null,
  22. "userId": 1
  23. }]
  24. }],
  25. [{
  26. "name": "John Smith",
  27. "id": 2,
  28. "createdAt": "2013-03-20T20:31:45.000Z",
  29. "updatedAt": "2013-03-20T20:31:45.000Z",
  30. "Instruments": [{
  31. "name": "Toothpick",
  32. "id": 1,
  33. "createdAt": null,
  34. "updatedAt": null,
  35. "userId": 1
  36. }]
  37. }],
  38. */

Including everything

To include all attributes, you can pass a single object with all: true:

  1. User.findAll({ include: [{ all: true }]});

Including soft deleted records

In case you want to eager load soft deleted records you can do that by setting include.paranoid to false

  1. User.findAll({
  2. include: [{
  3. model: Tool,
  4. where: { name: { [Op.like]: '%ooth%' } },
  5. paranoid: false // query and loads the soft deleted records
  6. }]
  7. });

Ordering Eager Loaded Associations

In the case of a one-to-many relationship.

  1. Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] });
  2. Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] });
  3. Company.findAll({
  4. include: [ { model: Division, as: 'Div' } ],
  5. order: [ [ { model: Division, as: 'Div' }, 'name' ] ]
  6. });
  7. Company.findAll({
  8. include: [ { model: Division, as: 'Div' } ],
  9. order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ]
  10. });
  11. Company.findAll({
  12. include: [ { model: Division, include: [ Department ] } ],
  13. order: [ [ Division, Department, 'name' ] ]
  14. });

In the case of many-to-many joins, you are also able to sort by attributes in the through table.

  1. Company.findAll({
  2. include: [ { model: Division, include: [ Department ] } ],
  3. order: [ [ Division, DepartmentDivision, 'name' ] ]
  4. });

Nested eager loading

You can use nested eager loading to load all related models of a related model:

  1. User.findAll({
  2. include: [
  3. {model: Tool, as: 'Instruments', include: [
  4. {model: Teacher, include: [ /* etc */]}
  5. ]}
  6. ]
  7. }).then(users => {
  8. console.log(JSON.stringify(users))
  9. /*
  10. [{
  11. "name": "John Doe",
  12. "id": 1,
  13. "createdAt": "2013-03-20T20:31:45.000Z",
  14. "updatedAt": "2013-03-20T20:31:45.000Z",
  15. "Instruments": [{ // 1:M and N:M association
  16. "name": "Toothpick",
  17. "id": 1,
  18. "createdAt": null,
  19. "updatedAt": null,
  20. "userId": 1,
  21. "Teacher": { // 1:1 association
  22. "name": "Jimi Hendrix"
  23. }
  24. }]
  25. }]
  26. */
  27. })

This will produce an outer join. However, a where clause on a related model will create an inner join and return only the instances that have matching sub-models. To return all parent instances, you should add required: false.

  1. User.findAll({
  2. include: [{
  3. model: Tool,
  4. as: 'Instruments',
  5. include: [{
  6. model: Teacher,
  7. where: {
  8. school: "Woodstock Music School"
  9. },
  10. required: false
  11. }]
  12. }]
  13. }).then(users => {
  14. /* ... */
  15. })

The query above will return all users, and all their instruments, but only those teachers associated with Woodstock Music School.

Include all also supports nested loading:

  1. User.findAll({ include: [{ all: true, nested: true }]});

Use right join for association

By default, associations are loaded using a left join, that is to say it only includes records from the parent table. You can change this behavior to a right join by passing the right property, if the dialect you are using supports it. Currenly, sqlite does not support right joins.

Note: right is only respected if required is false.

  1. User.findAll({
  2. include: [{
  3. model: Tool // will create a left join
  4. }]
  5. });
  6. User.findAll({
  7. include: [{
  8. model: Tool,
  9. right: true // will create a right join
  10. }]
  11. });
  12. User.findAll({
  13. include: [{
  14. model: Tool,
  15. required: true,
  16. right: true // has no effect, will create an inner join
  17. }]
  18. });
  19. User.findAll({
  20. include: [{
  21. model: Tool,
  22. where: { name: { [Op.like]: '%ooth%' } },
  23. right: true // has no effect, will create an inner join
  24. }]
  25. });
  26. User.findAll({
  27. include: [{
  28. model: Tool,
  29. where: { name: { [Op.like]: '%ooth%' } },
  30. required: false
  31. right: true // because we set `required` to false, this will create a right join
  32. }]
  33. });