2015-05-29 5 views
5

Angenommen, ich habe ein Parent Modell mit vielen Child, und das Child gehört auch zu OtherParent.ActiveRecord: Wie finde ich Eltern, deren ALLE Kinder eine Bedingung erfüllen?

Wie kann ich alle Parent finden, wo ALLES Child zu jedem OtherParent gehört?

In reiner SQL konnte ich

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 

(von here) tun, aber ich würde es lieber tun, indem, wenn möglich, die Vorteile von Active nehmen.

Danke!


Ich bin mit Rails 4.2.1 und PostgreSQL 9,3

+0

'OtherParent' und' Parent' Modelle sind anders? –

+0

Können Sie die Modellassoziationen anzeigen? –

+1

Dies wird Exact Relational Division genannt. Siehe http://dba.stackexchange.com/questions/45829/what-is-the-name-of-this-type-of-query-and-what-is-anefficient-example –

Antwort

3

Mit arel können Sie ziemlich weit. Der schwierige Teil ist, wie schreiben Sie nicht Ihre gesamte Abfrage mit arel eigene Abfragesyntax?

Hier ist ein Trick: Wenn Sie Ihre Abfrage mit where erstellen, wenn Sie arel Bedingungen verwenden, erhalten Sie einige zusätzliche Methoden kostenlos. Zum Beispiel können Sie die Unterabfrage, die Sie dort haben, mit .exists.not, die Sie erhalten eine (NOT (EXISTS (subquery))) werfen, dass in Eltern where -Klausel und Sie sind gesetzt.

Die Frage ist, wie verweisen Sie auf die beteiligten Tabellen? Sie brauchen Arel dafür. Sie könnte verwenden Arels where mit seinen hässlichen Bedingungen wie a.eq b. Aber warum? Da es sich um eine Gleichheitsbedingung handelt, können Sie stattdessen die Bedingungen von Rails verwenden!Sie können die Tabelle, die Sie abfragen, mit einem Hash-Schlüssel referenzieren, aber für die andere Tabelle (in der äußeren Abfrage) können Sie arel_table verwenden. Watch this:

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

Sie können sogar Arel Verbrauch verringern durch den Rückgriff ein wenig, und unter Berufung auf die Tatsache, Strings, die Sie where in Unterabfragen als Parameter an Rails' füttern können. Es bringt wenig Nutzen, aber es zwingt Sie nicht zu sehr in Arels Methoden zu graben, also können Sie diesen Trick oder andere SQL-Operatoren verwenden, die eine Unterabfrage durchführen (gibt es noch andere?):

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

die beiden wichtigsten Punkte sind hier:

  • Sie Subqueries gerade die gleiche Art und Weise bauen können Sie für den Aufbau regelmäßige Abfragen verwendet werden, verweisen die Tabelle des äußeren Abfrage mit Arel. Es kann nicht einmal ein echter Tisch sein, es kann ein Alias ​​sein! Verrücktes Zeug.
  • Sie können Unterabfragen als Parameter für Rails 'where Methode gut verwenden.
+0

Ich kam zu genau der gleichen Lösung nach einigen Änderungen an @ABMagil Antwort, aber da Ihre Antwort ohne Änderungen funktioniert, markiere ich es als akzeptiert. Vielen Dank! –

+1

Die Verwendung von Arel über reines SQL hat auch den Vorteil, dass anstelle eines Arrays eine ActiveRecord :: Relation zurückgegeben wird, was eine spätere Verkettung der Methode ermöglicht! –

+0

@FelipeZavan Ich bin noch nicht fertig: D Fummeln mit Arel macht eigentlich Spaß. –

-1

Parent und Child gegeben und Kind ist in einer belongs_to Beziehung mit OtherParent (Rails Standardwerte angenommen):

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

+0

Können Sie mir helfen, das zu verstehen, was bedeutet es, dass _Child auch zu OtherParent_ gehört? –

+0

@joseramonc Danke, aber ich denke, du hast meine Frage missverstanden, bitte lies sie noch einmal und sag mir, ob du irgendwelche Fragen hast. –

2

Mit der Ausnahme scuttle können Sie beliebige SQL in Ruby (ActiveRecord und ARel Abfragen)

01 übersetzen

Von diesem Tool Ihre Abfrage konvertiert zu

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 
) 

Aufspaltung der query-

  • Parent.select(Arel.star) für alle Spalten in der Elterntabelle abgefragt werden.
  • Child.arel_table bringt Sie in Arel-Welt, so dass Sie ein bisschen mehr Leistung bei der Erstellung Ihrer Abfrage von Ruby. Speziell Child.arel_table[:parent_id] gibt Ihnen ein Handle auf ein Arel :: Attributes :: Attribut, das Sie beim Erstellen einer Abfrage weiterhin verwenden können.
  • die .eq und .and Methoden tun genau das, was Sie erwarten würden, können Sie eine Abfrage von beliebiger Tiefe und Komplexität erstellen.

Nicht unbedingt "sauberer", aber ganz in Rubin, was schön ist.

+0

Ich weiß nicht, wie ich ein so tolles Tool wie Scuttle vergessen habe, danke, dass du mich daran erinnert hast! Leider funktioniert der generierte Code nicht ('ActiveRecord :: StatementInvalid: PG :: SyntaxError: ERROR: Unterabfrage muss nur eine Spalte zurückgeben'), aber mit etwas Googeln habe ich ihn angepasst und jetzt funktioniert es:' Parent.select (Arel .star) .where ( Child.select (Arel.star) .where ( Child.arel_table [: parent_id] .eq (Parent.arel_table [: id]). und (Child.arel_table [: other_parent_id] .eq (nil)) ) .exists.not ) ' –

Verwandte Themen