[ruby-on-rails] Rails에서 관련 레코드가없는 레코드를 찾으려면

간단한 연관성을 고려하십시오 …

class Person
   has_many :friends
end

class Friend
   belongs_to :person
end

ARel 및 / 또는 meta_where에 친구가없는 모든 사람을 얻는 가장 깨끗한 방법은 무엇입니까?

그리고 has_many : through 버전은 어떻습니까?

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

나는 정말로 counter_cache를 사용하고 싶지 않습니다. 그리고 읽은 것으로부터 has_many와는 작동하지 않습니다 :

모든 person.friends 레코드를 가져 와서 Ruby에서 반복하고 싶지 않습니다. meta_search gem과 함께 사용할 수있는 쿼리 / 범위를 갖고 싶습니다.

나는 쿼리의 성능 비용을 신경 쓰지 않는다

실제 SQL에서 멀수록 멀수록 좋습니다.



답변

이것은 여전히 ​​SQL과 매우 비슷하지만 첫 번째 경우 친구가없는 모든 사람을 확보해야합니다.

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


답변

보다 나은:

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

hmt의 경우 기본적으로 동일한 내용이므로 친구가없는 사람도 연락처가 없다는 사실에 의존합니다.

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

최신 정보

has_one의견 에 대한 질문이 있으므로 업데이트하십시오. 여기서 트릭 includes()은 연결 이름은 필요하지만 where테이블 이름은 필요합니다. A에 대한 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-Rails 5

탁월한 Rails 5 솔루션에 대한 @Anson 덕분에 (아래 답변에 +1을 줄 수 있음) left_outer_joins연결을로드하지 않도록 사용할 수 있습니다 .

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

사람들이 찾을 수 있도록 여기에 포함 시켰지만, +1을받을 자격이 있습니다. 훌륭한 추가!

업데이트 4-Rails 6.1

다가오는 6.1에서 다음을 수행 할 수 있음을 지적한 Tim Park 에게 감사드립니다 .

Person.where.missing(:contacts)

그가 연결 한 게시물 덕분에 .


답변

smathy는 좋은 Rails 3 답변을 가지고 있습니다.

Rails 5의left_outer_joins 경우 연결로드를 피하기 위해 사용할 수 있습니다 .

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

API 문서를 확인하십시오 . 풀 요청 # 12071 에서 소개되었습니다 .


답변

친구가없는 사람

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

아니면 적어도 친구가 하나 있습니다

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

범위를 설정하여 Arel을 사용 하여이 작업을 수행 할 수 있습니다 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)


답변

dmarkow와 Unixmonkey의 답변 모두 내가 필요한 것을 얻습니다-감사합니다!

내 실제 앱에서 두 가지를 모두 시도하고 타이밍을 얻었습니다. 두 가지 범위는 다음과 같습니다.

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 ‘Person’레코드가있는 작은 테이블-평균 5 회 실행하는 실제 앱으로이를 실행하십시오.

Unixmonkey의 접근 방식 ( :without_friends_v1) 813ms / query

dmarkow의 접근 방식 ( :without_friends_v2) 891ms / 쿼리 (~ 10 % 느림)

그러나 그때 나는 NO DISTINCT()...가있는 Person기록을 찾고 있는 전화가 필요 없다. Contacts그래서 그들은 단지 NOT IN연락처 목록 이어야 한다 person_ids. 그래서 나는이 범위를 시도했다 :

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

결과는 같지만 평균 425ms / call로 거의 절반의 시간이 소요됩니다.

이제 DISTINCT다른 유사한 쿼리 가 필요할 수도 있지만 내 경우에는 잘 작동하는 것 같습니다.

당신의 도움을 주셔서 감사합니다


답변

불행히도 SQL과 관련된 솔루션을 찾고 있지만 범위에서 설정 한 다음 해당 범위를 사용할 수 있습니다.

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있으며 다른 Arel 메소드와 연결할 수도 있습니다.Person.without_friends.order("name").limit(10)


답변

NOT EXISTS 상관 서브 쿼리는 특히 행 수와 하위 레코드 대 부모 레코드의 비율이 증가함에 따라 빨라야합니다.

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