У меня 3 модели:
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
Я хочу запросить список курсов в таблице Courses, которых нет в таблице StudentEnrollments, связанных с определенным студентом.
Я обнаружил, что, возможно, лучше всего пойти с левым соединением, но кажется, что joins () в рельсах принимает только таблицу в качестве аргумента. SQL-запрос, который, как мне кажется, будет делать то, что я хочу:
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
Как мне выполнить этот запрос способом Rails 4?
Любой вклад приветствуется.
se.student_id = <SOME_STUDENT_ID_VALUE>
будет невозможно?Ответы:
Вы можете передать строку, которая также является соединением-sql. например
joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")
Хотя для ясности я бы использовал стандартные наименования таблиц:
joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")
источник
Если кто-то пришел сюда в поисках универсального способа выполнения левого внешнего соединения в Rails 5, вы можете использовать эту
#left_outer_joins
функцию.Пример множественного соединения:
Рубин:
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
источник
left_outer_joins(a: [:b, :c])
left_joins
для краткости и вы ведете себя так же. Например.left_joins(:order_reports)
На самом деле для этого существует «способ Rails».
Вы можете использовать Arel , который Rails использует для создания запросов для ActiveRecrods.
Я бы обернул его в метод, чтобы вы могли красиво назвать его и передать любой аргумент, который вам нужен, например:
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
Существует также быстрый (и немного грязный) способ, которым многие пользуются
Course.eager_load(:students).where( student_enrollments: {student_id: some_user.id, id: nil}, active: true )
eager_load отлично работает, у него просто есть «побочный эффект» моделей кодирования в памяти, которые могут вам не понадобиться (как в вашем случае).
См. Rails ActiveRecord :: QueryMethods. eager_load
Он аккуратно выполняет именно то, что вы просите.
источник
Объединение
includes
иwhere
приводит к тому, что ActiveRecord выполняет ЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ за кулисами (без того, где это генерирует обычный набор из двух запросов).Итак, вы можете сделать что-то вроде:
Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })
Документы здесь: http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
источник
Добавьте к ответу выше, чтобы использовать
includes
, если вы хотите ВНЕШНЕЕ СОЕДИНЕНИЕ без ссылки на таблицу в where (например, id равняется нулю) или ссылка находится в строке, которую вы можете использоватьreferences
. Это выглядело бы так:Course.includes(:student_enrollments).references(:student_enrollments)
или же
Course.includes(:student_enrollments).references(:student_enrollments).where('student_enrollments.id = ?', nil)
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-references
источник
joins
на,includes
и это помогло.Вы бы выполнили запрос как:
Course.joins('LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id') .where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })
источник
Я знаю, что это старый вопрос и старая ветка, но в Rails 5 вы могли бы просто сделать
Course.left_outer_joins(:student_enrollments)
источник
Вы можете использовать гем left_joins , который возвращает
left_joins
метод из Rails 5 для Rails 4 и 3.Course.left_joins(:student_enrollments) .where('student_enrollments.id' => nil)
источник
Я довольно долго боролся с этой проблемой и решил что-то сделать, чтобы решить ее раз и навсегда. Я опубликовал Gist, посвященный этой проблеме: https://gist.github.com/nerde/b867cd87d580e97549f2
Я создал небольшой AR-хак, который использует таблицу Arel для динамического построения левых соединений для вас, без необходимости писать необработанный SQL в вашем коде:
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
Надеюсь, поможет.
источник
См. Ниже мой исходный пост по этому вопросу.
С тех пор я реализовал свой собственный
.left_joins()
для ActiveRecord v4.0.x (извините, мое приложение зависло в этой версии, поэтому мне не нужно было переносить его на другие версии):В файле
app/models/concerns/active_record_extensions.rb
поместите следующее: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)
Теперь я могу использовать
.left_joins()
везде, где обычно.joins()
.----------------- ОРИГИНАЛЬНЫЙ ПОСТ ВНИЗ -----------------
Если вам нужны ВНЕШНИЕ СОЕДИНЕНИЯ без всех дополнительных быстро загружаемых объектов ActiveRecord, используйте
.pluck(:id)
after.eager_load()
для прерывания активной загрузки при сохранении ВНЕШНЕГО СОЕДИНЕНИЯ. Использование.pluck(:id)
препятствует активной загрузке, поскольку псевдонимы имени столбца (items.location AS t1_r9
например) исчезают из сгенерированного запроса при использовании (эти поля с независимыми именами используются для создания экземпляров всех быстро загружаемых объектов ActiveRecord).Недостатком этого подхода является то, что затем вам нужно выполнить второй запрос, чтобы получить желаемые объекты ActiveRecord, указанные в первом запросе:
# 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)
источник
select(:id)
вместоpluck(:id)
и предотвратить материализацию внутреннего запроса и оставить все это в базе данных.Это запрос соединения в Active Model in Rails.
Щелкните здесь, чтобы получить дополнительную информацию о формате запроса активной модели .
@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
источник
Используйте Squeel :
Person.joins{articles.inner} Person.joins{articles.outer}
источник