Запрос ActiveRecord для количества ассоциаций с использованием таблицы соединений

Я работаю над функцией событий и сталкиваюсь с довольно сложным запросом.

Вот модели для справки:

class Event < ApplicationRecord
  has_one :party

  belongs_to :host, foreign_key: :host_id, class_name: 'User'
  belongs_to :convention, optional: true
  has_many :join_requests
  
  ...
end

Я хочу проверить, какие мероприятия принимают участников, проверив member_limit соответствующей стороны.

class Party < ApplicationRecord
  belongs_to :event
  belongs_to :host, foreign_key: :host_id, class_name: 'User'

  has_many :user_parties
  has_many :members, through: :user_parties, source: 'user'

  ...
end

Party имеет целочисленный столбец: member_limit -- который допускает только определенное количество членов группы.

На данный момент у меня есть область действия Event, которая выглядит так:

scope :accepting_members, -> () do
    joins(party: :user_parties).group('parties.member_limit').select('count(user_parties.user_id) < parties.member_limit')
  end

Что не очень хорошо работает.

Я на правильном пути?

Я пробовал использовать агрегат COUNT(), но он возвращает только хэш целых чисел.

Вы действительно не на правильном пути. Здесь существуют различные подходы, например использование WHERE events.id IN (subquery), где подзапрос получает идентификаторы событий, WHERE EXISTS(subquery) или использование бокового соединения.

max 19.03.2024 14:27

Причина, по которой ваш подход не будет работать, заключается в том, что группа parties.member_limit и предложение select будут применяться ко всему запросу, и вы не получите желаемого фактического результата, который представляет собой строки из таблицы событий.

max 19.03.2024 14:29
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
2
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я думаю, вам не нужны какие-либо сложные соединения, вы можете сделать это вот так.

Поскольку соотношение следующее

  1. В событии есть_одна вечеринка
  2. Сторона has_many UserParty

Вы можете сделать это вот так.

# event.rb
scope :accepting_members, -> { party.user_parties.count < party.member_limit }

Это вернет логическое значение. если лимит участников не достигнут, он вернет true, в противном случае — false.

Это не очень хороший ответ. Если бы этот код действительно работал, это вызвало бы запрос N+1 и потребовало бы загрузки всей таблицы.

max 19.03.2024 14:05
Ответ принят как подходящий

Это будет немного сложнее, чем вы изначально думали, если вам нужен полезный результат, и есть довольно много способов сделать это.

Один из способов — создать подзапрос для идентификаторов событий:

SELECT events.* 
FROM events
WHERE events.id IN (
  SELECT parties.events_id
  FROM parties
  OUTER LEFT JOIN user_parties ON parties.id = user_parties.party_id
  GROUP BY parties.event_id
  HAVING(
    COUNT(user_parties.user_id) < parties.member_limit
  )
)

Писать это в ActiveRecord/Arel становится немного сложно:

subquery = Party.select(:events_id)
                .left_joins(:user_parties)
                .group(:event_id)
                .having(
                  # COUNT(user_parties.user_id) < parties.member_limit
                  UserParty.arel_table[:user_id].count.lt(
                    Party.arel_table[:member_limit]
                  )
                )
                .where(
                  # events.id = parties.event_id
                  Event.arel_table[:id].eq(Party.arel_table[:id])
                )
 
Event.where(
  id: subquery
)

Другой способ сделать это — использовать EXIST или боковое соединение.

Другие вопросы по теме