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する」という意味ではない。
※基本的にはここに載ってる
sequelizeオブジェクトのつくりかた
sequelize.query
とかを使うためにsequelize
オブジェクトを生成する場合、Sequelize
のコンストラクタを使うのだが、その際引数の最後にconfigオブジェクトを渡す必要がある。
(/models/index
の中身見れば載ってるんだけど。)
...
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
の後ろに書式を指定するための第三引数を指定する必要がある。
他は試してないけど恐らくそれ以上の複数の(可変長の)引数を要する関数の場合は、同じ要領で後ろに引数をつなげていけばよいのだと思われる。
await db.user.findAll({
attributes:['name','birthday'],
where: {
where: sequelize.where(sequelize.fn('to_char', sequelize.col('birthday'), 'YYYY-MM-DD'), '2021-01-02')
}
});
※基本的にはここに載ってる
INNER JOINを実現する
SequelizeのManualを見るとなんかいろいろと小難しいこと書かれているが、個人的にはこちらのstackoverflowの解説が一番ぴったりはまって分かりやすかった。
要するに、includeは使うのだが、その中にonというフィールドを定義して、onフィールド内に結合条件(INNER JOIN XXX X ON A.COL1 = X.COL1
のON A.COL1 = X.COL1
にあたる部分)を記述すればよいというものだ。
SQLを記述するときの感覚に近く書けるので、SQLに慣れてる人ほどこっちのほうがわかりやすいと思う。
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にバインド変数を使う場合に日付書式でコロンを同居させる方法
例えば以下のようなコードがあったとして
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」というバインド変数の指定がないため、エラーになるというオチ。
言われてみればそうなのだが、どうすればよいのかわからなかった。
こういう場合はコロンによるバインド変数の指定はあきらめて、「?」とか「_」などを使った別の書式の変数指定を試みるしかない。
基本的にはここに載ってる。
上の例で行くと
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テーブルに以下のようにデータが入ってるとする
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)
このテーブルに対して以下のようなクエリを投げるとする
const data = await db.aaa.findAll({
where: {
name: 'test'
}
});
結果の「data」オブジェクトの中身をJSON.stringify(data)
で見てみると、何故か以下のようになっている
[{},{}]
なんとなく「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採用したのって気にはなるが…)