約100人のユーザーのテーブルがあり、ユーザーIDの配列もあります。私がやりたかったのは、このユーザーIDの配列の一部ではないすべてのユーザーを表示することです。私がこのようなことをするとき
User.where('id NOT IN (?)', [9, 2, 3, 4])
ユーザーのIDがその配列に属していないレコードを正常に返します。ただし、その配列がそのように空の場合
User.where('id NOT IN (?)', [])
ユーザーは返されません。SQLクエリは次のようになります。
SELECT "users".* FROM "users" WHERE (id NOT IN (NULL))
なぜこれが起こるのか、またはこれがバグである可能性があるのか誰かが知っていますか?私はPostgreSQLでRails 3.2.5を使用しています。
ActiveRecord(少なくとも3.2.1)は空の配列をNULLとして扱います。 where
呼び出しのプレースホルダーは、 _sanitize_sql
_ によって処理されます。コードを少しトレースすると、次のようになります _replace_bind_variables
_ :
_def replace_bind_variables(statement, values) #:nodoc:
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
bound = values.dup
c = connection
statement.gsub('?') { quote_bound_value(bound.shift, c) }
end
_
そして _quote_bound_value
_ :
_def quote_bound_value(value, c = connection) #:nodoc:
if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
else
value.map { |v| c.quote(v) }.join(',')
end
else
c.quote(value)
end
end
_
空の配列は、c.quote(nil)
に到達するための4つの条件すべてを満たし、そこからNULLが発生します。 c.quote(nil)
につながるすべての特別なロジックは、これが意図的な動作であることを示しています。
空のリストでIN(またはNOT IN)と言う:
_where c in ()
_
sQLエラーが発生するはずなので、ARの人々は、その悪いSQLを静かにc in (null)
に変換することで、それを防ごうとしているのかもしれません。これらのどちらにも注意してください:
_select ... from t where c in (null);
select ... from t where c not in (null);
_
sQLのNULLの動作により、結果が生成されるはずです。これは古典的な初心者の間違いであり、ARの人々は本当によく知っているはずです。
私自身は例外を好みます。足の弾丸を配備しようとしていると言った方が、別の銃を渡すよりもはるかに友好的です。
エグゼクティブサマリー:
where('c in (?)', [])
またはwhere('c not in (?)', [])
を試してはいけません。Rails 4では、User.where.not(id: [])
を使用すると、正しい結果が得られます。次のようになります。
_SELECT "users".* FROM "users" WHERE (1 = 1)
_
残念ながら、User.where('id NOT IN (?)', [])
は同等であるはずですが、そうではありません。それでも間違った結果が得られます:
_SELECT "users".* FROM "users" WHERE (id NOT IN (NULL))
_
参照:
User.where('id NOT IN (?)', ids+[0])
Rubyのアクティブレコードラッパーを使用します。
User.where.not(id: [])
これにより、空の配列の問題が処理されます。
これが求められている問題であるかどうかはわかりませんが、空の(シリアル化された)配列属性を持つすべてのレコードを見つけるためにここに来ました。私はそれをRails 5.0のように解決しました:
User.where(my_array_attribute: nil)
またはその逆の場合:
User.where.not(my_array_attribute: nil)