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

Rob*_*ert 30 postgresql activerecord ruby-on-rails arel ruby-on-rails-3

我有一个大约100个用户的表,我也有一个用户ID数组.我想要做的是显示所有不属于此用户ID数组的用户.当我做这样的事情

 User.where('id NOT IN (?)', [9, 2, 3, 4])
Run Code Online (Sandbox Code Playgroud)

它成功返回用户id不属于该数组的记录.但是,如果该数组是空的,那么

 User.where('id NOT IN (?)', [])
Run Code Online (Sandbox Code Playgroud)

它不会返回任何用户,SQL查询看起来像这样

 SELECT "users".* FROM "users" WHERE (id NOT IN (NULL))
Run Code Online (Sandbox Code Playgroud)

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

Gui*_*iGS 31

在Rails 4中,您可以使用User.where.not(id: [])它来获得正确的结果.它产生:

SELECT "users".* FROM "users" WHERE (1 = 1)
Run Code Online (Sandbox Code Playgroud)

不幸的是User.where('id NOT IN (?)', [])应该是等价的,但事实并非如此 它仍然会给你错误的结果:

SELECT "users".* FROM "users" WHERE (id NOT IN (NULL))
Run Code Online (Sandbox Code Playgroud)

参考文献:


mu *_*ort 25

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
Run Code Online (Sandbox Code Playgroud)

然后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
Run Code Online (Sandbox Code Playgroud)

一个空数组将满足所有四个条件,c.quote(nil)以及你的NULL来自哪里.所有导致的特殊逻辑c.quote(nil)表明这是故意行为.

用空列表说IN(或NOT IN):

where c in ()
Run Code Online (Sandbox Code Playgroud)

应该产生一个SQL错误,所以也许AR人们试图通过悄悄地将那个糟糕的SQL转变为阻止它c in (null).请注意,这些都不是:

select ... from t where c in (null);
select ... from t where c not in (null);
Run Code Online (Sandbox Code Playgroud)

应该由于SQL的NULL行为而产生任何结果.这是一个经典的新手错误,AR人真的应该知道更好.

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


执行摘要:

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

  • 是的,理智的东西*肯定*在这里是一个例外. (2认同)

小智 6

User.where('id NOT IN (?)', ids+[0])
Run Code Online (Sandbox Code Playgroud)

  • 请考虑添加关于 *** 为什么*** 这可能是正确答案的澄清评论。 (2认同)