ActiveRecord Count для подсчета строк, возвращаемых группой в Rails
Я огляделся и не мог найти ответы на это. Все ответы включали подсчеты, в которых не использовали 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 BY (и как я буду это делать)?
Ответы
Ответ 1
Обходной путь для моей ситуации, похоже, заключается в замене: 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)
Ответ 2
Похоже, вам нужно обрабатывать сгруппированные запросы отдельно. Выполнение подсчета без группы возвращает целое число, а подсчет с группой возвращает хеш:
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}
Ответ 3
Если вы используете Rails 4 или 5, вы также можете сделать следующее.
Tag.group(:id).count
Ответ 4
Другое (хакерское) решение:
selection = Tag.where(...).group(...)
count = Tag.connection.select_value "select count(*) from (" + selection.to_sql + ") as x"
Ответ 5
Если я правильно понял ваш вопрос, тогда он должен работать, если вы вообще не используете 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
Update:
Можно вызвать счетчик, с которым атрибут будет подсчитываться, а также параметр: 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)