最初,当我试图根据项目的确切内容(:name_id
)和:name_id
的频率,为在给定日期集之间具有Order
开始的所有Items
构建直方图时,我使用了以下代码:
dates = ["May 27, 2016", "May 30, 2016"]
items = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last)
histogram = {}
items.pluck(:name_id).uniq.each do |name_id|
histogram[name_id] = items.where(name_id:name_id).count
end
这个代码运行良好。
然而,现在,我正试图构建一个更具扩展性的直方图。我仍然想捕捉一段时间内:name_id
的频率,但现在我想通过Order
的开始和结束来绑定该时间。但是,我在组合查询后面的ActiveRecord关系时遇到了问题。具体来说,如果我的查询如下:
items_a = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last)
items_b = Item.joins(:order).where("orders.end >= ?", dates.first).where("orders.end <= ?", dates.last)
如何连接这两个查询,以便下面作用于查询对象的代码仍然有效
items.pluck(:name_id).each do |name_id|
histogram[name_id] = items.where(name_id:name_id).count
end
我尝试过的:
+
,但这当然不起作用,因为它将结果变成了一个数组,而像pluck
这样的方法不起作用:
(items_a + items_b).pluck(:name_id)
=> error
merge
,这似乎是所有SO的答案所说的。。。但它对我不起作用,因为正如文档所说,merge
计算出了交集,所以我的结果是这样的:
items_a.count
=> 100
items_b.count
=> 30
items_a.merge(items_b)
=> 15
仅供参考,目前,我已经用下面的猴子修补了这个,但它不是很理想。谢谢你的帮助!
name_ids = (items_a.pluck(:name_id) + items_b.pluck(:name_id)).uniq
name_ids.each do |name_id|
# from each query object, return the ids of the item objects that meet the name_id criterion
item_object_ids = items_a.where(name_id:name_id).pluck(:id) + items_b.where(name_id:name_id).pluck(:id) + items_c.where(name_id:name_id).pluck(:id)
# then check the item objects for duplicates and then count up. btw I realize that with the uniq here I'm SOMEWHAT doing an intersection of the objects, but it's nowhere near as severe... the above example where merge yielded a count of 15 is not that off from the truth, when the count should be maybe -5 from the addition of the 2 queries
histogram[name_id] = item_object_ids.uniq.count
end
您可以将两个查询合并为一个:
items = Item.joins(:order).where(
"(orders.start >= ? AND orders.start <= ?) OR (orders.end >= ? AND orders.end <= ?)",
dates.first, dates.last, dates.first, dates.last
)
这可能更可读:
items = Item.joins(:order).where(
"(orders.start >= :first AND orders.start <= :last) OR (orders.end >= :first AND orders.end <= :last)",
{ first: dates.first, last: dates.last }
)
Rails5将支持or
方法,这可能会让它变得更好:
items_a = Item.joins(:order).where(
"orders.start >= :first AND orders.start <= :last",
{ first: dates.first, last: dates.last }
).or(
"orders.end >= :first AND orders.end <= :last",
{ first: dates.first, last: dates.last }
)
或者在这种情况下可能不会更好
也许这会更干净一点:
date_range = "May 27, 2016".to_date.."May 30, 2016".to_date
items = Item.joins(:order).where('orders.start' => date_range).or('orders.end' => date_range)