연관 수가 0보다 큰 모든 레코드 찾기


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).

답변:


65

joins기본적으로 내부 조인을 사용하므로를 사용 Project.joins(:vacancies)하면 실제로 관련 공석이있는 프로젝트 만 반환됩니다.

최신 정보:

주석에서 @mackskatz가 지적했듯이 group절 없이 위 코드는 둘 이상의 공석이있는 프로젝트에 대해 중복 프로젝트를 반환합니다. 중복을 제거하려면

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

최신 정보:

@Tolsee가 지적했듯이 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
그러나 group by 절을 적용하지 않으면 둘 이상의 공석이있는 프로젝트에 대해 여러 프로젝트 개체가 반환됩니다.
mackshkatz

1
그러나 효율적인 SQL 문을 생성하지 않습니다.
David Aldridge

이것이 바로 Rails입니다. SQL 답변을 제공 할 수 있다면 (그리고 이것이 효율적이지 않은 이유를 설명 할 수 있다면) 훨씬 더 도움이 될 수 있습니다.
jvnill

어떻게 생각 Project.joins(:vacancies).distinct하세요?
Tolsee

1
그것은 BTW @Tolsee을입니다 : D
Tolsee

167

1) 최소 1 개의 공석이있는 프로젝트를 얻으려면 :

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

2) 공석이 2 개 이상인 프로젝트를 얻으려면 :

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')? 대신 프로젝트 ID의 공석의 수 쿼리
키스 Mattix

아니, @KeithMattix, 그것은 해야 하지. 그것은 수 있습니다 , 그것은 당신에게 더 나은 읽는 경우가 될; 그것은 선호도의 문제입니다. 모든 행에 값이 있다고 보장되는 조인 테이블의 모든 필드를 사용하여 계산할 수 있습니다. 가장 의미있는 후보는 projects.id, project_idvacancies.id. project_id조인이 이루어지는 필드이기 때문에 계산 하기로 선택했습니다 . 당신이 원한다면 조인의 척추. 또한 이것이 조인 테이블임을 상기시킵니다.
Arta

36

예, 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

groupor 와 결합 된 has_many 테이블에 대한 내부 조인을 수행하는 uniq것은 잠재적으로 매우 비효율적이며 SQL에서는 EXISTS상관 하위 쿼리와 함께 사용하는 세미 조인으로 더 잘 구현됩니다 .

이를 통해 쿼리 옵티마이 저는 올바른 project_id를 가진 행이 있는지 확인하기 위해 vacancies 테이블을 조사 할 수 있습니다. 해당 project_id가있는 행이 1 개인 지 백만 개인지는 중요하지 않습니다.

Rails에서 그렇게 간단하지는 않지만 다음을 통해 달성 할 수 있습니다.

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

마찬가지로 공석이없는 모든 프로젝트를 찾습니다.

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

편집 : 최근 Rails 버전에서는 existsarel에 위임되는 것에 의존하지 말라고 알려주는 폐기 경고가 표시 됩니다. 이 문제를 다음과 같이 수정하십시오.

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?true또는 false. Project.where(true)입니다 ArgumentError.
Les Nightingill

Vacancy.where("vacancies.project_id = projects.id").exists?실행되지 않을 것 projects입니다. 쿼리에 관계가 존재하지 않기 때문에 오류가 발생 합니다 (위의 샘플 코드에도 물음표가 없습니다). 따라서 이것을 두 개의 표현식으로 분해하는 것은 유효하지 않으며 작동하지 않습니다. 최근 Rails Project.where(Vacancies.where("vacancies.project_id = projects.id").exists)에서 사용 중단 경고가 발생했습니다. 질문을 업데이트하겠습니다.
David Aldridge

4

Rails 4+에서는 include 또는 eager_load 를 사용 하여 동일한 답을 얻을 수도 있습니다 .

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 (: vacancies) .distinct
Metaphysiker

당신이 맞아요! #uniq 대신 #distinct를 사용하는 것이 좋습니다. #uniq는 모든 객체를 메모리에로드하지만 #distinct는 데이터베이스 측에서 계산을 수행합니다.
Yuri Karpovich 2019 년

3

Rails의 마법없이 다음을 수행 할 수 있습니다.

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

이러한 유형의 조건은 대부분의 작업이 DB 측에서 직접 수행되므로 모든 Rails 버전에서 작동합니다. 또한 연결 .count방법도 잘 작동합니다. 나는 Project.joins(:vacancies)이전 과 같은 질문에 타 버렸다 . 물론 DB에 구애받지 않기 때문에 장단점이 있습니다.


1
각 프로젝트에 대해 'select count (*) ..'하위 쿼리가 실행되기 때문에 join 및 group 메서드보다 훨씬 느립니다.
YasirAzgar

@YasirAzgar 조인 및 그룹 메서드는 백만 개가 있더라도 모든 자식 행에 계속 액세스하므로 "존재"메서드보다 느립니다.
David Aldridge

0

테이블 에서 모든 열을 선택 EXISTS하는 SELECT 1대신 with 를 사용할 수도 있습니다 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
Kamil Lelonek 2014
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.