2015-05-29 3 views
5

Предположим, у меня есть модель Parent, у которой много Child, и что Child также относится к OtherParent.ActiveRecord: Как найти родителей, у которых ВСЕ дети соответствуют условию?

Как я могу найти все Parent где ВСЕ его Child принадлежит к любым OtherParent?

В чистом SQL я мог бы сделать

Parent.find_by_sql(<<SQL) 
    SELECT * 
    FROM parents p 
    WHERE NOT EXISTS (
    SELECT * 
    FROM children 
    WHERE parent_id = p.id 
     AND other_parent_id IS NULL 
) 
SQL 

(от here), но я бы предпочел, чтобы сделать это, воспользовавшись ActiveRecord, если это возможно.

Спасибо!


Я использую Rails 4.2.1 и PostgreSQL 9.3

+0

Модели 'OtherParent' и' Parent' отличаются? –

+0

Можете ли вы показать ассоциации моделей? –

+1

Это называется Точное реляционное подразделение. См. Http://dba.stackexchange.com/questions/45829/what-is-the-name-of-this-type-of-query-and-what-is-an-efficient-example –

ответ

3

Использование arel может привести вас к довольно далеко. Трудная часть заключается в том, как вы не пишете весь свой запрос, используя собственный синтаксис запроса arel?

Вот трюк: при построении запроса с использованием where, если вы используете arel условия, вы можете получить дополнительные методы для бесплатного скачивания. Например, вы можете заключить подзапрос, который у вас там, с .exists.not, который доставит вам (NOT (EXISTS (subquery))). Положите его на where -clause, и вы установите его.

Вопрос в том, как вы ссылаетесь на связанные таблицы? Для этого вам нужен Арел. Вы можете использовать Arel's where с его уродливыми условиями, такими как a.eq b. Но почему? Поскольку это условие равенства, вы можете использовать условия Rails вместо этого!Вы можете ссылаться на таблицу, которую вы запрашиваете, с помощью хеш-ключа, но для другой таблицы (во внешнем запросе) вы можете использовать ее arel_table. Смотреть это:

parents = Parent.arel_table 
Parent.where(
    Child.where(other_parent_id: nil, parent_id: parents[:id]).exists.not 
) 

Вы можете даже уменьшить использование Arel, прибегая к натягивает немного и опираясь на тот факт, что вы можете кормить в подзапросов в качестве параметров Rails' where. Для этого не так много пользы, но это не заставляет вас слишком много копаться в методах Arel, поэтому вы можете использовать этот трюк или другие операторы SQL, которые принимают подзапрос (есть ли какие-либо другие?):

parents = Parent.arel_table 
Parent.where('NOT EXISTS (?)', 
    Child.where(parent_id: parents[:id], other_parent_id: nil) 
) 

две основные точки здесь:

  • вы можете построить подзапросы точно так же, как вы привыкли к созданию регулярных запросов, ссылок таблицы внешнего запроса с Arel. Возможно, это даже не настоящая таблица, это может быть псевдоним! Сумасшедший.
  • Вы можете использовать подзапросы в качестве параметров для метода Rails where просто отлично.
+0

Я пришел к тому же решению после некоторых изменений в ответ @ABMagil, но так как ваш ответ работает без изменений, я отмечаю его как принятый. Спасибо! –

+1

Использование Arel над чистым SQL также имеет то преимущество, что возвращает ActiveRecord :: Relation вместо массива, что позволяет позже связывать метод! –

+0

@FelipeZavan Я еще не закончил: D Начать с Arel довольно весело. –

-1

Учитывая Parent и Child, и ребенок находится в belongs_to отношениях с OtherParent (Rails по умолчанию предполагается):

Parent.joins(:childs).where('other_parent_id = ?', other_parent_id)

+0

Можете ли вы помочь мне понять, что это значит, что _Child также принадлежит OtherParent_? –

+0

@joseramonc Спасибо, но я думаю, что вы пропустили мой вопрос, пожалуйста, прочитайте его еще раз и скажите мне, есть ли у вас какие-либо вопросы. –

2

Используя исключительный scuttle, вы можете перевести произвольный SQL в ruby ​​(запросы ActiveRecord и ARel)

С этого инструмента, запрос преобразуется в

Parent.select(Arel.star).where(
    Child.select(Arel.star).where(
    Child.arel_table[:parent_id].eq(Parent.arel_table[:id]).and(Child.arel_table[:other_parent_id].eq(nil)) 
).ast 
) 

дробя query-

  • Parent.select(Arel.star) будет запрашивать для всех столбцов в таблице Parent.
  • Child.arel_table перенесет вас в мир Arel, позволяя вам немного больше энергии для генерации вашего запроса из ruby. В частности, Child.arel_table[:parent_id] дает вам дескриптор атрибута Arel :: Attributes :: Attribute, который вы можете продолжать использовать при построении запроса.
  • .eq и .and методы делают именно то, что вы ожидаете, позволяя строить запрос произвольной глубины и сложности.

Не обязательно «чище», но полностью внутри рубина, что приятно.

+0

Я не знаю, как я забыл такой потрясающий инструмент, как Скатл, спасибо, что напомнил мне! К сожалению, сгенерированный код не работает ('ActiveRecord :: StatementInvalid: PG :: SyntaxError: ERROR: subquery должен возвращать только один столбец'), но с небольшим количеством поисковых запросов я его адаптировал, и теперь он работает:' Parent.select (Arel .star) .where ( Child.select (Arel.star) .where ( Child.arel_table [: parent_id] .eq (Parent.arel_table [: id]). (Child.arel_table [: other_parent_id] .eq (nil)) ) .exists.not ) ' –

Смежные вопросы