2014-04-09 5 views
4

두 개의 공용체가 교차하는 꽤 복잡한 ActiveRecord 쿼리를 생성하려고합니다. 내 솔루션은 작동하지만 생성 된 SQL에는 많은 중복이 있습니다. 더 효율적인 방법이 있습니까?효율적인 방법으로 ActiveRecord/Arel (Rails 4)에서 공용체의 교차 부분을 수행 하시겠습니까?

내 우주에는 사람, 고양이, 개, 책 및 앨범이 있습니다. (이것은 더 복잡 협회 따라서도 이보다 SQL을 포함 실제 모델 설정의 장난감 버전입니다.) 나는 1을 소유 존스라는 이름의 모든 사람) 월러스라는 이름의 애완 동물과를 검색 할

class Person < ActiveRecord::Base 
    has_many :cats 
    has_many :dogs 
    has_many :books 
    has_many :albums 

    ... 
end 

2) 1996 년에 발표 한 책이나 앨범은이 같은 쿼리를 구성 할 수 있도록하려면 :

Person.named("Jones").with_pet_named("Wallace").with_book_or_album_released_in(1996) 

을 부분은 쉽게 "존스라는 이름의"쿼리의

scope :named, ->(name) { where(name: name) } 

다른 두 부분 ~은 ~이다. 칙칙한. 내가 (: http://danshultz.github.io/talks/mastering_activerecord_arel/#/이 이야기에서 빌려온 패턴) : 노동 조합을 구성 Arel 방법을 사용하고, 지금하고 있어요 방법은 내가 함께 이러한 범위 및 방법을 체인 때

class Person 
    ... 

    def self.with_pet_named(name) 
    with_cat_named = joins(:cats).where(cats: {name: name}) 
    with_dog_named = joins(:dogs).where(dogs: {name: name}) 
    with_cat_or_dog_named = with_cat_named.union(with_dog_named) 
    from(arel_table.create_table_alias(with_cat_or_dog_named, :people)) 
    end 

    def self.with_book_or_album_released_in(year) 
    with_book_released_in = joins(:books).where(books: {release_date: year}) 
    with_album_released_in = joins(:albums).where(albums: {release_date: year}) 
    with_book_or_album_released_in = with_book_released_in.union(with_album_released_in) 
    from(arel_table.create_table_alias(with_book_or_album_released_in, :people)) 
    end 
end 

문제가 발생합니다. 정확한 사람들이 검색되지만 SQL은 별보다 적게 보입니다.

Person.named("Jones").with_pet_named("Wallace").with_book_or_album_released_in(1996). 
    to_sql 

반환

SELECT "people".* FROM 
    (SELECT "people".* FROM 
    (SELECT "people".* FROM "people" 
     INNER JOIN "cats" 
     ON "cats"."person_id" = "people"."id" 
     WHERE "people"."name" = 'Jones' AND "cats"."name" = 'Wallace' 
     UNION 
     SELECT "people".* FROM "people" 
     INNER JOIN "dogs" 
     ON "dogs"."person_id" = "people"."id" 
     WHERE "people"."name" = 'Jones' AND "dogs"."name" = 'Wallace' 
    ) "people" 
     INNER JOIN "books" 
     ON "books"."person_id" = "people"."id" 
     WHERE "people"."name" = 'Jones' AND "book"."release_date" = 1996 
    UNION 
    SELECT "people".* FROM 
    (SELECT "people".* FROM "people" 
     INNER JOIN "cats" 
     ON "cats"."person_id" = "people"."id" 
     WHERE "people"."name" = 'Jones' AND "cats"."name" = 'Wallace' 
     UNION 
     SELECT "people".* FROM "people" 
     INNER JOIN "dogs" 
     ON "dogs"."person_id" = "people"."id" 
     WHERE "people"."name" = 'Jones' AND "dogs"."name" = 'Wallace' 
    ) "people" 
     INNER JOIN "albums" 
     ON "albums"."person_id" = "people"."id" 
     WHERE "people"."name" = 'Jones' AND "album"."release_date" = 1996 
) "people" 
    WHERE "people"."name" = 'Jones' 

은 내가 더 이런 식으로 뭔가를 할 생각 :

다루기 힘든 반면, 적어도 중복되지 않습니다,
SELECT "people".* FROM 
    ((SELECT "people".* FROM "people" 
     INNER JOIN "cats" 
     ON "cats"."person_id" = "people"."id" 
     WHERE "cats"."name" = 'Wallace' 
     UNION 
     SELECT "people".* FROM "people" 
     INNER JOIN "dogs" 
     ON "dogs"."person_id" = "people"."id" 
     WHERE "dogs"."name" = 'Wallace' 
    ) INTERSECT 
    (SELECT "people".* FROM "people" 
     INNER JOIN "books" 
     ON "books"."person_id" = "people"."id" 
     WHERE "book"."release_date" = 1996 
     UNION 
     SELECT "people".* FROM "people" 
     INNER JOIN "albums" 
     ON "albums"."person_id" = "people"."id" 
     WHERE "album"."release_date" = 1996 
    ) 
) "people" 
    WHERE "people"."name" = 'Jones' 

. ActiveRecord 및 Arel 메서드를 사용하여 내 모델이나 연결을 변경하지 않고도 더 깔끔한 SQL 쿼리를 얻을 수 있습니까?

답변

2

db 스키마가 없어도이를 테스트하기는 어렵지만 원하는 SQL에 더 가까워 야한다고 생각합니다.

참고 :이 '솔루션'은 코드에서 사용할 수있는 체인 연결을 끊습니다.

class Person 
    #… 
    def self.with_pet_named(name) 
    with_cat_named = joins(:cats).where(cats: {name: name}) 
    with_dog_named = joins(:dogs).where(dogs: {name: name}) 
    Arel::Nodes::Union.new(with_cat_named.ast, with_dog_named.ast) 
    end 

    def self.with_book_or_album_released_in(year) 
    with_book_released_in = joins(:books).where(books: {release_date: year}) 
    with_album_released_in = joins(:albums).where(albums: {release_date: year}) 
    Arel::Nodes::Union.new(with_book_released_in.ast, with_album_released_in.ast) 
    end 
    #… 
end 

inter = Arel::Nodes::Intersect.new(
    Person.with_pet_named('Wallace'), 
    Person.with_book_or_album_released_in(1996) 
) 
p = Arel::Nodes::TableAlias.new(inter, :p) 
Person.from(p).where(p[:name].eq('Jones')).to_sql