如何计算此postgres JOIN GROUP BY的行数(在Rails范围内)

时间:2015-10-31 05:48:55

标签: ruby-on-rails ruby postgresql

我设置了Rails 4范围,以便我找到订购了一定次数的用户。我的问题是,当我将.count放在范围的末尾时,它不会给我一个计数。它产生的底层SQL似乎是错误的。我该如何解决这个问题?

相关架构是一个简单的用户和订单模型。用户模型中的我的范围是:

scope :with_order_count_of, -> (count) {
  joins('LEFT OUTER JOIN orders ON users.id=orders.user_id').group('users.id').having("COUNT(orders.id)=#{count}")
}

当我这样调用它时:

User.with_order_count_of(0)

它会让我找到合适的用户。它生成的SQL是:

SELECT users.* FROM "users" LEFT OUTER JOIN ORDERS on users.id = orders.user_id GROUP BY users.id HAVING COUNT(orders.id) = 0;

我的问题是我经常想要这些。但是,这不起作用。它返回{user_ids =>的哈希值0}

User.with_order_count_of(0).count

它使用的SQL是:

SELECT COUNT(*) AS count_all, users.id AS users_id FROM "users" LEFT OUTER JOIN ORDERS on users.id = orders.user_id GROUP BY users.id HAVING COUNT(orders.id) = 0;

这里发生了什么?在Postgres中,COUNT在GROUP BY中应用,而不是计算总行数。我正在摆弄原始SQL,如果我执行这样的嵌套查询,我可以使用SQL获得正确的结果:

SELECT COUNT(*) (SELECT users.* FROM "users" LEFT OUTER JOIN ORDERS on users.id = orders.user_id GROUP BY users.id HAVING COUNT(orders.id) = 0) q;

问题是当我把.count放在最后时,rails没有这样做。我知道我可以做.length而不是.count和ruby会给我正确的答案,但我的表格很大,所以我希望计数发生在SQL级别。

如何修复我的范围,以便为我提供合适的用户:

User.with_order_count_of(0)

这为我提供了用户数量:

User.with_order_count_of(0).count

我离我的答案越来越近了。如果我可以弄清楚如何覆盖rails中的.count范围,那么我想我可以这样做:

scope :count, -> {
  existing_scope_sql = self.connection.unprepared_statement { self.reorder(nil).to_sql }
  if existing_scope_sql.downcase.include? 'group by'
    self.find_by_sql("SELECT COUNT(*) as count_all FROM (#{existing_scope_sql}) q").first.count_all
  else
    super
  end
}

Rails不会让我这样做。我花了一段时间谷歌搜索,我无法找到方法。任何人都可以帮助完成此解决方案或建议另一种解决方案吗?

1 个答案:

答案 0 :(得分:0)

嗯,你可以使用Postgresql的window function概念来实现你想要的。

您目前拥有以下范围:

scope :with_order_count_of, -> (count) {
  joins('LEFT OUTER JOIN orders ON users.id=orders.user_id')
  .group('users.id')
  .having("COUNT(orders.id) = ?", count)
}

# window function is triggered using over()
scope :total_orders_count, ->() {
 with_order_count_of.
 select("users.id, sum(count(*)) over () as total_count")
}