ActiveRecord Count 用于计算 Rails 中 group by 返回的行数

发布于 2024-10-18 12:05:13 字数 1370 浏览 3 评论 0原文

我环顾四周,找不到任何答案。所有答案都涉及使用 GROUP BY 的计数。

背景: 我有一个分页器,它将采用 ActiveRecord.find 的选项。它添加 :limit 和 :offset 选项并执行查询。我还需要做的是计算记录总数(减去限制),但有时查询包含 :group 选项,并且 ActiveRecord.count 尝试返回 GROUP BY 返回的所有行及其每个计数。我在 Rails 2.3.5 中这样做。

我想要的是 ActiveRecord.count 返回 GROUP BY 返回的行数。

下面是一些示例代码,演示了一个实例(用于查找所有标签并按带有该标签的帖子数量对它们进行排序):

options = { :select => 'tags.*, COUNT(*) AS post_count',
            :joins => 'INNER JOIN posts_tags',   #Join table for 'posts' and 'tags'
            :group => 'tags.id',
            :order => 'post_count DESC' }

@count = Tag.count(options)

options = options.merge { :offset => (page - 1) * per_page, :limit => per_page }

@items = Tag.find(options)

使用 :select 选项,Tag.count 生成以下 SQL:

SELECT count(tags.*, COUNT(*) AS post_count) AS count_tags_all_count_all_as_post_count, tags.id AS tags_id FROM `tags`  INNER JOIN posts_tags  GROUP BY tags.id  ORDER BY COUNT(*) DESC

如您所见只是将 COUNT() 包裹在“tags.*, COUNT(*)”周围,MySQL 会抱怨 COUNT 中的 COUNT。

如果没有 :select 选项,它会生成此 SQL:

SELECT count(*) AS count_all, tags.id AS tags_id FROM `tags` INNER JOIN posts_tags GROUP BY tags.id ORDER BY COUNT(*)

它返回整个 GROUP BY 结果集,而不是行数。

有没有办法解决这个问题,或者我是否必须破解分页器来解释 GROUP BY 的查询(以及我将如何去做)?

I looked around and couldn't find any answers to this. All answers involved counts that did not use a GROUP BY.

Background:
I have a paginator that will take options for an ActiveRecord.find. It adds a :limit and :offset option and performs the query. What I also need to do is count the total number of records (less the limit), but sometimes the query contains a :group option and ActiveRecord.count tries to return all rows returned by the GROUP BY along with each of their counts. I'm doing this in Rails 2.3.5.

What I want is for ActiveRecord.count to return the number of rows returned by the GROUP BY.

Here is some sample code that demonstrates one instance of this (used for finding all tags and ordering them by the number of posts with that tag):

options = { :select => 'tags.*, COUNT(*) AS post_count',
            :joins => 'INNER JOIN posts_tags',   #Join table for 'posts' and 'tags'
            :group => 'tags.id',
            :order => 'post_count DESC' }

@count = Tag.count(options)

options = options.merge { :offset => (page - 1) * per_page, :limit => per_page }

@items = Tag.find(options)

With the :select option, the Tag.count generates the following SQL:

SELECT count(tags.*, COUNT(*) AS post_count) AS count_tags_all_count_all_as_post_count, tags.id AS tags_id FROM `tags`  INNER JOIN posts_tags  GROUP BY tags.id  ORDER BY COUNT(*) DESC

As you can see it merely wrapped a COUNT() around the 'tags.*, COUNT(*)', and MySQL complains about the COUNT within a COUNT.

Without the :select option, it generates this SQL:

SELECT count(*) AS count_all, tags.id AS tags_id FROM `tags` INNER JOIN posts_tags GROUP BY tags.id ORDER BY COUNT(*)

which returns the whole GROUP BY result set and not the number of rows.

Is there a way around this or will I have to hack up the paginator to account for queries with GROUP BYs (and how would I go about doing that)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

素衣风尘叹 2024-10-25 12:05:13

似乎您需要单独处理分组查询。在没有组的情况下进行计数会返回一个整数,而在有组的情况下进行计数会返回一个哈希值:

Tag.count
  SQL (0.2ms)  SELECT COUNT(*) FROM "tags"
 => 37

Tag.count(:group=>"tags.id")
  SQL (0.2ms)  SELECT COUNT(*) AS count_all, tags.id AS tags_id FROM "tags" 
    GROUP BY tags.id
 => {1=>37}

Seems like you'd need to handle the grouped queries separately. Doing a count without a group returns an integer, while counting with a group returns a hash:

Tag.count
  SQL (0.2ms)  SELECT COUNT(*) FROM "tags"
 => 37

Tag.count(:group=>"tags.id")
  SQL (0.2ms)  SELECT COUNT(*) AS count_all, tags.id AS tags_id FROM "tags" 
    GROUP BY tags.id
 => {1=>37}
祁梦 2024-10-25 12:05:13

如果您使用的是 Rails 4 或 5,您也可以执行以下操作。

Tag.group(:id).count

If you're using Rails 4 or 5 you can do the following as well.

Tag.group(:id).count
能否归途做我良人 2024-10-25 12:05:13

我的情况的解决方法似乎是替换 :group => 'tags.id' 与 :select =>在执行计数之前选项哈希中的“DISTINCT Tags.id”。

count_options = options.clone
count_options.delete(:order)

if options[:group]
  group_by = count_options[:group]
  count_options.delete(:group)
  count_options[:select] = "DISTINCT #{group_by}"
end

@item_count = @type.count(count_options)

The workaround for my situation seems to be to replace the :group => 'tags.id' with :select => 'DISTINCT tags.id' in the options hash before executing the count.

count_options = options.clone
count_options.delete(:order)

if options[:group]
  group_by = count_options[:group]
  count_options.delete(:group)
  count_options[:select] = "DISTINCT #{group_by}"
end

@item_count = @type.count(count_options)
够钟 2024-10-25 12:05:13

另一个(hacky)解决方案:

selection = Tag.where(...).group(...)
count = Tag.connection.select_value "select count(*) from (" + selection.to_sql + ") as x"

Another (hacky) solution:

selection = Tag.where(...).group(...)
count = Tag.connection.select_value "select count(*) from (" + selection.to_sql + ") as x"
尴尬癌患者 2024-10-25 12:05:13

如果我正确理解你的问题,那么如果你根本不使用 Tag.count ,它应该可以工作。在您的选择哈希中指定“COUNT(*) AS post_count”就足够了。例如:

@tag = Tag.first(options)
@tag.post_count

如您所见,查询中的 post_count 值可以从 @tag 实例访问。如果你想获取所有标签,那么也许是这样的:

@tags = Tag.all(options)
@tags.each do |tag|
  puts "Tag name: #{tag.name} posts: #{tag.post_count}"
end

更新:

可以使用要计数的属性和参数 :distinct 来调用 Count

options = { :select => 'tags.*, COUNT(*) AS post_count',
            :joins => 'INNER JOIN posts_tags',   #Join table for 'posts' and 'tags'
            :group => 'tags.id',
            :order => 'post_count DESC',
            :offset => (page - 1) * per_page,
            :limit => per_page }

@count = Tag.count(:id, :distinct => true, :joins => options[:joins])

@items = Tag.find(options)

If I understand your question correctly, then it should work if you don't use Tag.count at all. Specifying 'COUNT(*) AS post_count' in your select hash should be enough. For example:

@tag = Tag.first(options)
@tag.post_count

As you can see, the post_count value from the query is accessible from the @tag instance. And if you want to get all tags, then perhaps something like this:

@tags = Tag.all(options)
@tags.each do |tag|
  puts "Tag name: #{tag.name} posts: #{tag.post_count}"
end

Update:

Count can be called with which attribute to count and also a parameter :distinct

options = { :select => 'tags.*, COUNT(*) AS post_count',
            :joins => 'INNER JOIN posts_tags',   #Join table for 'posts' and 'tags'
            :group => 'tags.id',
            :order => 'post_count DESC',
            :offset => (page - 1) * per_page,
            :limit => per_page }

@count = Tag.count(:id, :distinct => true, :joins => options[:joins])

@items = Tag.find(options)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文