Mam 3 modele:
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
Chcę zapytać o listę kursów w tabeli Kursy, które nie istnieją w tabeli StudentEnrollments, które są powiązane z określonym studentem.
Odkryłem, że być może Left Join jest właściwą drogą, ale wydaje się, że joins () w railsach akceptuje tylko tabelę jako argument. Zapytanie SQL, które moim zdaniem zrobiłoby to, czego chcę, to:
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
Jak wykonać to zapytanie sposobem Rails 4?
Każdy wkład jest mile widziany.
se.student_id = <SOME_STUDENT_ID_VALUE>
byłby niemożliwy?Odpowiedzi:
Możesz również przekazać łańcuch, który jest złączeniem sql. na przykład
joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")
Chociaż użyłbym standardowego nazewnictwa tabel dla przejrzystości:
joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")
źródło
Jeśli ktoś przyszedł tutaj i szukał ogólnego sposobu na wykonanie lewego zewnętrznego sprzężenia w Rails 5, może użyć
#left_outer_joins
funkcji.Przykład wielokrotnego łączenia:
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
źródło
left_outer_joins(a: [:b, :c])
left_joins
krótkie i zachowujesz się w ten sam sposób. Na przykład.left_joins(:order_reports)
Właściwie jest na to „sposób na szyny”.
Możesz użyć Arela , którego Railsy używają do tworzenia zapytań dla ActiveRecrods
Owinąłbym to w metodę, abyś mógł ją ładnie nazwać i przekazać dowolny argument, na przykład:
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
Istnieje również szybki (i nieco brudny) sposób, z którego korzysta wielu
Course.eager_load(:students).where( student_enrollments: {student_id: some_user.id, id: nil}, active: true )
eager_load działa świetnie, ma po prostu "efekt uboczny" umieszczania modeli w pamięci, których możesz nie potrzebować (jak w twoim przypadku).
Zobacz Rails ActiveRecord :: QueryMethods .eager_load
Robi dokładnie to, o co prosisz w schludny sposób.
źródło
Połączenie
includes
iwhere
powoduje wykonanie przez ActiveRecord LEFT OUTER JOIN za kulisami (bez miejsca, w którym wygenerowałoby to normalny zestaw dwóch zapytań).Możesz więc zrobić coś takiego:
Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })
Dokumenty tutaj: http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
źródło
Dodając do powyższej odpowiedzi, użyj
includes
, jeśli chcesz, aby JOIN ZEWNĘTRZNY bez odwoływania się do tabeli w miejscu (np. Id jest zerowy) lub odniesienie jest w ciągu, którego możesz użyćreferences
. To wyglądałoby tak:Course.includes(:student_enrollments).references(:student_enrollments)
lub
Course.includes(:student_enrollments).references(:student_enrollments).where('student_enrollments.id = ?', nil)
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-references
źródło
joins
naincludes
i to załatwiło sprawę.Wykonałbyś zapytanie jako:
Course.joins('LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id') .where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })
źródło
Wiem, że to stare pytanie i stary wątek, ale w Railsach 5 możesz po prostu to zrobić
Course.left_outer_joins(:student_enrollments)
źródło
Możesz użyć gem left_joins , który jest
left_joins
metodą backportów z Rails 5 dla Rails 4 i 3.Course.left_joins(:student_enrollments) .where('student_enrollments.id' => nil)
źródło
Od jakiegoś czasu zmagam się z tego rodzaju problemem i postanowiłem zrobić coś, aby rozwiązać go raz na zawsze. Opublikowałem streszczenie, które dotyczy tego problemu: https://gist.github.com/nerde/b867cd87d580e97549f2
Stworzyłem mały hack AR, który używa Arel Table do dynamicznego budowania lewych złączeń, bez konieczności pisania surowego SQL w kodzie:
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
Mam nadzieję, że to pomoże.
źródło
Zobacz poniżej mój oryginalny post na to pytanie.
Od tego czasu wdrażam własne
.left_joins()
dla ActiveRecord v4.0.x (przepraszam, moja aplikacja jest zawieszona w tej wersji, więc nie musiałem przenosić jej do innych wersji):W pliku
app/models/concerns/active_record_extensions.rb
umieść następujące informacje: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)
Teraz mogę używać
.left_joins()
wszędzie tam, gdzie normalnie używam.joins()
.----------------- ORYGINALNY POST PONIŻEJ -----------------
Jeśli chcesz, aby OUTER JOINs nie zawierały wszystkich dodatkowych, chętnie ładowanych obiektów ActiveRecord, użyj
.pluck(:id)
after,.eager_load()
aby przerwać gorące ładowanie, zachowując jednocześnie OUTER JOIN. Użycie.pluck(:id)
uniemożliwia przyspieszone ładowanie, ponieważ aliasy nazw kolumn (items.location AS t1_r9
na przykład) znikają z wygenerowanego zapytania, gdy są używane (te niezależnie nazwane pola są używane do tworzenia instancji wszystkich chętnie ładowanych obiektów ActiveRecord).Wadą tego podejścia jest to, że trzeba następnie uruchomić drugie zapytanie, aby pobrać żądane obiekty ActiveRecord zidentyfikowane w pierwszym zapytaniu:
# 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)
źródło
select(:id)
zamiastpluck(:id)
i zapobiegać materializacji wewnętrznego zapytania i pozostawieniu tego wszystkiego w bazie danych.Zapytanie łączące It'a w Active Model w Railsach.
Kliknij tutaj, aby uzyskać więcej informacji na temat formatu zapytania aktywnego modelu .
@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
źródło
Użyj Squeel :
Person.joins{articles.inner} Person.joins{articles.outer}
źródło