SequelizeでINNER JOINとLEFT OUTER JOINを試してみる

JOINについて調べてみました。DBはMySQLです。

結果はrequired: false を設定するとLEFT OUTER JOIN、 required: true を設定するとINNER JOINになりました。

データ

この様なデータをDBに入れてLEFT OUTER JOINとINNER JOINの挙動を確認してみます。

has_books_userというユーザーにはbookのレコードが二つ紐付いており、has_not_book_userはbookのレコードが紐付いていません。

Userテーブル

id name createdAt updatedAt
1 has_books_user 2018-03-23 15:18:49 2018-03-23 15:18:49
2 has_not_book_user 2018-03-23 15:18:49 2018-03-23 15:18:49

Bookテーブル

id name userId createdAt updatedAt
1 エベレストを越えて 1 2018-03-28 04:46:27 2018-03-28 04:46:27
2 青春を山に賭けて 1 2018-03-28 04:46:27 2018-03-28 04:46:27

アソシエーションの設定

事前にhasMany(hasOne)やbelongsToの設定をしておきます。

userモデル

'use strict';
module.exports = (sequelize, DataTypes) => {
  var User = sequelize.define('User', {
    name: DataTypes.STRING
  }, {});
  User.associate = function(models) {
    User.hasMany(models.Book, {foreignKey: 'userId'});
  };
  return User;
};

bookモデル

'use strict';
module.exports = (sequelize, DataTypes) => {
  var Book = sequelize.define('Book', {
    name: DataTypes.STRING,
    userId: DataTypes.INTEGER
  }, {});
  Book.associate = function(models) {
    Book.belongsTo(models.User);
  };
  return Book;
};

LEFT OUTER JOIN

db.User.findAll({
    where: {
        id: [1,2]
    },
    raw: true,
    include: [{
        model: db.Book,
        required: false
    }]
}).then((users)=>{
    console.log(users);
})

SQL

SELECT `User`.`id`, `User`.`name`, `User`.`createdAt`, `User`.`updatedAt`, `Books`.`id` AS `Books.id`, `Books`.`name` AS `Books.name`, `Books`.`userId` AS `Books.userId`, `Books`.`createdAt` AS `Books.createdAt`, `Books`.`updatedAt` AS `Books.updatedAt`, `Books`.`UserId` AS `Books.UserId`
    FROM `Users` AS `User`
    LEFT OUTER JOIN `Books` AS `Books` ON `User`.`id` = `Books`.`userId`
    WHERE `User`.`id` IN (1, 2);

結果

LEFT OUTER JOINの為、bookのレコードを持っていないuserも出力されています。

[ { id: 1,
    name: 'has_books_user',
    createdAt: 2018-03-23T15:18:49.000Z,
    updatedAt: 2018-03-23T15:18:49.000Z,
    'Books.id': 1,
    'Books.name': 'エベレストを越えて',
    'Books.userId': 1,
    'Books.createdAt': 2018-03-28T04:46:27.000Z,
    'Books.updatedAt': 2018-03-28T04:46:27.000Z,
    'Books.UserId': 1 },
  { id: 1,
    name: 'has_books_user',
    createdAt: 2018-03-23T15:18:49.000Z,
    updatedAt: 2018-03-23T15:18:49.000Z,
    'Books.id': 2,
    'Books.name': '青春を山に賭けて',
    'Books.userId': 1,
    'Books.createdAt': 2018-03-28T04:46:27.000Z,
    'Books.updatedAt': 2018-03-28T04:46:27.000Z,
    'Books.UserId': 1 },
  { id: 2,
    name: 'has_not_book_user',
    createdAt: 2018-03-23T15:18:49.000Z,
    updatedAt: 2018-03-23T15:18:49.000Z,
    'Books.id': null,
    'Books.name': null,
    'Books.userId': null,
    'Books.createdAt': null,
    'Books.updatedAt': null,
    'Books.UserId': null } ]

INNER JOIN

db.User.findAll({
    where: {
        id: [1,2]
    },
    raw: true,
    include: [{
        model: db.Book,
        required: true
    }]
}).then((users)=>{
    console.log(users);
})

SQL

SELECT `User`.`id`, `User`.`name`, `User`.`createdAt`, `User`.`updatedAt`, `Books`.`id` AS `Books.id`, `Books`.`name` AS `Books.name`, `Books`.`userId` AS `Books.userId`, `Books`.`createdAt` AS `Books.createdAt`, `Books`.`updatedAt` AS `Books.updatedAt`, `Books`.`UserId` AS `Books.UserId`
    FROM `Users` AS `User`
    INNER JOIN `Books` AS `Books` ON `User`.`id` = `Books`.`userId`
    WHERE `User`.`id` IN (1, 2);

結果

INNER JOINの為、bookのレコードを持っていないuserは出力されていません。

[ { id: 1,
    name: 'has_books_user',
    createdAt: 2018-03-23T15:18:49.000Z,
    updatedAt: 2018-03-23T15:18:49.000Z,
    'Books.id': 1,
    'Books.name': 'エベレストを越えて',
    'Books.userId': 1,
    'Books.createdAt': 2018-03-28T04:46:27.000Z,
    'Books.updatedAt': 2018-03-28T04:46:27.000Z,
    'Books.UserId': 1 },
  { id: 1,
    name: 'has_books_user',
    createdAt: 2018-03-23T15:18:49.000Z,
    updatedAt: 2018-03-23T15:18:49.000Z,
    'Books.id': 2,
    'Books.name': '青春を山に賭けて',
    'Books.userId': 1,
    'Books.createdAt': 2018-03-28T04:46:27.000Z,
    'Books.updatedAt': 2018-03-28T04:46:27.000Z,
    'Books.UserId': 1 } ]