पटरियों 4 में बाईं ओर शामिल हों


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

मैं पाठ्यक्रम तालिका में पाठ्यक्रमों की एक सूची के लिए क्वेरी करना चाहता हूं, जो कि उस छात्र-परीक्षा तालिका में मौजूद नहीं हैं जो एक निश्चित छात्र के साथ जुड़ी हुई हैं।

मैंने पाया कि शायद लेफ्ट ज्वाइन जाने का रास्ता है, लेकिन ऐसा लगता है कि रेल (जोड़) केवल एक टेबल को तर्क के रूप में स्वीकार करते हैं। 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

मैं इस क्वेरी को 4 तरीके से कैसे निष्पादित करूं?

किसी भी इनपुट की सराहना की है।


यदि छात्र-छात्रों में रिकॉर्ड मौजूद नहीं है, तो निश्चित रूप se.student_id = <SOME_STUDENT_ID_VALUE>से असंभव होगा?
PJSCopeland

जवाबों:


84

आप एक स्ट्रिंग पास कर सकते हैं जो जॉइन-एसक्यूएल भी है। जैसेjoins("LEFT JOIN StudentEnrollment se ON c.id = se.course_id")

हालांकि मैं स्पष्टता के लिए रेल-स्टैंडर्ड टेबल नामकरण का उपयोग करूंगा:

joins("LEFT JOIN student_enrollments ON courses.id = student_enrollments.course_id")

2
मेरा समाधान समाप्त हो रहा है: क्वेरी = "LEFT JOIN student_enrollments on courses.id = student_enrollments.course_id और" + "student_enrollments.student_id = # {self.id}" courses.active.joins (क्वेरी) वे (student_enrollments)। {id: nil}) यह रेल के रूप में के रूप में मैं चाहता हूँ यह नहीं है, हालांकि यह काम हो जाता है। मैंने .includes () का उपयोग करने की कोशिश की, जो LEFT JOIN करता है, लेकिन यह मुझे जुड़ने पर एक अतिरिक्त शर्त निर्दिष्ट नहीं करने देता। धन्यवाद Taryn!
खानतोर

1
महान। अरे, कभी-कभी हम इसे काम करने के लिए करते हैं। इसके वापस आने और भविष्य में इसे बेहतर बनाने के लिए समय ... :)
टैरिन ईस्ट

1
@TarynEast "इसे काम करो, इसे तेज़ करो, इसे सुंदर बनाओ।" :)
जोशुआ पिंटर

31

यदि कोई भी रेलगाड़ी 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)
अलेक्सांटुरियो

23

ऐसा करने के लिए वास्तव में एक "रेल्स वे" है।

आप इस्तेमाल कर सकते हैं अरेल है, जो है क्या 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
)

उत्सुक_लोड बहुत अच्छा काम करता है, इसका सिर्फ मेमोरी में मॉडेलिंग मॉडल का "साइड इफेक्ट" होता है, जिसकी आपको आवश्यकता नहीं हो सकती है (जैसे आपके मामले में)
कृपया Rails ActiveRecord देखें :: QueryMethods .eager_load
यह वही करता है जो आप बड़े करीने से पूछ रहे हैं।


54
मुझे सिर्फ इतना कहना है कि मुझे विश्वास नहीं हो रहा है कि ActiveRecord के पास इतने वर्षों के बाद भी इसके लिए कोई अंतर्निहित समर्थन नहीं है। यह पूरी तरह से अथाह है।
श्रीब्रांडो

1
Sooooo रेल में डिफ़ॉल्ट ORM कब बन सकता है?
एनिमेटेड

5
रेल फूला हुआ नहीं होना चाहिए। Imo वे सही हो गया जब वे जवाहरात बाहर निकालने का फैसला किया जो पहली जगह में डिफ़ॉल्ट रूप से बंडल किए गए थे। दर्शन "कम करो, लेकिन अच्छा करो" और "जो आप चाहते हैं उसे उठाओ"
आदित्य सक्सेना

9
रेल 5 के लिए LEFT OUTER JOIN: blog.bigbinary.com/2016/03/24/…
मुराद युसुफोव

उत्सुक_ लोड के "साइड इफेक्ट" से बचने के लिए, मेरा जवाब देखें
टेक्सचरल

13

ActiveRecord में संयोजन includesऔर whereपरिणाम पर्दे के पीछे एक लेफ्टिनेंट जॉय का प्रदर्शन करते हैं (बिना जहां यह दो प्रश्नों के सामान्य सेट को उत्पन्न करेगा)।

तो आप कुछ ऐसा कर सकते हैं:

Course.includes(:student_enrollments).where(student_enrollments: { course_id: nil })

डॉक्स यहां: http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations


12

ऊपर दिए गए उत्तर में जोड़ने के लिए, उपयोग करने के लिए includes, यदि आप तालिका में जहां (जैसे आईडी शून्य हो रहा है) का संदर्भ लिए बिना संदर्भ चाहते हैं या संदर्भ एक स्ट्रिंग में है जिसका आप उपयोग कर सकते हैं 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और इसने चाल चली।
राफमैक्स

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

मुझे पता है कि यह एक पुराना सवाल है और एक पुराना धागा है, लेकिन रेल 5 में, आप बस कर सकते हैं

Course.left_outer_joins(:student_enrollments)

सवाल विशेष रूप से रेल्स 4.2 पर लक्षित है।
Volte


4

मैं काफी समय से इस तरह की समस्या से जूझ रहा था, और एक बार और सभी के लिए इसे हल करने के लिए कुछ करने का फैसला किया। मैंने एक Gist प्रकाशित किया जो इस मुद्दे को संबोधित करता है: https://gist.github.com/nerde/b867cd87d580e97549f2

मैंने थोड़ी सी AR हैक बनाई जो कि आपके कोड में कच्ची SQL लिखने के बिना, आपके लिए लेफ्ट जॉइन बनाने के लिए Arel टेबल का उपयोग करती है:

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 चाहते हैं, तो OUTER JOIN को संरक्षित करते समय उत्सुक लोड को रोकने के .pluck(:id)बाद उपयोग करें .eager_load().pluck(:id)Thwarts उत्सुक लोड हो रहा है का उपयोग कर क्योंकि स्तंभ नाम उपनाम ( 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)आंतरिक क्वेरी को भौतिक बनाने से रोक सकते हैं, और इसे सभी डेटाबेस पर छोड़ सकते हैं।
आंद्रे फिग्यूएरेडो

3

यह रेल में सक्रिय मॉडल में क्वेरी से जुड़ता है।

सक्रिय मॉडल क्वेरी प्रारूप के बारे में अधिक जानकारी के लिए कृपया यहां क्लिक करें

@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
पोस्ट किए गए अपने उत्तर में कुछ स्पष्टीकरण जोड़ना बेहतर है।
भूषण कवाडकर

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.