ActiveRecord Count для подсчета строк, возвращаемых группой в Rails - PullRequest
19 голосов
/ 22 февраля 2011

Я оглянулся и не смог найти ответов на это. Все ответы включают подсчеты, которые не использовали GROUP BY.

Справочная информация: У меня есть paginator, который будет принимать параметры для 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 () вокруг тегов. *, 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, а не количество строк.

Есть ли способ обойти это, или мне придется взломать paginator для учета запросов с GROUP BYs (и как мне это сделать)?

Ответы [ 5 ]

24 голосов
/ 22 февраля 2011

Похоже, вам нужно обрабатывать сгруппированные запросы отдельно. Подсчет без группы возвращает целое число, а подсчет с группой возвращает хеш:

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}
16 голосов
/ 27 сентября 2014

Если вы используете Rails 4 или 5, вы также можете сделать следующее.

Tag.group(:id).count
8 голосов
/ 22 февраля 2011

Обходной путь для моей ситуации, по-видимому, состоит в замене: 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)
3 голосов
/ 13 апреля 2015

Другое (хакерское) решение:

selection = Tag.where(...).group(...)
count = Tag.connection.select_value "select count(*) from (" + selection.to_sql + ") as x"
2 голосов
/ 22 февраля 2011

Если я правильно понимаю ваш вопрос, то он должен работать, если вы вообще не используете 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

Обновление:

Можно вызвать счетчик, с каким атрибутом считать, а также с параметром: Different

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