sequelize-typescript の JOIN で キー項目を指定する方法

sequelize-typescript の JOIN で キー項目を指定する方法 技術ネタ

sequelize-typescript で JOIN する時に、JOINキーを指定する方法でわからず悩みました。その方法について紹介したいと思います。



やりたかったこと

従業員テーブルと部署テーブルの2テーブルをJOINし、従業員と所属する部署名を表示することが目標で、具体的には、以下のSQLを sequelize-typescript で生成することでした。

SELECT
  e.employee_no,e.employee_name,d.department_name
FROM
  employee e 
  INNER JOIN department d
    ON e.department_no=d.department_no
+-------------+---------------+-----------------+
| employee_no | employee_name | department_name |
+-------------+---------------+-----------------+
|          10 | 従業員A       | 人事部          |
|          11 | 従業員B       | 人事部          |
|          12 | 従業員C       | 開発部          |
+-------------+---------------+-----------------+

従業員テーブル

CREATE TABLE `employee` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `employee_no` int(10) unsigned NOT NULL UNIQUE COMMENT '従業員番号',
  `employee_name` varchar(20) NOT NULL COMMENT '従業員名',
  `department_no` int NOT NULL COMMENT '部署番号'
)  COMMENT='従業員テーブル';
select * from employee;
+----+-------------+---------------+---------------+
| id | employee_no | employee_name | department_no |
+----+-------------+---------------+---------------+
|  1 |          10 | 従業員A       |           600 |
|  3 |          11 | 従業員B       |           600 |
|  4 |          12 | 従業員C       |           500 |
+----+-------------+---------------+---------------+

部署テーブル

CREATE TABLE `department` (
  `id` int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `department_no` int unsigned NOT NULL UNIQUE COMMENT '部署番号',
  `department_name` varchar(20) NOT NULL COMMENT '部署名'
)  COMMENT='部署テーブル';
select * from department;
+----+---------------+-----------------+
| id | department_no | department_name |
+----+---------------+-----------------+
|  1 |           500 | 開発部          |
|  2 |           600 | 人事部          |
+----+---------------+-----------------+

最初にはまったこと

最初は、ココ に書いてある通り、従業員テーブル(employee)に BelongsTo を設定し、JOINしてみたところ、なぜかSQLが
`employee`.`department_no` = `department`.`id`
と生成され、部署テーブル(department)側のJOINキーが department_no にらず、 id となってしまいました。

具体的なソースは以下になります。

従業員テーブル用のmodel

@Table({ modelName: "employee" })
export class Employee extends Model {
  @PrimaryKey
  @Column({ type: DataType.INTEGER, field: "id" })
  public id: number;

  @AllowNull(false)
  @Unique
  @Column({ type: DataType.INTEGER, field: "employee_no" })
  public employeeNo: number;

  @Column({ type: DataType.STRING, field: "employee_name" })
  public employeeName: string;

  @ForeignKey(() => Department)
  @AllowNull(false)
  @Column({ type: DataType.INTEGER, field: "department_no" })
  public departmentNo: number;

  @BelongsTo(() => Department, "department_no")
  public department: Department;
}

部署テーブル用のmodel

@Table({ modelName: "department" })
export class Department extends Model {
  @PrimaryKey
  @Column({ type: DataType.INTEGER, field: "id" })
  public id: number;

  @AllowNull(false)
  @Unique
  @Column({ type: DataType.INTEGER, field: "department_no" })
  public departmentNo: number;

  @Column({ type: DataType.STRING, field: "department_name" })
  public departmentName: string;
}

実行したコード

Employee.findAll({
  include: [
    {
      model: Department,
      required: true
    }
  ]
}).then((result: any[]) => {
  console.log(JSON.stringify(result, null, "  "));
});

生成されたSQL

SELECT
  `employee`.`id`,
  `employee`.`employee_no` AS `employeeNo`,
  `employee`.`employee_name` AS `employeeName`,
  `employee`.`department_no` AS `departmentNo`,
  `employee`.`department_no`,
  `department`.`id` AS `department.id`,
  `department`.`department_name` AS `department.department_name`
FROM
  `employee` AS `employee`
  INNER JOIN `department` AS `department`
    ON `employee`.`department_no` = `department`.`id`

正しいSQLが生成されるやり方

JOINするキーを指定する方法は、@BelongsTo で targetKey に キー項目を記述する ことで実現できます。

具体的な修正後のコードは以下となります。

修正後の従業員テーブル用のmodel

@Table({ modelName: "employee" })
export class Employee extends Model {
  @PrimaryKey
  @Column({ type: DataType.INTEGER, field: "id" })
  public id: number;

  @AllowNull(false)
  @Unique
  @Column({ type: DataType.INTEGER, field: "employee_no" })
  public employeeNo: number;

  @Column({ type: DataType.STRING, field: "employee_name" })
  public employeeName: string;

  @ForeignKey(() => Department)
  @AllowNull(false)
  @Column({ type: DataType.INTEGER, field: "department_no" })
  public departmentNo: number;

  // @BelongsTo(() => Department, "department_no")
  @BelongsTo(() => Department, {
    targetKey: "departmentNo",
    keyType: DataType.INTEGER
  })
  public department: Department;
}

生成されたSQL

SELECT
  `employee`.`id`,
  `employee`.`employee_no` AS `employeeNo`,
  `employee`.`employee_name` AS `employeeName`,
  `employee`.`department_no` AS `departmentNo`,
  `department`.`id` AS `department.id`,
  `department`.`department_name` AS `department.department_name`
FROM
  `employee` AS `employee`
  INNER JOIN `department` AS `department`
    ON `employee`.`department_no` = `department`.`department_no`

最後に

sequelize-typescript の日本語で書かれた情報が少なく、行き詰まることが多いですが、今後も悩んで解決したことを公開していきたいと思います。