Ich habe 3 Modelle:
class Student < ActiveRecord::Base
has_many :student_enrollments, dependent: :destroy
has_many :courses, through: :student_enrollments
end
class Course < ActiveRecord::Base
has_many :student_enrollments, dependent: :destroy
has_many :students, through: :student_enrollments
end
class StudentEnrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
Ich möchte eine Liste von Kursen in der Tabelle "Kurse" abfragen, die in der Tabelle "StudentEnrollments" nicht vorhanden sind und einem bestimmten Studenten zugeordnet sind.
Ich fand, dass Left Join vielleicht der richtige Weg ist, aber es scheint, dass joins () in Rails nur eine Tabelle als Argument akzeptiert. Die SQL-Abfrage, von der ich denke, dass sie das tun würde, was ich will, ist:
SELECT *
FROM Courses c LEFT JOIN StudentEnrollment se ON c.id = se.course_id
WHERE se.id IS NULL AND se.student_id = <SOME_STUDENT_ID_VALUE> and c.active = true
Wie führe ich diese Abfrage auf Rails 4-Weise aus?
Jede Eingabe wird geschätzt.
se.student_id = <SOME_STUDENT_ID_VALUE>
wäre dies sicherlich unmöglich?Antworten:
Sie können eine Zeichenfolge übergeben, die auch die Join-SQL ist. z.B
joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")
Obwohl ich aus Gründen der Klarheit die Schienen-Standardtabellennamen verwenden würde:
joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")
quelle
Wenn jemand hierher gekommen ist, um nach einer generischen Möglichkeit zu suchen, eine linke äußere Verknüpfung in Rails 5 durchzuführen, können Sie die
#left_outer_joins
Funktion verwenden.Beispiel für mehrere Verknüpfungen:
Rubin:
Source. select('sources.id', 'count(metrics.id)'). left_outer_joins(:metrics). joins(:port). where('ports.auto_delete = ?', true). group('sources.id'). having('count(metrics.id) = 0'). all
SQL:
SELECT sources.id, count(metrics.id) FROM "sources" INNER JOIN "ports" ON "ports"."id" = "sources"."port_id" LEFT OUTER JOIN "metrics" ON "metrics"."source_id" = "sources"."id" WHERE (ports.auto_delete = 't') GROUP BY sources.id HAVING (count(metrics.id) = 0) ORDER BY "sources"."id" ASC
quelle
left_outer_joins(a: [:b, :c])
left_joins
für kurze Zeit zur Verfügung und verhalten sich genauso. Z.B.left_joins(:order_reports)
Es gibt tatsächlich einen "Rails Way", um dies zu tun.
Sie können Arel verwenden , mit dem Rails Abfragen für ActiveRecrods erstellt
Ich würde es in eine Methode einwickeln, damit Sie es schön aufrufen und jedes Argument übergeben können, das Sie möchten, so etwas wie:
class Course < ActiveRecord::Base .... def left_join_student_enrollments(some_user) courses = Course.arel_table student_entrollments = StudentEnrollment.arel_table enrollments = courses.join(student_enrollments, Arel::Nodes::OuterJoin). on(courses[:id].eq(student_enrollments[:course_id])). join_sources joins(enrollments).where( student_enrollments: {student_id: some_user.id, id: nil}, active: true ) end .... end
Es gibt auch den schnellen (und leicht schmutzigen) Weg, den viele benutzen
Course.eager_load(:students).where( student_enrollments: {student_id: some_user.id, id: nil}, active: true )
eifrig_load funktioniert großartig, es hat nur den "Nebeneffekt", dass Modelle im Speicher gespeichert werden, die Sie möglicherweise nicht benötigen (wie in Ihrem Fall).
Siehe Rails ActiveRecord :: QueryMethods .eager_load
Es macht genau das, was Sie fragen, auf eine ordentliche Art und Weise.
quelle
Kombinieren
includes
undwhere
führt dazu, dass ActiveRecord hinter den Kulissen einen LEFT OUTER JOIN ausführt (ohne das, wo dies den normalen Satz von zwei Abfragen erzeugen würde).Sie könnten also so etwas tun wie:
Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })
Dokumente hier: http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
quelle
Hinzufügen zu der obigen Antwort, um zu verwenden
includes
, wenn Sie einen OUTER JOIN möchten, ohne auf die Tabelle im where zu verweisen (wie id ist nil) oder die Referenz in einer Zeichenfolge ist, die Sie verwenden könnenreferences
. Das würde so aussehen:Course.includes(:student_enrollments).references(:student_enrollments)
oder
Course.includes(:student_enrollments).references(:student_enrollments).where('student_enrollments.id = ?', nil)
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-references
quelle
joins
durchincludes
und es hat den Trick.Sie würden die Abfrage wie folgt ausführen:
Course.joins('LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id') .where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })
quelle
Ich weiß, dass dies eine alte Frage und ein alter Thread ist, aber in Rails 5 können Sie dies einfach tun
Course.left_outer_joins(:student_enrollments)
quelle
Sie können den Edelstein left_joins verwenden , der die
left_joins
Methode von Rails 5 für Rails 4 und 3 zurückportiert .Course.left_joins(:student_enrollments) .where('student_enrollments.id' => nil)
quelle
Ich habe seit einiger Zeit mit solchen Problemen zu kämpfen und beschlossen, etwas zu tun, um sie ein für alle Mal zu lösen. Ich habe eine Übersicht veröffentlicht, die dieses Problem behebt : https://gist.github.com/nerde/b867cd87d580e97549f2
Ich habe einen kleinen AR-Hack erstellt, der Arel Table verwendet, um die linken Joins dynamisch für Sie zu erstellen, ohne dass Sie unformatiertes SQL in Ihren Code schreiben müssen:
class ActiveRecord::Base # Does a left join through an association. Usage: # # Book.left_join(:category) # # SELECT "books".* FROM "books" # # LEFT OUTER JOIN "categories" # # ON "books"."category_id" = "categories"."id" # # It also works through association's associations, like `joins` does: # # Book.left_join(category: :master_category) def self.left_join(*columns) _do_left_join columns.compact.flatten end private def self._do_left_join(column, this = self) # :nodoc: collection = self if column.is_a? Array column.each do |col| collection = collection._do_left_join(col, this) end elsif column.is_a? Hash column.each do |key, value| assoc = this.reflect_on_association(key) raise "#{this} has no association: #{key}." unless assoc collection = collection._left_join(assoc) collection = collection._do_left_join value, assoc.klass end else assoc = this.reflect_on_association(column) raise "#{this} has no association: #{column}." unless assoc collection = collection._left_join(assoc) end collection end def self._left_join(assoc) # :nodoc: source = assoc.active_record.arel_table pk = assoc.association_primary_key.to_sym joins source.join(assoc.klass.arel_table, Arel::Nodes::OuterJoin).on(source[assoc.foreign_key].eq( assoc.klass.arel_table[pk])).join_sources end end
Ich hoffe es hilft.
quelle
Siehe unten meinen ursprünglichen Beitrag zu dieser Frage.
Seitdem habe ich meine eigene
.left_joins()
für ActiveRecord v4.0.x implementiert (leider ist meine App in dieser Version eingefroren, sodass ich sie nicht auf andere Versionen portieren musste):Fügen Sie
app/models/concerns/active_record_extensions.rb
in die Datei Folgendes ein:module ActiveRecordBaseExtensions extend ActiveSupport::Concern def left_joins(*args) self.class.left_joins(args) end module ClassMethods def left_joins(*args) all.left_joins(args) end end end module ActiveRecordRelationExtensions extend ActiveSupport::Concern # a #left_joins implementation for Rails 4.0 (WARNING: this uses Rails 4.0 internals # and so probably only works for Rails 4.0; it'll probably need to be modified if # upgrading to a new Rails version, and will be obsolete in Rails 5 since it has its # own #left_joins implementation) def left_joins(*args) eager_load(args).construct_relation_for_association_calculations end end ActiveRecord::Base.send(:include, ActiveRecordBaseExtensions) ActiveRecord::Relation.send(:include, ActiveRecordRelationExtensions)
Jetzt kann ich
.left_joins()
überall verwenden, wo ich normalerweise verwenden würde.joins()
.----------------- ORIGINAL POST UNTEN -----------------
Wenn Sie OUTER JOINs ohne alle besonders eifrig geladenen ActiveRecord-Objekte möchten, verwenden Sie
.pluck(:id)
after.eager_load()
, um das eifrige Laden abzubrechen, während Sie OUTER JOIN beibehalten. Verwenden Sie das.pluck(:id)
eifrige Laden, da die Spaltennamen-Aliase (items.location AS t1_r9
zum Beispiel) bei Verwendung aus der generierten Abfrage verschwinden (diese unabhängig benannten Felder werden verwendet, um alle eifrig geladenen ActiveRecord-Objekte zu instanziieren).Ein Nachteil dieses Ansatzes besteht darin, dass Sie dann eine zweite Abfrage ausführen müssen, um die gewünschten ActiveRecord-Objekte abzurufen, die in der ersten Abfrage identifiziert wurden:
# first query idents = Course .eager_load(:students) # eager load for OUTER JOIN .where( student_enrollments: {student_id: some_user.id, id: nil}, active: true ) .distinct .pluck(:id) # abort eager loading but preserve OUTER JOIN # second query Course.where(id: idents)
quelle
select(:id)
anstelle derpluck(:id)
Verhinderung der Materialisierung innerer Abfragen verwenden und alles der Datenbank überlassen.Es handelt sich um eine Join-Abfrage in Active Model in Rails.
Klicken Sie hier, um weitere Informationen zum Active Model Query Format zu erhalten .
@course= Course.joins("LEFT OUTER JOIN StudentEnrollment ON StudentEnrollment .id = Courses.user_id"). where("StudentEnrollment .id IS NULL AND StudentEnrollment .student_id = <SOME_STUDENT_ID_VALUE> and Courses.active = true").select
quelle
Verwenden Sie Squeel :
Person.joins{articles.inner} Person.joins{articles.outer}
quelle