2010-02-04 5 views
13

레일스에 HABTM 관계가있는 두 개의 테이블이 있습니다. 다음과 같은 뭔가 :Rails에서 대량 HABTM 연관을 만드는 가장 빠른 방법은 무엇입니까?

 
class Foo < ActiveRecord::Base 
    has_and_belongs_to_many :bars 
end 

class Bar < ActiveRecord::Base 
    has_and_belongs_to_many :foos 
end 

지금 나는 새로운 Foo 개체가, 내가 미리가로드했습니다하는, 그것에 바 수천 질량이-할당 할 :

 
@foo = Foo.create 
@bars = Bar.find_all_by_some_attribute(:a) 

가장 빠른 무엇입니까 이 일을하는 방법?

 
@foo.bars = @bars 
@foo.bars << @bars 

그리고 모두가 각 bar에 대해 다음과 같은 항목으로, 정말 느리게 실행 : 내가 해봤 bars_foos SQL FROM 필드가 표시

bars_foos 열 (1.1ms) (0.6ms) INSERT INTO bars_foos (bar_id, foo_id)는 I에보고 된 값 (100, 117,200)

ar- 확장이지만, import 함수는 조인 테이블에 대한 사용을 배제하는 모델 (Model.import) 없이는 작동하지 않는 것처럼 보입니다.

내가 SQL을 작성해야합니까, 또는 레일은 예뻐 방법이 있습니까?

+0

? 아무도? 너희들은 레이 업과 "모범 사례"에 관한 질문에 뛰어 올랐다. – klochner

답변

6

성능면에서는 SQL을 사용하고 쿼리 당 여러 행을 대량 삽입하는 것이 가장 좋습니다. 당신은 하나의 쿼리에서 수천 개의 행을 삽입 할 수 있어야한다

INSERT INTO foos_bars (foo_id,bar_id) VALUES (1,1),(1,2),(1,3).... 

: 당신은 같은 무언가를 INSERT 문을 구축 할 수 있습니다. 나는 당신의 mass_habtm 방법을 시도하지 않은,하지만 당신은 같은 것을 위해 할 수있는 것처럼 보인다 : 당신이 "some_attribute"로 줄을 검색 할 경우, 또한

bars = Bar.find_all_by_some_attribute(:a) 
foo = Foo.create 
values = bars.map {|bar| "(#{foo.id},#{bar.id})"}.join(",") 
connection.execute("INSERT INTO foos_bars (foo_id, bar_id) VALUES #{values}") 

을, 당신이 당신의 데이터베이스에 색인이 해당 필드를 가지고 있는지 확인하십시오.

+0

내 mass_habtm은 쿼리를 단일 검색/삽입 쿼리에 결합하기 만하면 여기에있는 것보다 훨씬 많은 * 이득을 얻을 수 있습니다. 나는 자기 자신을 고르는 것을 싫어한다. 적어도 나에게 실행 가능한 옵션을 주셔서 고마워. – klochner

1

이 7 배 빠른에 해당하는 기본 레일의 코드보다이었다

 
class << Foo 
    def mass_habtm(attr_array) 
    attr_str = attr_array.map{|a| %Q{'#{a}'} }.uniq.join(",") 
    self.connection.execute(%Q{insert into foos_bars (foo_id,bar_id) 
        select distinct foos.id,bars.id from foos,bars 
        where foos.id = #{self.id} 
        and bars.some_attribute in (#{attr_str})}) 
    end 
end 

그것은 나에게 것이이 레일에 효율적으로 지원해야한다는 간단한 충분히 작업이는 것을, 나는에 사랑 누군가가 깨끗한 방법을 가지고 있다면 듣는다.

나는 2.2.2 실행 해요, 어쩌면 그것은 3.x를 더 효율적으로 구현이야? 및 3.0.2에서도 동일하게 나타납니다.

-3

정직하게 말하면, has_and_belongs_to_many은 일을 처리하는 매우 구식의 방법입니다. 조인 테이블을 수행하는 새로운 방법 인 has_many :through을 조사해야하며 오랫동안 사용 해왔다.

class Foo < ActiveRecord::Base 
    has_many :foobars 
    has_many :bars, :through => :foobars 

    def add_many_bars(bars) 
    bars.each do |bar| 
     self.bars << bar 
    end 
    end 
end 

class Bar < ActiveRecord::Base 
    has_many :foobars 
    has_many :foos, :through => :foobars 
end 

class FooBar < ActiveRecord::Base 
    belongs_to :foo 
    belongs_to :bar 
end 

또한, 생산에 동일하게 실행 해보십시오 캐싱의 많은 반드시 개발에 발생하지 않습니다 생산에 지남에 따라 당신이 얻을 성능 어떤 종류의 볼 수 있습니다.

+1

멍청이가 되려하지 말고, 생산을 더 잘 할 수 있다고 추측하는 것 외에 관계를 만드는 속도라는 주된 질문을 결코 다루지 않았다. 캐싱이 도움이 될 수는 있지만 SQL이 공식화되는 방식은 거의 바뀌지 않을 것입니다. 또한 has_many-> through에는 model 클래스가 필요하기 때문에 habtm이이 항목을 최적화하는 더 좋은 후보라고 주장 할 것입니다. 이는 모델 조인 모델에 콜백이있을 수 있음을 의미합니다. – klochner

+1

그리고 저는 여러분의 구현이 * 충분히 빠르지 않은 것으로 제시 한 것보다 느릴 것이라고 확신합니다. – klochner

+0

예.하지만 조인 모델을 사용하면 액세스 할 수 없었던 파인더 및 기타 옵션을 사용하여 다른 작업을 수행 할 수 있습니다. 적어도이 방법을 사용하면 앞뒤로 가고 싶을 때 두 모델에서 코드를 반복하지 않고 코드를 넣는 다른 모델을 사용할 수 있습니다. –

5

activerecord-import을 계속 볼 수 있습니다. 모델 없이는 작동하지 않는다는 것이 맞지만 가져 오기 전용 모델을 만들 수 있습니다.

class FooBar < ActiveRecord::Base; end 

FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]] 

당신은 여기에서와 같이 HABTM가 완전히 채워됩니다 보장하기 위해 트랜잭션이 포장 할 수 있습니다 정말

ActiveRecord::Base.transaction do 
    imported_foo = Foo.import(foo_names, foo_values) 
    imported_bar = Bar.import(bar_names, bar_values) 
    FooBar.import([:foo_id, :bar_id], imported_foo.ids.zip(imported_bar.ids) 
end 
관련 문제