রিলে কোনও সম্পর্কিত রেকর্ডের সাথে রেকর্ডগুলি সন্ধান করতে চান


177

একটি সাধারণ সমিতি বিবেচনা করুন ...

class Person
   has_many :friends
end

class Friend
   belongs_to :person
end

আর্লে এবং / অথবা মেটা_এই জায়গায় কোনও বন্ধু নেই এমন সমস্ত ব্যক্তিকে পাওয়ার সবচেয়ে সহজ উপায় কী?

এবং তারপরে কি একটি has_many সম্পর্কে: সংস্করণ মাধ্যমে

class Person
   has_many :contacts
   has_many :friends, :through => :contacts, :uniq => true
end

class Friend
   has_many :contacts
   has_many :people, :through => :contacts, :uniq => true
end

class Contact
   belongs_to :friend
   belongs_to :person
end

আমি সত্যিই কাউন্টার_ ক্যাশেটি ব্যবহার করতে চাই না - এবং আমি যা পড়েছি তা থেকে has_many: এর মাধ্যমে কাজ করে না

আমি সমস্ত ব্যক্তির সাথে টানতে চাই না friend

প্রশ্নগুলির পারফরম্যান্স ব্যয়ের বিষয়টি আমি আপত্তি করি না

এবং প্রকৃত এসকিউএল থেকে আরও অনেক দূরে ...

উত্তর:


110

এটি এখনও এসকিউএল এর খুব কাছাকাছি, তবে এটি প্রথম ক্ষেত্রে কোনও বন্ধুবিহীন সবাইকে পাওয়া উচিত:

Person.where('id NOT IN (SELECT DISTINCT(person_id) FROM friends)')

6
শুধু কল্পনা করুন আপনার বন্ধুদের টেবিলে 10000000 রেকর্ড রয়েছে। সেক্ষেত্রে পারফরম্যান্সের কী হবে?
গুডনাইস্বেব

@ গুডনাইসওয়েব আপনার সদৃশ ফ্রিকোয়েন্সি অনুসারে আপনি সম্ভবত এটিকে ফেলে দিতে পারেন DISTINCT। অন্যথায়, আমি মনে করি আপনি সেই ক্ষেত্রে ডেটা এবং সূচীটি স্বাভাবিক করতে চান want আমি কোনও friend_idsহস্টোর বা সিরিয়ালযুক্ত কলাম তৈরি করে তা করতে পারি । তারপরে আপনি বলতে পারেনPerson.where(friend_ids: nil)
ইউনিক্সমনকি

আপনি যদি স্কয়ার ব্যবহার করতে চলেছেন তবে সম্ভবত এটি ব্যবহার করা ভাল not exists (select person_id from friends where person_id = person.id)(বা সম্ভবত people.idবা persons.idআপনার টেবিলটি কী তার উপর নির্ভর করে)) কোনও নির্দিষ্ট পরিস্থিতিতে সবচেয়ে দ্রুতগতি কী তা নিশ্চিত নন, তবে অতীতেও আমার পক্ষে এটি ভালভাবে কাজ করেছে যখন আমি অ্যাক্টিভেকর্ড ব্যবহার করার চেষ্টা করছিল না।
নরোজ করুন

441

উত্তম:

Person.includes(:friends).where( :friends => { :person_id => nil } )

এইচএমটিটির জন্য এটি মূলত একই জিনিস, আপনি নির্ভর করে যে কোনও বন্ধুবিহীন ব্যক্তির কোনও যোগাযোগ থাকবে না:

Person.includes(:contacts).where( :contacts => { :person_id => nil } )

হালনাগাদ

has_oneমন্তব্যগুলিতে একটি প্রশ্ন পেয়েছেন , তাই কেবল আপডেট হচ্ছে। এখানে কৌশলটি includes()সমিতির whereনামটি প্রত্যাশা করে তবে সারণির নামটি প্রত্যাশা করে। কোনও has_oneসংস্থার জন্য সাধারণত একবচনতে প্রকাশ করা হবে, যাতে পরিবর্তন হয় তবে where()অংশটি যেমন থাকে তেমন থাকে। সুতরাং যদি Personকেবল has_one :contactতবেই আপনার বক্তব্যটি হ'ল:

Person.includes(:contact).where( :contacts => { :person_id => nil } )

আপডেট 2

কেউ বিপরীত সম্পর্কে জিজ্ঞাসা করেছেন, বন্ধু নেই লোকে। যেমন আমি নীচে মন্তব্য করেছি, এটি আসলে আমাকে উপলব্ধি করেছে যে শেষ ক্ষেত্রটি (উপরে: দ্য :person_id) আসলে আপনি যে মডেলটি ফিরে আসছেন তার সাথে সম্পর্কিত হতে হবে না, এটি কেবল যোগদানের টেবিলে একটি ক্ষেত্র হতে হবে। তারা সব হতে যাচ্ছে nilতাই এটি তাদের যে কোনও হতে পারে। এটি উপরোক্তগুলির একটি সহজ সমাধানের দিকে নিয়ে যায়:

Person.includes(:contacts).where( :contacts => { :id => nil } )

এবং এরপরে কোনও লোককে না দিয়ে বন্ধুদের ফেরাতে এটিকে পরিবর্তন করা সহজতর হয়ে ওঠে, আপনি কেবল সামনের অংশে পরিবর্তন করুন:

Friend.includes(:contacts).where( :contacts => { :id => nil } )

আপডেট 3 - রেল 5

অ্যালসনকে চমৎকার রেল ৫ টি সমাধানের জন্য ধন্যবাদ (নীচে তার উত্তরের জন্য তাকে কিছু + 1 দিন), আপনি সমিতিটি left_outer_joinsলোড করা এড়াতে ব্যবহার করতে পারেন :

Person.left_outer_joins(:contacts).where( contacts: { id: nil } )

আমি এটি এখানে অন্তর্ভুক্ত করেছি যাতে লোকেরা এটি খুঁজে পেতে পারে তবে তিনি এর জন্য +1 এর প্রাপ্য। দুর্দান্ত সংযোজন!

আপডেট 4 - রেল 6.1

আগামী 6.1 এ আপনি এটি করতে পারেন যে উল্লেখ করার জন্য টিম পার্ককে ধন্যবাদ :

Person.where.missing(:contacts)

ধন্যবাদ তিনি যে পোস্টটি দিয়েছিলেন।


4
আপনি এটিকে এমন একটি সুযোগে অন্তর্ভুক্ত করতে পারেন যা আরও পরিষ্কার হবে।
আইটান

3
আরও ভাল উত্তর, নিশ্চিত নয় যে অন্যটি কেন গৃহীত হিসাবে রেট করা হয়েছে।
তামিক সোজিভ

5
হ্যাঁ এটি করে, কেবল এটি ধরে নিয়ে যে আপনার has_oneসংস্থার জন্য আপনার একক নাম রয়েছে আপনাকে includesকলটিতে সমিতির নাম পরিবর্তন করতে হবে the সুতরাং ধরে has_one :contactPersonPerson.includes(:contact).where( :contacts => { :person_id => nil } )
নিচ্ছি

3
আপনি যদি আপনার বন্ধুর মডেলটিতে কাস্টম টেবিলের নাম self.table_name = "custom_friends_table_name"ব্যবহার করেন ( ), তবে ব্যবহার করুন Person.includes(:friends).where(:custom_friends_table_name => {:id => nil})
জেক

5
পাগল 6.1 মধ্যে @smathy একটি সুন্দর আপডেট একটি যোগ missingঠিক করতে পদ্ধতি এই !
টিম পার্ক

171

বুদ্ধিমানের একটি ভাল রেয়েল রয়েছে 3 টি উত্তর।

রেল 5 এর জন্য , আপনি left_outer_joinsসমিতি লোড করা এড়াতে ব্যবহার করতে পারেন ।

Person.left_outer_joins(:contacts).where( contacts: { id: nil } )

এপিআই ডক্স পরীক্ষা করে দেখুন । এটি টান অনুরোধ # 12071 তে প্রবর্তিত হয়েছিল ।


এটার কি কোন ডাউনসাইড আছে? আমি চেক করেছি এবং এটি 0.1 এমএস দ্রুত গতিতে লোড হয়েছে। অন্তর্ভুক্ত
কিওয়ারটি

আপনি যদি পরে এটি অ্যাক্সেস করেন তবে অ্যাসোসিয়েশন লোড না করা একটি খারাপ দিক, তবে আপনি যদি এটি অ্যাক্সেস না করেন তবে একটি উপকার। আমার সাইটগুলির জন্য, একটি 0.1 মিমি হিট বেশ নগণ্য, তাই .includesলোডের সময় অতিরিক্ত ব্যয় এমন কিছু হবে না যা আমি অনুকূলকরণের বিষয়ে খুব চিন্তিত হব। আপনার ব্যবহারের ক্ষেত্রে ভিন্ন হতে পারে।
আনসন

1
এবং যদি আপনার কাছে এখনও 5 টি রেল না থাকে তবে আপনি এটি করতে পারেন: Person.joins('LEFT JOIN contacts ON contacts.person_id = persons.id').where('contacts.id IS NULL')এটি সুযোগ হিসাবে ঠিক ঠিক কাজ করে। আমি আমার রেল প্রকল্পগুলিতে সর্বদা এটি করি।
ফ্রাঙ্ক

3
এই পদ্ধতির বড় সুবিধা হ'ল মেমরি সঞ্চয়। আপনি যখন এটি করেন includes, তখন সমস্ত এআর অবজেক্ট মেমরিতে লোড হয়ে যায়, যা টেবিলগুলি আরও বড় হয়ে ওঠার কারণে খারাপ জিনিস হতে পারে। আপনার যদি যোগাযোগের রেকর্ডটিতে অ্যাক্সেসের প্রয়োজন না হয় তবে left_outer_joinsযোগাযোগটি মেমরিতে লোড করে না। এসকিউএল অনুরোধের গতি একই, তবে সামগ্রিক অ্যাপ্লিকেশন সুবিধাটি আরও বড়।
ক্রিসম্যান্ডারসন

2
এটা সত্যই খুব ভালো! ধন্যবাদ! এখন যদি রেল দেবতারা সম্ভবত এটি একটি সরল হিসাবে প্রয়োগ করতে পারেন Person.where(contacts: nil)বা Person.with(contact: contact)যেখানে খুব দূরের জায়গাটিকে 'যথাযথতা' হিসাবে ব্যবহার করা হয় - তবে সেই যোগাযোগটি দেওয়া হয়েছে: ইতিমধ্যে বিশ্লেষণ করা হয়েছে এবং একটি সমিতি হিসাবে চিহ্নিত করা হয়েছে, এটি যুক্তিযুক্ত বলে মনে হয় যে প্রয়োজনীয়গুলি সহজেই যা প্রয়োজন তা কার্যকর করতে পারে log ...
জাস্টিন ম্যাক্সওয়েল

14

যে ব্যক্তিদের কোনও বন্ধু নেই

Person.includes(:friends).where("friends.person_id IS NULL")

অথবা তার কমপক্ষে একটি বন্ধু রয়েছে

Person.includes(:friends).where("friends.person_id IS NOT NULL")

আপনি স্কেপগুলি সেটআপ করে আরেলের সাথে এটি করতে পারেন Friend

class Friend
  belongs_to :person

  scope :to_somebody, ->{ where arel_table[:person_id].not_eq(nil) }
  scope :to_nobody,   ->{ where arel_table[:person_id].eq(nil) }
end

এবং তারপরে, কমপক্ষে একটি বন্ধু রয়েছে এমন ব্যক্তিরা:

Person.includes(:friends).merge(Friend.to_somebody)

বন্ধুহীন:

Person.includes(:friends).merge(Friend.to_nobody)

2
আমি মনে করি আপনার কাছে করতে পারেন: Person.includes (: বন্ধুদের) .where (বন্ধুদের: {ব্যক্তিঃ শূন্য})
ReggieB

1
দ্রষ্টব্য: একত্রীকরণ কৌশল মাঝে মাঝে মত একটি সতর্কবার্তা উত্পাদ করতে পারেনDEPRECATION WARNING: It looks like you are eager loading table(s) Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string
genkilabs

12

ডার্কো এবং ইউনিকস্মনকি উভয়ই উত্তর আমার যা প্রয়োজন তা পেয়েছে - ধন্যবাদ!

আমি আমার বাস্তব অ্যাপ্লিকেশনটিতে উভয়ের চেষ্টা করেছিলাম এবং তাদের জন্য সময় পেয়েছি - এখানে দুটি স্কপ রয়েছে:

class Person
  has_many :contacts
  has_many :friends, :through => :contacts, :uniq => true
  scope :without_friends_v1, -> { where("(select count(*) from contacts where person_id=people.id) = 0") }
  scope :without_friends_v2, -> { where("id NOT IN (SELECT DISTINCT(person_id) FROM contacts)") }
end

একটি বাস্তব অ্যাপ্লিকেশন দিয়ে এটি চালান - ~ 700 'ব্যক্তি' রেকর্ড সহ ছোট টেবিল - গড়ে পাঁচ রান

ইউনিকসমনকি এর পদ্ধতির ( :without_friends_v1) 813 মিমি / ক্যোয়ারী

ডার্কোর পদ্ধতির ( :without_friends_v2) 891 মিমি / ক্যোয়ারী (~ 10% ধীর)

তবে তখন DISTINCT()...আমার কাছে এমনটি ঘটেছিল যে আমি Personকোনও সংখ্যার সাথে রেকর্ডের সন্ধান করছি আমাকে কল করার দরকার নেই Contacts- সুতরাং তাদের কেবল NOT INযোগাযোগের তালিকা হওয়া দরকার person_ids। সুতরাং আমি এই সুযোগ চেষ্টা করেছিলাম:

  scope :without_friends_v3, -> { where("id NOT IN (SELECT person_id FROM contacts)") }

এটি একই ফলাফল পায় তবে প্রায় 425 এমএস / কল - প্রায় অর্ধেক সময় ...

এখন আপনার DISTINCTঅন্যান্য অনুরূপ প্রশ্নের প্রয়োজন হতে পারে - তবে আমার ক্ষেত্রে এটি ঠিক কাজ করছে বলে মনে হচ্ছে।

আপনার সাহায্যের জন্য ধন্যবাদ


5

দুর্ভাগ্যক্রমে, আপনি সম্ভবত এসকিউএল জড়িত একটি সমাধান খুঁজছেন, তবে আপনি এটিকে একটি সুযোগে সেট করতে পারেন এবং তারপরে কেবল সেই সুযোগটি ব্যবহার করতে পারেন:

class Person
  has_many :contacts
  has_many :friends, :through => :contacts, :uniq => true
  scope :without_friends, where("(select count(*) from contacts where person_id=people.id) = 0")
end

তারপরে এগুলি পেতে, আপনি কেবলমাত্র করতে পারেন Person.without_friends, এবং আপনি অন্যান্য আরেল পদ্ধতিতে এটি চেইনও করতে পারেন:Person.without_friends.order("name").limit(10)


1

কোনও অস্তিত্বের সাথে সম্পর্কযুক্ত সাব-কোয়েরি দ্রুত হওয়া উচিত, বিশেষত পিতামাতার রেকর্ডের সাথে সন্তানের সারি গণনা এবং অনুপাত বৃদ্ধি পায়।

scope :without_friends, where("NOT EXISTS (SELECT null FROM contacts where contacts.person_id = people.id)")

1

এছাড়াও, উদাহরণস্বরূপ এক বন্ধু দ্বারা ফিল্টার আউট:

Friend.where.not(id: other_friend.friends.pluck(:id))

3
এটি সাবকোয়ারির চেয়ে 2 টি ক্যোয়ারিতে ফল দেবে।
গ্রেপসডক
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.