2013-05-02 8 views
40

Ist es in Rails> 3.2 möglich, Bedingungen zu der Join-Anweisung hinzuzufügen, die von der includes-Methode generiert wird?Schienen enthält mit Bedingungen

Sagen wir, ich habe zwei Modelle, Person und Note. Jede Person hat viele Notizen und jede Notiz gehört einer Person. Jede Notiz hat ein Attribut important.

Ich möchte alle Leute finden, die nur die wichtigen Notizen vorladen. In SQL das sein wird:

SELECT * 
FROM people 
LEFT JOIN notes ON notes.person_id = people.id AND notes.important = 't' 

In Rails, die nur ähnliche Weise zu tun, ist mit includes (Anmerkung: joins nicht Noten Vorspannung) wie folgt aus:

Person.includes(:notes).where(:important, true) 

jedoch, dass Wille erzeugen die folgende SQL-Abfrage, die ein anderes Ergebnis zurückgibt:

SELECT * 
FROM people 
LEFT JOIN notes ON notes.person_id = people.id 
WHERE notes.important = 't' 

Bitte beachten Sie, dass die erste resultset beinhaltet alle Menschen und die zweite nur die Menschen, mit wichtigen Notizen verbunden.

Beachten Sie auch, dass Bedingungen seit 3.1 veraltet sind.

+0

Ist Gibt es einen Unterschied in der Leistung? notes.important = 't' in einer Join-Klausel ist nicht semantisch, da sie nur ein Modell berücksichtigt. – fotanus

+0

Ja. Es ist eine Frage der Leistung. Ich brauche alle Leute, eifrig wichtige Notizen, wenn es welche gibt. – gusa

+1

Ich stehe vor der gleichen Frage, wie Sie Ihre Abfrage schließlich verwaltet haben? Hast du es in reinem SQL geschrieben? –

Antwort

25

Nach diesem Handbuch Active Record Querying

Sie Bedingungen enthält für eager loading wie diese

Person.includes(:notes).where("notes.important", true) 

joins trotzdem zu verwenden Es empfiehlt angeben. wäre dann in der Lage sein zu tun, um diese

Person.find(:all, include: :important_notes) 
+5

Person.includes (...). Wo (...) gibt nur Personen zurück, die mit wichtigen Notizen verknüpft sind. Ich brauche alle Leute, eifrig wichtige Notizen zu laden. – gusa

+2

Die zweite Lösung löst das Beispiel, das ich der Übersichtlichkeit halber gezeichnet habe. Die Realität ist jedoch komplizierter. Angenommen, ich brauche wichtige Notizen in einem Zeitraum (zwischen Startdatum und Enddatum). – gusa

0

Ein Weg

für diese Eine Abhilfe wie diese Sie

class Person < ActiveRecord::Base 
    has_many :important_notes, :class_name => 'Note', 
      :conditions => ['important = ?', true] 
end 

einen anderen Verein zu schaffen wäre die LEFT JOIN-Klausel selbst, indem Sie schreiben Joins:

Person.joins('LEFT JOIN "notes" ON "notes"."person_id" = "people.id" AND "notes"."important" IS "t"') 

Nicht schön, obwohl.

+1

Aber Includes werden zum einfachen Laden und Verbinden von Lazy Loads verwendet. Also ich denke, dies in unangemessenen –

4

Ich konnte die Includes mit einer Bedingung wie Leo Correas Antwort nicht verwenden. Insted ich neeed zu verwenden:

Lead.includes(:contacts).where("contacts.primary" =>true).first 

oder Sie können auch werden

Lead.includes(:contacts).where("contacts.primary" =>true).find(8877) 

Dieses letzt man die Führung mit ID 8877 abrufen, sondern enthalten nur seinen primären Kontakt

8

Rails 4.2 und höher:

A Option - "Vorbelastung" - Mehrfach wählt verwendet „ID IN (...")

class Person < ActiveRecord::Base 
    has_many :notes 
    has_many :important_notes, -> { where(important: true) }, class_name: "Note" 
end 

Person.preload(:important_notes) 

SQL:

SELECT "people".* FROM "people" 

SELECT "notes".* FROM "notes" WHERE "notes"."important" = ? AND "notes"."person_id" IN (1, 2) 

Option B - ") eager_load" - eine riesige Auswahl, verwendet "LEFT JOIN")

class Person < ActiveRecord::Base 
    has_many :notes 
    has_many :important_notes, -> { where(important: true) }, class_name: "Note" 
end 

Person.eager_load(:important_notes) 

SQL:

SELECT "people"."id" AS t0_r0, "people"."name" AS t0_r1, "people"."created_at" AS t0_r2, "people"."updated_at" AS t0_r3, "notes"."id" AS t1_r0, "notes"."person_id" AS t1_r1, "notes"."important" AS t1_r2 
FROM "people" 
LEFT OUTER JOIN "notes" ON "notes"."person_id" = "people"."id" AND "notes"."important" = ? 
0

für Leute mit Interesse habe ich versucht, dies in dem ein Attributsatz

Lead.includes(:contacts).where("contacts.primary" => false).first 

falsch war und das funktioniert nicht. Irgendwie für booleans nur true Werke, so drehte ich sie um where.not

Lead.includes(:contacts).where.not("contacts.primary" => true).first 

Das funktioniert perfekt

5

Rails 5+ Syntax enthalten:

Person.includes(:notes).where(notes: {important: true}) 

Nested:

Person.includes(notes: [:grades]).where(notes: {important: true, grades: {important: true}) 
+0

Dies funktioniert nicht, wie es hier erwähnt wurde: [Active Record Guide] (http://guides.rubyonrails.org/active_record_querying.html). Es gibt keine Person-Objekte zurück, die keiner Notiz zugeordnet sind. Es werden nur Personen zurückgegeben, die eine Notiz mit dem Attribut master haben, die gleich true ist – bkdir