2012-10-18 97 views
26

我有一个大约100个用户的表,我也有一个用户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。

+2

这看起来像一个Rails问题,或者可能是'Pg'宝石的问题;它将空数组视为NULL。非常奇怪 - 而且行为不好。你可以直接用'Pg' gem来测试一个准备好的语句,看它是否以这种方式处理数组参数,或者它是否是Rails/ActiveRecord级别的呢? –

+3

@CraigRinger:这将是一个Rails/ActiveRecord问题,而不是“pg”问题。 AR处理占位符本身。 –

+2

@CraigRinger:有时候,AR人员在SQL方面令人惊讶,他们故意这样做。在这里插入“儿子,我很失望”的形象。 –

回答

21

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 

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

一个空列表(或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代码以检查空数组,并执行需要完成的任何操作以获得您期望的结果。
+2

是的,理智的事情肯定会成为一个例外。 –

+1

@CraigRinger:我像往常一样,责怪MySQL :) –

+0

我现在明白为什么我的测试失败。 :) –

30

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

参考文献:

+0

在一个模糊的相关说明中,User.where(:id => [[],[3]])给你无效的sql:“SELECT'users'。* FROM'users' WHERE'users'.'id' IN( ,3)“ –

6
User.where('id NOT IN (?)', ids+[0]) 
+1

请考虑增加一个澄清评论为***为什么这可能是正确的答案。 –

+0

@DavidL:这确保数组不会为空。这也假设没有值为0的id(在Rails中通常是这种情况)。 – bragboy

2

使用Ruby的活动记录包装:

User.where.not(id: []) 

此为您处理空数组问题。

1

我不知道这是否是问题所在,但我来到这里是为了找到所有具有空(序列化)数组属性的记录。我解决它为Rails 5.0这样的:

User.where(my_array_attribute: nil) 

或为逆:

User.where.not(my_array_attribute: nil)