Łącznik zewnętrzny lewy w szynach 4

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

Chciałbym zapytać o listę kursów w tabeli kursów, które nie istnieją w tabeli StudentEnrollments, które są związane z pewnym studentem.

Odkryłem, że być może Left Join jest dobrym rozwiązaniem, ale wygląda na to, że joins () w rails akceptuje tylko tabelę jako argument. Zapytanie SQL, które myślę, że zrobiłoby to, co 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 w Rails 4 way?

Każdy wkład jest doceniany.

Author: Yarin, 2014-06-23

12 answers

Możesz również przekazać ciąg znaków join-sql. eg joins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")

Chociaż użyłbym rails-standardowego nazewnictwa tabeli dla jasności:

joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")
 69
Author: Taryn East,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-23 05:21:58

W rzeczywistości istnieje "Rails Way", aby to zrobić.

Możesz użyć Arel, czyli tego, czego Rails używa do konstruowania zapytań dla ActiveRecrods

Zawinąłbym go w metodzie, abyś mógł go ładnie nazwać i przekazać w dowolnym argumencie, jaki byś chciał, coś w stylu:

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 lekko brudny) sposób, który wielu używa

Course.eager_load(:students).where(
    student_enrollments: {student_id: some_user.id, id: nil}, 
    active: true
)

Eager_load działa świetnie, po prostu ma "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.

 20
Author: superuseroi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-04 22:04:24

Jeśli ktoś tu przyszedł, szukając ogólnego sposobu na zrobienie lewego zewnętrznego połączenia w Rails 5, możesz użyć #left_outer_joins Funkcja.

Przykład Multi-join:

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
 20
Author: Blaskovicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-14 21:48:53

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 })
 8
Author: Joe Kennedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-17 17:30:25

Połączenie includes i where powoduje, że ActiveRecord wykonuje lewe zewnętrzne połączenie Za kulisami (bez gdzie generowałoby to normalny zestaw dwóch zapytań).

Więc możesz 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

 8
Author: mackshkatz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-20 17:43:33

Dodając do powyższej odpowiedzi, aby użyć includes, jeśli chcesz zewnętrzne połączenie bez odwoływania się do tabeli w where (np. id jest zerowe) lub odniesienie jest w łańcuchu możesz użyć references. To by wyglądało 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

 8
Author: Jonathon Gardner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-24 17:58:57

To join query w aktywnym modelu w Rails.

Kliknij tutaj, aby uzyskać więcej informacji na temat aktywnego formatu zapytania 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
 4
Author: jainvikram444,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-23 08:57:33

Od dłuższego czasu borykam się z tego typu problemami i postanowiłem zrobić coś, aby rozwiązać je raz na zawsze. Opublikowałem Gist, który porusza ten problem: https://gist.github.com/nerde/b867cd87d580e97549f2

Stworzyłem mały hack AR, który używa tabeli Arel do dynamicznego budowania lewych złączy dla ciebie, 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.
 4
Author: Diego,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-12 12:01:34

Użyj :

Person.joins{articles.inner}
Person.joins{articles.outer}
 3
Author: Yarin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-12 12:45:20

Jeśli chcesz dołączyć zewnętrzne Bez wszystkich dodatkowych, chętnie załadowanych obiektów ActiveRecord, użyj .pluck(:id) po .eager_load(), aby przerwać eager load, zachowując zewnętrzne połączenie. Użycie .pluck(:id) utrudnia chętne ładowanie, ponieważ aliasy nazw kolumn (na przykładitems.location AS t1_r9) znikają z wygenerowanego zapytania, gdy są używane (te niezależnie nazwane pola są używane do tworzenia instancji wszystkich chętnie załadowanych obiektów ActiveRecord).

Wadą tego podejścia jest to, że musisz uruchomić drugie zapytanie, aby pobranie żądanych obiektów ActiveRecord zidentyfikowanych 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)
 2
Author: textral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-07 06:43:08

Wiem, że to stare pytanie i stary wątek, ale w Rails 5 możesz po prostu zrobić

Course.left_outer_joins(:student_enrollments)
 2
Author: jDmendiola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-30 04:16:11

Możesz użyć left_joins gem, który backportuje metodę left_joins z Rails 5 dla Rails 4 i 3.

Course.left_joins(:student_enrollments)
      .where('student_enrollments.id' => nil)
 1
Author: khiav reoy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-27 04:01:05