16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rails は IN 句に NULL を入れて良いのか?

Last updated at Posted at 2019-11-18

つまり 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 が作られるかを知っておきたい

参考

16
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?