【Sequelize】ハマったポイントのメモ

Sequelizeを少し使ってみて、いろいろハマったところがあったので、そのメモ。
version: ``Sequelize CLI [Node: 16.13.1, CLI: 6.3.0, ORM: 6.12.0-beta.1]``
  
# migrationでファイルを指定してundoするときの注意点

``db:migrate:undo:all``に``--to``オプションを付けてファイル名を指定すると、「そのファイルまで」undoされる。
例えば、migrationパス配下にA、B、Cの3ファイルがあって、A→B→Cの順に作成していたと仮定して、``--to A``と指定すると、AだけでなくBもCもundoされる点に注意。
決して「そのファイルの分だけをundoする」という意味ではない。
※基本的には[ここ](https://sequelize.org/master/manual/migrations.html#undoing-migrations)に載ってる

# sequelizeオブジェクトのつくりかた

``sequelize.query``とかを使うために``sequelize``オブジェクトを生成する場合、``Sequelize``のコンストラクタを使うのだが、その際引数の最後にconfigオブジェクトを渡す必要がある。
（``/models/index``の中身見れば載ってるんだけど。）

```javascript
...
const { Sequelize } = require('../models/index');
const config = require(__dirname + '/../config/config.json')[process.env['NODE_ENV']];

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}
...
```


# DATEやTIMESTAMPの項目をto_charで書式指定して文字列に変換しつつwhere句で指定する

``sequelize.fn``とか``sequelize.col``とかを駆使するのだが、DATEやTIMESTAMPを相手に、``to_char``で書式変換する場合は、``sequelize.col``の後ろに書式を指定するための第三引数を指定する必要がある。
他は試してないけど恐らくそれ以上の複数の(可変長の)引数を要する関数の場合は、同じ要領で後ろに引数をつなげていけばよいのだと思われる。

```javascript
await db.user.findAll({
            attributes:['name','birthday'],
            where: {
                where: sequelize.where(sequelize.fn('to_char', sequelize.col('birthday'), 'YYYY-MM-DD'), '2021-01-02')
            }
        });
```

※基本的には[ここ](https://sequelize.org/master/manual/model-querying-basics.html#advanced-queries-with-functions--not-just-columns-)に載ってる



# INNER JOINを実現する

[SequelizeのManual](https://sequelize.org/master/manual/eager-loading.html#required-eager-loading)を見るとなんかいろいろと小難しいこと書かれているが、個人的には[こちらのstackoverflowの解説](https://stackoverflow.com/questions/42226351/sequelize-join-with-multiple-column)が一番ぴったりはまって分かりやすかった。
要するに、includeは使うのだが、その中にonというフィールドを定義して、onフィールド内に結合条件(``INNER JOIN XXX X ON A.COL1 = X.COL1``の``ON A.COL1 = X.COL1``にあたる部分)を記述すればよいというものだ。
SQLを記述するときの感覚に近く書けるので、SQLに慣れてる人ほどこっちのほうがわかりやすいと思う。

```javascript
        const data = await db.aaa.findAll({
            include: [{
                model: db.xxx,
                as: 'xxx',
                on: {
                    col1: sequelize.where(sequelize.col('aaa.col1'),'=',sequelize.col('xxx.col1'))
                },
                attributes: []
            }],
        });
```

``attributes: []``は上のstackoverflowにならっていれている。
最初は結合したテーブルのカラムを記述していたのだが、指定してすらいない（なんならテーブルに存在すらしない）「id」というSequelizeのデフォルトの主キーカラムを指して「xxxテーブルに"id"が存在しません」というエラーが発生したので、回避する目的で意図的にattibutesフィールドに空配列を指定している。

ただ正直この程度の結合条件を書くのにSequelizeの使い方を学ぶくらいなら素直にSQLを書いた方が絶対早いし今後のためになると思う…（後から振り返って、これに時間使ったのが馬鹿らしくなった）

# sequelize.queryにバインド変数を使う場合に日付書式でコロンを同居させる方法

例えば以下のようなコードがあったとして

```javascript
const result = await sequelize.query(`select aaa, 
                                             bbb,
                                             to_char(ccc,'yyyy-MM-dd HH24:MI:SS') ccc
                                      from xxx
                                      where ddd = :ddd`,
                                      {
                                         replacements: {ddd: ddd},
                                         type: QueryTypes.SELECT,
                                      }
                                     );
```

これは実行時にエラーになる。
というのも``to_char(ccc,'yyyy-MM-dd HH24:MI:SS')``で指定している日付書式の``:MI``や``:SS``の部分が意図せずバインド変数とみなされてしまい、一方で``replacements``の指定には「MI」というバインド変数の指定がないため、エラーになるというオチ。
言われてみればそうなのだが、どうすればよいのかわからなかった。

こういう場合はコロンによるバインド変数の指定はあきらめて、「？」とか「_」などを使った別の書式の変数指定を試みるしかない。
基本的には[ここ](https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html#instance-method-query)に載ってる。
上の例で行くと

```javascript
const result = await sequelize.query(`select aaa, 
                                             bbb,
                                             to_char(ccc,'yyyy-MM-dd HH24:MI:SS') ccc
                                      from xxx
                                      where ddd = ?`,
                                      {
                                         replacements: ['ddd'],
                                         type: QueryTypes.SELECT,
                                      }
                                     );
```

とかにすれば動く。
上のリンク先のマニュアルに沿う限りでは先頭に"_"（アンダースコア）を付与したものをパラメータとして扱う方法もあるらしいが試していない。

ちなみに````to_char(ccc,'yyyy-MM-dd HH24'||':'||'MI'||':'||'SS')````とかして、コロンと日付書式指定文字を無理やり離せば、最初の例のまま（コロン付きのバインド変数を使った状態）でも動くっちゃ動く。一応。でもここまでしてこだわる必要は…

# findAllの結果がなぜか空オブジェクトになる

テーブルに対してfindAllでクエリを投げると、結果が帰ってきている節はあるのだが、中身がなぜか空オブジェクトになる問題。
この現象は「起きるテーブル」と「起きないテーブル」がある。
つまりfindAllを使うときは必ずこの問題が起きるというわけではない。

例えばaaaテーブルに以下のようにデータが入ってるとする


```sh
postgres=# select * from aaa;
 id | name |         createdAt          |         updatedAt          
----+------+----------------------------+---------------------------
  1 | test | 2021-12-15 16:48:49.083+00 | 2021-12-15 16:48:49.083+00
  2 | test | 2021-12-15 16:49:23.112+00 | 2021-12-15 16:49:23.112+00
(2 row)
```

このテーブルに対して以下のようなクエリを投げるとする

```javascript
const data = await db.aaa.findAll({
    where: {
        name: 'test'
    }
});
```

結果の「data」オブジェクトの中身を``JSON.stringify(data)``で見てみると、何故か以下のようになっている

```json
[{},{}]
```

なんとなく「2件引っかかった」という様子は見てとれるのだが、2件とも中身が空オブジェクトで、取得したデータの内容が把握できない。

この件、「起きるテーブルと起きないテーブルがある」というのが厄介で、発生する条件が掴めていない。
実際、同じ環境の別テーブルに同様の条件を再現して、ほぼ同じクエリを投げてみたら再現しなかった＝ちゃんと中身が入ったオブジェクトが返ってきた。

色々調べたのだが、個人的に調べた範疇の情報では、これは解決しなかった。
一応参考までに載せておくと、下記のようなところに同じ問題で悩んでいる人を何人か見つけた。
https://github.com/sequelize/sequelize/issues/7667
https://lzomedia.com/blog/sequelize-findall-empty-but-there-is-info/

1点目のGithub Issueは2017年のものだが、そこでのやり取り「別のissueあげてくれ」と言われており、以後の状況を追跡できていない。
しかし2点目のほうは2021年9月の記事なので、同様の問題は引き続き起きている（解決していない）と思われる。
私の場合は、やりたいことをSQLべた書きして``sequelize.query``を使うことで回避した。
というかsequelizeを使うユースケースは大抵の場合自分でSQL書けばなんとかなる。
この問題にぶち当たったら素直にSQLべた書きに作戦変更したほうが良いと思われる。（なんのためにsequelize採用したのって気にはなるが…）