उन सभी रिकॉर्डों को खोजें जिनमें एक संघ की संख्या शून्य से अधिक है


98

मैं कुछ ऐसा करने की कोशिश कर रहा हूं जो मुझे लगा कि यह सरल होगा लेकिन ऐसा नहीं लगता है।

मेरे पास एक परियोजना मॉडल है जिसमें कई रिक्तियां हैं।

class Project < ActiveRecord::Base

  has_many :vacancies, :dependent => :destroy

end

मैं उन सभी परियोजनाओं को प्राप्त करना चाहता हूं जिनमें कम से कम 1 रिक्ति हो। मैंने कुछ इस तरह की कोशिश की:

Project.joins(:vacancies).where('count(vacancies) > 0')

लेकिन यह कहता है

SQLite3::SQLException: no such column: vacancies: SELECT "projects".* FROM "projects" INNER JOIN "vacancies" ON "vacancies"."project_id" = "projects"."id" WHERE ("projects"."deleted_at" IS NULL) AND (count(vacancies) > 0)

जवाबों:


66

joinsडिफ़ॉल्ट रूप से एक आंतरिक Project.joins(:vacancies)जुड़ाव का उपयोग करता है, इसलिए केवल एक रिक्त स्थान रखने वाले प्रोजेक्ट्स को प्रभावी रूप से उपयोग करना होगा।

अपडेट करें:

जैसा कि @mackskatz द्वारा टिप्पणी में कहा गया है, एक groupखंड के बिना , ऊपर दिया गया कोड उन अधिक रिक्तियों वाली परियोजनाओं के लिए डुप्लिकेट प्रोजेक्ट लौटाएगा। डुप्लिकेट को निकालने के लिए, का उपयोग करें

Project.joins(:vacancies).group('projects.id')

अपडेट करें:

जैसा कि @ टॉलसी ने बताया है, आप भी इस्तेमाल कर सकते हैं distinct

Project.joins(:vacancies).distinct

उदहारण के लिए

[10] pry(main)> Comment.distinct.pluck :article_id
=> [43, 34, 45, 55, 17, 19, 1, 3, 4, 18, 44, 5, 13, 22, 16, 6, 53]
[11] pry(main)> _.size
=> 17
[12] pry(main)> Article.joins(:comments).size
=> 45
[13] pry(main)> Article.joins(:comments).distinct.size
=> 17
[14] pry(main)> Article.joins(:comments).distinct.to_sql
=> "SELECT DISTINCT \"articles\".* FROM \"articles\" INNER JOIN \"comments\" ON \"comments\".\"article_id\" = \"articles\".\"id\""

1
हालाँकि, एक समूह को खंड द्वारा लागू किए बिना यह उन परियोजनाओं के लिए कई प्रोजेक्ट ऑब्जेक्ट लौटाएगा जिनकी एक से अधिक रिक्ति है।
mackshkatz

1
हालांकि, एक कुशल एसक्यूएल बयान उत्पन्न नहीं करता है।
डेविड एल्ड्रिज

अच्छा है कि आप के लिए रेल है। यदि आप एक sql उत्तर प्रदान कर सकते हैं (और बताएं कि यह कुशल क्यों नहीं है), तो यह बहुत अधिक सहायक हो सकता है।
jvnill

आप क्या सोचते हैं Project.joins(:vacancies).distinct?
टॉल्सी

1
यह @ टॉलसी btw: D
टॉलसी

168

1) कम से कम 1 रिक्ति के साथ परियोजनाएं प्राप्त करने के लिए:

Project.joins(:vacancies).group('projects.id')

2) 1 से अधिक रिक्ति वाले परियोजनाएं प्राप्त करने के लिए:

Project.joins(:vacancies).group('projects.id').having('count(project_id) > 1')

3) या, यदि Vacancyमॉडल काउंटर कैश सेट करता है:

belongs_to :project, counter_cache: true

तो यह भी काम करेगा:

Project.where('vacancies_count > ?', 1)

मैन्युअल रूप से निर्दिष्टvacancy करने की आवश्यकता हो सकती है, के लिए इन्फ़्लेशन नियम ?


2
क्या यह नहीं होना चाहिए Project.joins(:vacancies).group('projects.id').having('count(vacancies.id) > 1')? प्रोजेक्ट आईडी के बजाय रिक्तियों की संख्या को
छोड़कर

1
नहीं है, @KeithMattix, यह चाहिए नहीं हो। हालांकि, यह हो सकता है, अगर यह आपके लिए बेहतर पढ़ता है; यह प्राथमिकता की बात है। गणना किसी भी क्षेत्र से जुड़ने की तालिका में की जा सकती है जिसकी गारंटी है कि हर पंक्ति में एक मूल्य हो। सबसे सार्थक उम्मीदवार हैं projects.id, project_id, और vacancies.id। मैंने गिनना चुना project_idक्योंकि यह वह क्षेत्र है जिस पर सम्मिलित किया जाता है; यदि आप करेंगे की रीढ़ में शामिल हो। यह भी याद दिलाता है कि यह एक ज्वाइन टेबल है।
आर्टा

37

हां, vacanciesज्वाइन में फील्ड नहीं है। मेरा मानना ​​है कि आप चाहते हैं:

Project.joins(:vacancies).group("projects.id").having("count(vacancies.id)>0")

16
# None
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 0')
# Any
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 0')
# One
Project.joins(:vacancies).group('projects.id').having('count(vacancies) = 1')
# More than 1
Project.joins(:vacancies).group('projects.id').having('count(vacancies) > 1')

5

एक के साथ संयुक्त has_many तालिका में एक आंतरिक जुड़ाव प्रदर्शन करना groupया uniqसंभावित रूप से बहुत अक्षम है, और SQL में यह बेहतर रूप से अर्ध- EXISTSजुड़ाव के रूप में कार्यान्वित किया जाएगा जो एक सहसंबद्ध उपश्रेणी के साथ उपयोग करता है ।

यह क्वेरी ऑप्टिमाइज़र को सही प्रोजेक्ट_आईडी वाली पंक्ति के अस्तित्व की जांच करने के लिए रिक्तियों तालिका की जांच करने की अनुमति देता है। इससे कोई फर्क नहीं पड़ता कि एक पंक्ति या एक लाख है जो कि project_id है।

यह रेल में उतना सीधा नहीं है, लेकिन इसके साथ प्राप्त किया जा सकता है:

Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)

इसी प्रकार, उन सभी परियोजनाओं को खोजें जिनमें कोई रिक्तियाँ नहीं हैं:

Project.where.not(Vacancies.where("vacancies.project_id = projects.id").exists)

संपादित करें: हाल के रेल संस्करणों में आपको एक पदावनति चेतावनी मिलती है, जो आपको यह बताती है कि आप को इस पर भरोसा नहीं किया existsजा सकता है। इसे ठीक करें:

Project.where.not(Vacancies.where("vacancies.project_id = projects.id").arel.exists)

संपादित करें: यदि आप कच्चे SQL के साथ असहज हैं, तो प्रयास करें:

Project.where.not(Vacancies.where(Vacancy.arel_table[:project_id].eq(Project.arel_table[:id])).arel.exists)

arel_tableउदाहरण के लिए, के उपयोग को छिपाने के लिए वर्ग विधियों को जोड़कर आप इसे कम गन्दा बना सकते हैं :

class Project
  def self.id_column
    arel_table[:id]
  end
end

... इसलिए ...

Project.where.not(
  Vacancies.where(
    Vacancy.project_id_column.eq(Project.id_column)
  ).arel.exists
)

ये दो सुझाव काम नहीं करते ... सबक्वेरी या Vacancy.where("vacancies.project_id = projects.id").exists?तो उपज देती है । एक है । truefalseProject.where(true)ArgumentError
लेस

Vacancy.where("vacancies.project_id = projects.id").exists?निष्पादित नहीं करने जा रहा है - यह एक त्रुटि उत्पन्न करेगा क्योंकि projectsसंबंध क्वेरी में मौजूद नहीं होगा (और नमूना कोड में कोई प्रश्नचिह्न नहीं है)। इसलिए इसे दो भावों में बदलना वैध नहीं है और काम नहीं करता है। हाल ही में रेल में Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)एक डेप्रिसिएशन चेतावनी उठती है ... मैं प्रश्न को अपडेट करूंगा।
डेविड एल्ड्रिज

4

रेल 4+ में, आप एक ही उत्तर पाने के लिए शामिल या उत्सुक_उपयोग भी कर सकते हैं :

Project.includes(:vacancies).references(:vacancies).
        where.not(vacancies: {id: nil})

Project.eager_load(:vacancies).where.not(vacancies: {id: nil})

4

मुझे लगता है कि एक सरल उपाय है:

Project.joins(:vacancies).distinct

1
.Distinct: (रिक्तियों) यह भी "अलग", जैसे Project.joins उपयोग करना संभव है
Metaphysiker

तुम सही हो! # औक़ात की जगह # शिद्दत का इस्तेमाल करना बेहतर है। #unq सभी ऑब्जेक्ट्स को मेमोरी में लोड करेगा, लेकिन #distinct एक डेटाबेस साइड पर कैलकुलेशन करेगा।
यूरी करपोविच

3

ज्यादा रेल के जादू के बिना, आप कर सकते हैं:

Project.where('(SELECT COUNT(*) FROM vacancies WHERE vacancies.project_id = projects.id) > 0')

इस प्रकार की स्थितियाँ सभी रेल संस्करणों में काम करेंगी क्योंकि अधिकांश कार्य सीधे डीबी पक्ष पर किए जाते हैं। इसके अलावा, जंजीर .countविधि अच्छी तरह से भी काम करेगा। मुझे पहले की तरह प्रश्नों द्वारा जला दिया गया है Project.joins(:vacancies)। बेशक, पेशेवरों और विपक्ष हैं क्योंकि यह डीबी अज्ञेयवादी नहीं है।


1
यह ज्वाइन और ग्रुप मेथड की तुलना में बहुत धीमा है, क्योंकि 'सेलेक्ट काउंट (*) ..' सबकुछ प्रत्येक प्रोजेक्ट के लिए निष्पादित होगा।
यासिरअजगर

@YasirAzgar "समूह में मौजूद" विधि की तुलना में सम्मिलित और समूह विधि धीमी है क्योंकि यह अभी भी सभी चाइल्ड रो को एक्सेस करेगा, भले ही उनमें से एक लाख हों।
डेविड एल्ड्रिज

0

तुम भी उपयोग कर सकते हैं EXISTSके साथ SELECT 1से सभी स्तंभों का चयन करने के बजाय vacanciesतालिका:

Project.where("EXISTS(SELECT 1 from vacancies where projects.id = vacancies.project_id)")

-6

त्रुटि आपको बता रही है कि मूल रूप से परियोजनाओं में रिक्तियों का कॉलम नहीं है।

यह काम करना चाहिए

Project.joins(:vacancies).where('COUNT(vacancies.project_id) > 0')

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