ЛЕВЫЙ ВНЕШНИЙ ВСТУПИТЕЛЬ В Rails 4
У меня есть 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
Я хочу запросить список курсов в таблице Курсов, которые не существуют в таблице StudentEnrollments, которые связаны с определенным учеником.
Я обнаружил, что, возможно, Left Join - путь, но кажется, что joins() в rails принимает только таблицу как аргумент.
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?
Приветствуется любой ввод.
Ответы
Ответ 1
Вы также можете передать строку, которая является соединением-sql. например joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")
Хотя я бы использовал определение имен таблиц на основе rails для ясности:
joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")
Ответ 2
Для этого существует "путь 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
Он делает именно то, что вы просите аккуратным способом.
Ответ 3
Вы выполнили бы запрос как:
Course.joins('LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id')
.where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })
Ответ 4
Он соединяет запрос в Active Model in Rails.
Пожалуйста, нажмите здесь для получения дополнительной информации об Active Model Query Format.
@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
Ответ 5
Если кто-то пришел сюда искать общий способ сделать левое внешнее соединение в Rails 5, вы можете использовать функцию #left_outer_joins
.
Пример с несколькими соединениями:
Ruby:
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
Ответ 6
Я довольно долго боролся с подобной проблемой и решил сделать что-то, чтобы решить ее раз и навсегда. Я опубликовал 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 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
Надеюсь, что это поможет.
Ответ 7
Используйте Squeel:
Person.joins{articles.inner}
Person.joins{articles.outer}
Ответ 8
Если вы хотите OUTER JOINs без всех дополнительных загруженных объектов ActiveRecord, используйте .pluck(:id)
после .eager_load()
, чтобы прервать загрузку при сохранении OUTER JOIN. Использование .pluck(:id)
thwarts eager loading, потому что псевдонимы столбцов (например, t23 > , например) исчезают из сгенерированного запроса при использовании (эти поля с независимым именем используются для создания экземпляров всех объектов с активной загрузкой 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)
Ответ 9
Объединение includes
и where
приводит к тому, что ActiveRecord выполняет LEFT OUTER JOIN за кулисами (без того, где это создаст нормальный набор из двух запросов).
Итак, вы можете сделать что-то вроде:
Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })
Документы здесь: http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations