간단한 연관성을 고려하십시오 …
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 } )
답변
친구가없는 사람
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)")