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テーブルに存在しないCoursesテーブルのコースのリストを照会したい。
おそらく左結合が進むべき道であることがわかりましたが、Railsの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方法で実行するにはどうすればよいですか?
どんな入力でも大歓迎です。
Join-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")
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
実際には、これを行うための「レールウェイ」があります。
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が舞台裏でLEFT OUTER JOINを実行します(2つのクエリの通常のセットを生成する場所を除く)。
そのため、次のようなことができます。
Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })
上記の答えに加えて、includes
を使用するには、whereのテーブルを参照せずに外部結合が必要な場合(idがnilなど)、または参照が文字列にある場合、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
次のようにクエリを実行します。
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 gemを使用できます。gemは、Rails 5 for Rails 4からleft_joins
メソッドをバックポートします。および3。
Course.left_joins(:student_enrollments)
.where('student_enrollments.id' => nil)
私はこの種の問題にかなり長い間苦労してきましたが、それを解決するために何かをすることにしました。この問題に対処する要旨を公開しました: https://Gist.github.com/nerde/b867cd87d580e97549f2
コードで生のSQLを記述することなく、Arel Tableを使用して動的に左結合を構築する小さなARハックを作成しました。
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
それが役に立てば幸い。
RailsのActive Modelの結合クエリです。
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
Squeel を使用します。
Person.joins{articles.inner}
Person.joins{articles.outer}
余分に積極的にロードされたActiveRecordオブジェクトをすべて持たないOUTER JOINが必要な場合は、.pluck(:id)
の後に.eager_load()
を使用して、OUTER JOINを保持しながら積極的なロードを中止します。 .pluck(:id)
を使用すると、使用時に列名のエイリアス(たとえば、items.location AS t1_r9
)が生成されたクエリから消えるため(これらの独立した名前のフィールドは、すべての熱心にロードされたActiveRecordオブジェクトをインスタンス化するために使用されます).
このアプローチの欠点は、2番目のクエリを実行して、最初のクエリで識別された目的の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)