Rails 4의 LEFT OUTER JOIN


80

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 테이블의 강좌 목록을 쿼리하고 싶습니다.

아마도 Left Join이 갈 길이라는 것을 알았지 만 레일의 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 방식으로 어떻게 실행합니까?

모든 입력을 부탁드립니다.


기록이 StudentEnrollments에 존재하지 않는다면 확실히 se.student_id = <SOME_STUDENT_ID_VALUE>불가능할까요?
PJSCopeland

답변:


84

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")

2
내 솔루션은 다음과 같았습니다. {id : nil}) 내가 원하는대로 Rails는 아니지만 작업은 완료됩니다. LEFT JOIN을 수행하는 .includes ()를 사용해 보았지만 조인에 대한 추가 조건을 지정할 수는 없습니다. 감사합니다 Taryn!
Khanetor 2014-06-23

1
큰. 헤이, 때때로 우리는 그것을 작동시키기 위해 우리가하는 일을합니다. :) ... 그것을 다시 오는 미래에 더 잘 만들기위한 시간
타린 동쪽

1
@TarynEast "작동하고, 빠르게 만들고, 아름답게 만드세요." :)
Joshua Pinter 19

31

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

1
감사합니다. 교차 연관 왼쪽 외부 조인에 대해 언급하고 싶습니다. 사용left_outer_joins(a: [:b, :c])
fangxing

또한 left_joins짧게 사용할 수 있으며 동일한 방식으로 행동합니다. 예 : left_joins(:order_reports)
alexventuraio

23

실제로 이것을하기위한 "Rails Way"가 있습니다.

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를 참조하십시오
.


54
몇 년이 지난 후에도 ActiveRecord가 여전히이를 지원하지 않는다는 것을 믿을 수 없다고 말해야합니다. 완전히 헤아릴 수 없습니다.
mrbrdo

1
Sooooo 언제 Sequel이 Rails에서 기본 ORM이 될 수 있습니까?
animatedgif

5
레일이 부풀어지지 않아야합니다. Imo 그들은 처음에 기본적으로 번들로 제공되는 보석을 추출하기로 결정했을 때 올바르게 잡았습니다. 철학은 "덜 할 수 있지만 좋은"와 "당신이 원하는 것을 선택"입니다
ADIT Saxena는

9
Rails 5는 LEFT OUTER JOIN을 지원합니다 : blog.bigbinary.com/2016/03/24/…
Murad Yusufov

eager_load의 "부작용"을 피하려면 내 대답을 참조하십시오
textral


12

위의 대답에 추가 includes하여을 사용 하려면 where (id가 nil 인 것과 같이) 테이블을 참조하지 않고 OUTER JOIN을 원하거나 참조가 문자열에 있습니다 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


이것은 깊게 중첩 된 관계에 대해 작동합니까 아니면 관계가 쿼리되는 모델에서 직접 중단되어야합니까? 전자의 예를 찾을 수없는 것 같습니다.
dps

그것을 사랑하십시오! 그냥 교체했다 joinsincludes그것은 트릭을했다.
RaphaMex

8

쿼리를 다음과 같이 실행합니다.

Course.joins('LEFT JOIN student_enrollment on courses.id = student_enrollment.course_id')
      .where(active: true, student_enrollments: { student_id: SOME_VALUE, id: nil })

7

나는 이것이 오래된 질문이고 오래된 스레드라는 것을 알고 있지만 Rails 5에서는 간단하게 할 수 있습니다.

Course.left_outer_joins(:student_enrollments)

문제는 특히 Rails 4.2를 대상으로합니다.
Volte

6

Rails 4와 3을 위해 Rails 5의 메소드 를 백 포트 하는 left_joins gem을 사용할 수 있습니다 left_joins.

Course.left_joins(:student_enrollments)
      .where('student_enrollments.id' => nil)

4

나는 이런 종류의 문제로 꽤 오랫동안 어려움을 겪어 왔고 그것을 단번에 해결하기 위해 무언가를하기로 결정했습니다. 이 문제를 해결하는 Gist를 게시했습니다 : https://gist.github.com/nerde/b867cd87d580e97549f2

Arel Table을 사용하여 코드에 원시 SQL을 작성하지 않고도 왼쪽 조인을 동적으로 빌드하는 약간의 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

도움이 되었기를 바랍니다.


4

이 질문에 대한 내 원래 게시물 아래를 참조하십시오.

그 이후로 .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 개체없이 OUTER JOIN을 원하는 경우 .pluck(:id)after .eager_load()를 사용 하여 OUTER JOIN을 유지하면서 즉시로드를 중단합니다. 사용 .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)

이건 재미 있네.
dps

+1하지만 조금 더 개선하고 select(:id)대신 사용할 수 있으며 pluck(:id)내부 쿼리를 구체화하고 모든 것을 데이터베이스에 남겨 둘 수 있습니다.
Andre Figueiredo

3

Rails의 Active Model의 조인 쿼리입니다.

활성 모델 쿼리 형식에 대한 자세한 내용을 보려면 여기를 클릭하십시오 .

@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

3
게시 된 답변에 설명을 추가하는 것이 좋습니다.
Bhushan Kawadkar 2014-06-23

3

Squeel 사용 :

Person.joins{articles.inner}
Person.joins{articles.outer}

2
Squeel은 지원되지 않는 라이브러리이므로 권장되지 않습니다.
iNulty
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.