つまり User.where(code: [1, 2, nil])
なんて書き方は大丈夫なの?
結論
配列なら大丈夫です、Railsが勝手にやってくれます、速度面だけは少し注意
サブクエリは駄目です
不安な理由: SQLではNULL比較ができない
SQL では IS NULL
を使う必要があり IN 句で NULL は正しく動きません
WHERE code IN (NULL) -- NG: code が NULL のレコードが取れない
WHERE code IS NULL -- OK
確認: 実行SQLを出力する
全部 IN 区に入れず OR code IS NULL
と勝手に NULL 考慮 SQL を作ってくれます
楽だけど where(code: [1, 2, nil])
の実装からは認識しにくい OR
が生えてる
速度のほうは大丈夫?
> User.where(code: [1, 2, nil]).to_sql
=> "SELECT `users`.* FROM `users`
WHERE (`users`.`code` IN ('1', '2') OR `users`.`code` IS NULL)"
注意: サブクエリ
与える配列が他テーブルから取得した場合を考えます
pluck
等で一旦配列にしてから与えると上記の通り考慮されますが
select
でサブクエリとして与えると変換する余地がなく SQL に従い無視されます
User.where(code: Profile.pluck(:code)).to_sql
=> "SELECT `users`.* FROM `users`
WHERE (`users`.`code` IN ('1', '2') OR `users`.`code` IS NULL)"
User.where(code: Profile.pluck(:code)).to_a
=> [#<User: code=1>, #<User: code=2>, #<User: code=nil>]
User.where(code: Profile.select(:code)).to_sql
=> "SELECT `users`.* FROM `users`
WHERE `users`.`code` IN (SELECT `profiles`.`code` FROM `profiles`)"
User.where(code: Profile.select(:code)).to_a
=> [#<User: code=1>, #<User: code=2>] ## code=nil が取れない!
補足: NOT IN でも同じ
User.where.not(id: [1,2, nil]).to_sql
=> "SELECT `users`.* FROM `users` WHERE NOT ((`users`.`id` IN (1, 2) OR `users`.`id` IS NULL))"
感想
記述が少なくて便利なのは裏で色々やっていることの裏返し
ActiveRecord を使いこなすには、どのような SQL が作られるかを知っておきたい