ActiveRecord Association выбирает количество включенных записей - PullRequest
14 голосов
/ 09 февраля 2012

Пример

class User  
  has_many :tickets 
end

Я хочу создать ассоциацию, которая содержит логику подсчета билетов пользователя и использовать ее во включениях (пользователь has_one ticket_count)

Users.includes(:tickets_count)

Я пытался

  has_one :tickets_count, :select => "COUNT(*) as tickets_count,tickets.user_id " ,:class_name => 'Ticket', :group => "tickets.user_id", :readonly => true  

User.includes(:tickets_count)

 ArgumentError: Unknown key: group

В этом случае для запроса ассоциации в include следует использовать count с group by ... Как я могу реализовать это с помощью rails?

Обновление

  • Я не могу изменить структуру таблицы
  • Я хочу, чтобы AR сгенерировал 1 запрос для набора пользователей, включающий

Update2

Iзнаю SQL и я знаю, как выбрать это с помощью объединений, но мой вопрос теперь похож на «Как получить данные».Мой вопрос о создании ассоциации, которую я могу использовать, в том числе.Спасибо

Update3 Я пытался создать ассоциацию, созданную как пользователь has_one ticket_count, но

  1. похоже, has_one не поддерживает расширения ассоциации
  2. has_oneне поддерживает: опция группы
  3. has_one не поддерживает finder_sql

Ответы [ 4 ]

13 голосов
/ 21 февраля 2012

Попробуйте:

class User

  has_one :tickets_count, :class_name => 'Ticket', 
    :select => "user_id, tickets_count",
    :finder_sql =>   '
        SELECT b.user_id, COUNT(*) tickets_count
        FROM   tickets b
        WHERE  b.user_id = #{id}
        GROUP BY b.user_id
     '
end

Редактировать:

Похоже, что ассоциация has_one не поддерживает параметр finder_sql.

Вы можете легко достичь желаемого, используя комбинацию scope / class методов

class User < ActiveRecord::Base

  def self.include_ticket_counts
    joins(
     %{
       LEFT OUTER JOIN (
         SELECT b.user_id, COUNT(*) tickets_count
         FROM   tickets b
         GROUP BY b.user_id
       ) a ON a.user_id = users.id
     }
    ).select("users.*, COALESCE(a.tickets_count, 0) AS tickets_count")
  end    
end

Теперь

User.include_ticket_counts.where(:id => [1,2,3]).each do |user|
  p user.tickets_count 
end

Это решение влияет на производительность, если у вас миллионыстрок в таблице tickets.Следует рассмотреть возможность фильтрации набора результатов JOIN, указав WHERE для внутреннего запроса.

9 голосов
/ 09 февраля 2012

Вы можете просто использовать для конкретного пользователя:

user.tickets.count

Или если вы хотите, чтобы это значение автоматически кэшировалось Rails.

Объявите параметр counter_cache => true на другой сторонеАссоциация

class ticket
  belongs_to :user, :counter_cache => true
end

Вам также нужен столбец в пользовательской таблице с именем tickets_count.При этом каждый раз, когда вы добавляете новые заявки на пользовательские рельсы, этот столбец будет обновляться, поэтому, когда вы откроете свою пользовательскую запись, вы можете просто получить этот столбец, чтобы получить количество билетов без дополнительного запроса.

7 голосов
/ 14 февраля 2012

Не красиво, но работает:

users = User.joins("LEFT JOIN tickets ON users.id = tickets.user_id").select("users.*, count(tickets.id) as ticket_count").group("users.id")
users.first.ticket_count
0 голосов
/ 20 февраля 2012

А как насчет добавления метода в модель User, который выполняет запрос?

Вы не изменили бы структуру таблицы или не можете изменить это тоже?

...