检索具有空数组的记录时出现问题

我有一个大约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)) 

有谁知道为什么会发生这种情况或者这可能是一个错误? 我正在使用Rails 3.2.5和PostgreSQL。

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) ,这就是您的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人真的应该知道更好。

我自己更喜欢一个例外:告诉我,我即将部署一个脚踩子弹会比给我一把不同的枪更友好。


执行摘要

  1. 这种“空数组意味着NULL”的行为是故意的。
  2. 你永远不应该尝试where('c in (?)', [])where('c not in (?)', [])因为这两个陈述都没有多大意义。
  3. 更新您的Ruby代码以检查空数组并执行任何需要执行的操作以获得您期望的结果。

在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)