将两个 ActiveRecord::Relation 对象合并

203

假设我有以下两个对象:

first_name_relation = User.where(:first_name => 'Tobias') # ActiveRecord::Relation
last_name_relation  = User.where(:last_name  => 'Fünke') # ActiveRecord::Relation

是否有可能将这两个条件组合成一个ActiveRecord::Relation对象?

注意:我知道我可以链接多个where来实现这个行为,但我真正感兴趣的是在拥有两个不同的ActiveRecord::Relation对象的情况下。


可能会有帮助:ActiveRecord OR 查询哈希符号表示法 - potashin
10个回答

257

如果你想要使用AND(交集)来组合,可以使用merge

first_name_relation.merge(last_name_relation)

如果你想要使用OR(并集)进行组合,请使用or

first_name_relation.or(last_name_relation)

仅适用于 ActiveRecord 5+;对于4.2版本,请安装where-or后移植此功能。


1
@NewAlexandria 是的,所有情况下,Rails都在欺骗你关于对象的类。 - Andrew Marshall
78
有没有“OR合并”的版本? - Arcolye
8
可能是因为合并你的两个关系的实际结果就是这样。请注意,merge是一个交集操作,而不是并集操作。 - Andrew Marshall
5
请注意,#or方法已于2015年1月添加到ActiveRecord::Relation,并将成为Rails 5.0的一部分,预计在2015年末发布。该方法允许使用OR运算符来组合WHERE或HAVING子句。如果您需要在正式发布之前使用它,请查看HEAD。有关详细信息,请参见合并拉取请求#16052 @Arcolye @AndrewMarshall @Aldo-xoen-Giambelluca。 - Claudio Floreani
2
这个#or正是我所需要的。在我的Rails 4项目中,我扩展了ActiveRecord::Relation来支持#or,而不是使用gem。假设模型相同:klass.from("(#{to_sql} union #{other_relation.to_sql}) as #{table_name}") - M. Wyatt
显示剩余9条评论

42
关系对象可以转换为数组。这将使其无法在之后使用任何 ActiveRecord 方法,但我不需要这样做。我这样做了:
name_relation = first_name_relation + last_name_relation

Ruby 1.9,Rails 3.2


更新:遗憾的是,这个无法按日期合并。 - Daniel Morris
80
它并不是一个数组,而是将你的关系转换为数组。因此,你不能再像使用.where()一样在其上使用ActiveRecord方法了... - Augustin Riedinger
2
如果您不关心返回类型是关系,可以使用 first_name_relation | last_name_relation 将多个结果组合在一起。 "|" 运算符也适用于多个关系。结果值是一个数组。 - MichaelZ
14
可以将这两个条件组合成一个 ActiveRecord::Relation 对象吗?这个答案返回的是一个数组... - courtsimas
这是许多情况下的绝佳解决方案。如果您只需要记录的枚举以进行循环(例如,您不关心它是数组还是ActiveRecord :: Relation),并且不介意两个查询的开销,则此解决方案可以用明显、简单的语法解决问题。真希望我能给它+2! - David Hempy

24

merge实际上不像OR那样工作。它只是交集(AND)。

我曾为将两个ActiveRecord::Relation对象合并成一个而苦苦挣扎,但没有找到任何适合我的解决方案。

与其搜索正确的方法来创建这两组的联合,我更注重集合代数。您可以使用德摩根定律以不同的方式完成它。

ActiveRecord提供了merge方法(AND),您还可以使用not方法或none_of(NOT)。

search.where.none_of(search.where.not(id: ids_to_exclude).merge(search.where.not("title ILIKE ?", "%#{query}%")))

你有这样一个式子 (A ∪ B)' = A' ∩ B'

更新: 上面的解法可以用于更复杂的情况。 在你的情况下,类似于以下内容就足够了:

User.where('first_name LIKE ? OR last_name LIKE ?', 'Tobias', 'Fünke')

none_of 实际上似乎是第三方 active_record_extended gem 的一部分。 - Wolfgang

16

即使在许多奇怪的情况下,我也能够通过使用Rails内置的Arel来完成这些操作。

User.where(
  User.arel_table[:first_name].eq('Tobias').or(
    User.arel_table[:last_name].eq('Fünke')
  )
)

使用Arel的or将两个ActiveRecord关系合并。


如在此处所建议的那样使用合并(merge)对我没有用。它从结果中删除了第二组关系对象。


2
这是我认为最好的答案。对于OR类型的查询,它可以完美地工作。 - thekingoftruth
谢谢指出这一点,对我帮助很大。其他答案仅适用于少量字段,但对于上万个IN语句中的ID,性能可能非常差。 - onetwopunch
1
@KeesBriggs — 这不是真的。我在所有版本的Rails 4中都使用过它。 - 6ft Dan

15

有一个名为active_record_union的宝石可能是你正在寻找的。

它的使用示例如下:

current_user.posts.union(Post.published)
current_user.posts.union(Post.published).where(id: [6, 7])
current_user.posts.union("published_at < ?", Time.now)
user_1.posts.union(user_2.posts).union(Post.published)
user_1.posts.union_all(user_2.posts)

1
感谢分享。即使是在您发布四年后,这仍然是我创建ransackacts-as-taggable-on联合并保持我的ActiveRecord实例完整的唯一解决方案。 - Benjamin Bojko
当使用这个 gem 时,如果你在模型上定义了作用域,那么在调用 union() 方法时可能不会返回 ActiveRecord::Relation。请参阅 Readme 中的 ActiveRecord 联合状态。 - dechimp

14

如果您使用pluck获取每个记录的标识符,将数组连接在一起,然后最终对这些连接的ID进行查询,那么以下是我如何“处理”它:

  transaction_ids = @club.type_a_trans.pluck(:id) + @club.type_b_transactions.pluck(:id) + @club.type_c_transactions.pluck(:id)
  @transactions = Transaction.where(id: transaction_ids).limit(100)

6
如果您有一组活动记录关系数组并想将它们合并,您可以这样做:
array.inject(:merge)

在 Rails 5.1.4 中,array.inject(:merge) 不能用于 ActiveRecord 关联数组。但是,array.flatten! 可以使用。 - zhisme

1
在不兼容使用“或”的情况下,我会使用类似以下代码来获取ActiveRecord_Relation对象:
User.from("(#{complex_raw_query} UNION #{complex_raw_query}) AS users")

这对我有用,使用了作用域和查询中的.to_sql(在获取到“传递给#or的关系必须在结构上兼容。不兼容的值:[:joins]”之后)。User.from("(#{scope_returning_users_1.to_sql} UNION #{scope_returning_users_2.to_sql}) AS users") - caroline

0
希望这对某些人有用——您可以进行第二个查询以通过ID查找匹配项:
ids = last_name_relation.ids + first_name_relation.ids
User.where(id: ids)

我意识到这可能不是最有效的方法,需要进行3个数据库请求,但它可以完成工作并且易于理解。


0

暴力破解:

first_name_relation = User.where(:first_name => 'Tobias') # ActiveRecord::Relation
last_name_relation  = User.where(:last_name  => 'Fünke') # ActiveRecord::Relation

all_name_relations = User.none
first_name_relation.each do |ar|
  all_name_relations.new(ar)
end
last_name_relation.each do |ar|
  all_name_relations.new(ar)
end

在Rails3中,即使将MyModel.none更改为MyModel.where(1=2),由于Rails3没有'none'方法,仍会出现NoMethodError(#MyModel:0x...的undefined method 'stringify_keys')。 - JosephK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接