Можно ли создать OR Joins в Rails для выборки записей, которые пересекаются с 2 разными моделями? - PullRequest
0 голосов
/ 01 июня 2018

Я также опубликовал связанный вопрос: https://stackoverflow.com/a/50613524?noredirect=1,, который был больше об этой концепции.

Я пытаюсь сделать следующее: я хочу найти Accounts, для которогоопределенный User создал Report, или , для которого у пользователя есть deadline.

  scope :active_accounts_for_user, -> (user) {
    joins(:deadlines)
    .where(:deadlines => {user: [nil, user]})
    .joins(:reports)
    .where(:reports => {user: user, day: (Date.today - 1.day..Date.today)})
  }

Проблема в том, что Rails создал запрос AND, поэтому оннайдет только те аккаунты, у которых есть отчет и срок.Это явно ограничивает мой сценарий использования.

Редактировать: Хорошо, я нашел решение, но я хотел бы подтвердить, является ли это хорошей практикой, хотя.Я в основном создаю отдельные запросы к обеим моделям, чтобы получить идентификаторы учетной записи, которые я использую в простом где:

  scope :active_accounts_for_user, -> (user) {
    accounts_with_reports = Report.where(user: user).where('date > ?', 24.hours.ago).pluck(:account_id)
    accounts_with_deadlines = Deadline.where(user: user).pluck(:account_id)
    account_ids = (accounts_with_reports + accounts_with_deadlines).compact
    return includes(:past_deadlines, :deadlines).where(id: account_ids)
  }

Это правильное использование области?

Ответы [ 2 ]

0 голосов
/ 01 июня 2018

Один из вариантов - использовать ванильный SQL.

Если я правильно понимаю вашу схему, вам нужно что-то, что требует левых соединений больше, чем оператора ИЛИ.Оператор ИЛИ ВНУТРЕННЕГО СОЕДИНЕНИЯ не достигнет того, на что вы надеетесь, поскольку любая запись, у которой есть только крайний срок, но нет отчета, не будет возвращена.Вместо этого LEFT JOIN возвращает вам все записи учетных записей, в которых есть соответствующие идентификаторы user_id в таблицах сроков или отчетов.

select * from accounts 
left join deadlines on deadlines.user = accounts.user
left join reports on reports.user = accounts.user
where day ...
and (reports.user IS NOT NULL or deadline.user IS NOT NULL)

В Rails 4 я полагаю, что вы можете указать код SQL для объединения, при этом объединяя в цепочкупункт, как вы бы обычно.Таким образом, это будет выглядеть так:

joinsql = "left join deadlines on 
 deadlines.user = accounts.user
 left join reports 
 on reports.user = accounts.user"

Account.join(joinsql).where(
   accounts: {user: user}, 
   deadline: {day: (Date.today - 1.day..Date.today)}
 ).where("reports.user IS NOT NULL or deadlines.user IS NOT NULL")

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

Обратите внимание на добавление второго предложения where, связанного с первым, эффективно Иобычное текстовое условие, гарантирующее, что оба отчета и крайние сроки не являются пустыми.

Если у вас есть связи, соединяющие таблицы через атрибут user, то вы можете использовать простые запросы Active Record для использования

Account.includes(:accounts).includes(:deadlines).where... 

См .: http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations

Другой вариант, позволяющий избежать LEFT JOIN, - это использовать внутреннее соединение для одной таблицы и UNION с аналогичным запросом с внутренним соединением для другой таблицы.Чтобы это работало, вы должны указать возвращаемые поля с .select, чтобы объединение могло работать.На мой взгляд, это более сложно.

В любом случае, зачастую проще просто написать SQL до точки условий условия where, которые содержат ввод пользователя, чем пытаться угадать, что собирается Railsсделать, чтобы создать запрос, который вы хотите.

0 голосов
/ 01 июня 2018

Это возможно в Rails 5.0 и выше.

В руководстве есть раздел, как это сделать: http://guides.rubyonrails.org/active_record_querying.html#or-conditions

Единственный catch с объединениями - эточто они должны дублировать оба отношения, чтобы сделать весь запрос структурно совместимым:

scope :active_accounts_for_user, -> (user) {
 joins(:deadlines).joins(:reports).where(deadlines: ...).or(
   joins(:deadlines).joins(:reports).where(reports: ...)
  )
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...