레일에서 단일 호출로 여러 개체 저장


93

다음과 같은 작업을 수행하는 레일에 메서드가 있습니다.

a = Foo.new("bar")
a.save

b = Foo.new("baz")
b.save

...
x = Foo.new("123", :parent_id => a.id)
x.save

...
z = Foo.new("zxy", :parent_id => b.id)
z.save

문제는 더 많은 엔티티를 추가할수록 더 오래 걸립니다. 나는 이것이 모든 레코드에 대해 데이터베이스를 검색해야하기 때문이라고 생각합니다. 중첩되어 있기 때문에 부모가 구해지기 전에는 아이를 구할 수 없다는 것을 알지만, 모든 부모를 한꺼번에 구한 다음 모든 아이를 구하고 싶습니다. 다음과 같이하면 좋을 것입니다.

a = Foo.new("bar")
b = Foo.new("baz")
...
saveall(a,b,...)

x = Foo.new("123", :parent_id => a.id)
...
z = Foo.new("zxy", :parent_id => b.id)
saveall(x,...,z)

그것은 단지 두 개의 데이터베이스 적중에서 모든 것을 할 것입니다. 레일에서이 작업을 쉽게 수행 할 수있는 방법이 있습니까? 아니면 한 번에 하나씩 수행해야합니까?

답변:


69

Foo.new 대신 Foo.create를 사용해보십시오. Create "객체 (또는 여러 객체)를 생성하고 유효성 검사에 통과하면 데이터베이스에 저장합니다. 객체가 데이터베이스에 성공적으로 저장되었는지 여부에 관계없이 결과 객체가 반환됩니다."

다음과 같이 여러 개체를 만들 수 있습니다.

# Create an Array of new objects
  parents = Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])

그런 다음 각 상위에 대해 create를 사용하여 연관에 추가 할 수도 있습니다.

parents.each do |parent|
  parent.children.create (:child_name => 'abc')
end

ActiveRecord 문서 와 Rails Guides on ActiveRecord 쿼리 인터페이스ActiveRecord 연관을 모두 읽는 것이 좋습니다 . 후자는 연관을 선언 할 때 클래스가 얻는 모든 메소드에 대한 가이드를 포함합니다.


78
불행히도 ActiveRecord는 생성 된 모델 당 하나의 INSERT 쿼리를 생성합니다. OP는 ActiveRecord가 수행하지 않는 단일 INSERT 호출을 원합니다.
François Beausoleil

예, 한 번의 삽입 호출로 모든 것을 얻고 싶었지만 activerecord가 그렇게 똑똑하지 않다면 쉽지 않은 것 같습니다.
captncraig 2010

@ FrançoisBeausoleil 질문 stackoverflow.com/questions/15386450/…을 살펴 보 시겠습니까? 이것이 내가 동시에 여러 레코드를 삽입 할 수없는 이유입니까?
Richlewis 2013 년

3
AR이 하나의 INSERT 또는 UPDATE를 생성하도록 ActiveRecord::Base.transaction { records.each(&:save) }할 수는 없지만 적어도 모든 INSERT 또는 UPDATE를 단일 트랜잭션에 넣을 수는 있습니다.
yuval

1
실제로 OP는 DB 액세스 속도를 높이기 위해 데이터베이스에 덜 접근하기를 원하며 ActiveRecord는 실제로 모든 호출을 하나의 트랜잭션으로 일괄 처리하여 그렇게 할 수 있습니다. (허용되는 대답이어야하는 Harish의 대답을 참조하십시오.) ActiveRecord가 허용하지 않는 것은 DB가 트랜잭션 당 하나의 INSERT 쿼리를 생성하도록하는 것이지만 지연 시간이 네트워크를 수행하는 데서 발생하므로 그다지 중요하지 않습니다. INSERT 쿼리를 수행 할 때 DB 자체 내부가 아닌 DB에 대한 액세스.
Magne

99

여러 삽입을 수행해야하므로 데이터베이스가 여러 번 히트합니다. 귀하의 경우 지연은 각 저장이 다른 DB 트랜잭션에서 수행되기 때문입니다. 모든 작업을 하나의 트랜잭션으로 묶어 지연 시간을 줄일 수 있습니다.

class Foo
  belongs_to  :parent,   :class_name => "Foo"
  has_many    :children, :class_name => "Foo", :foreign_key=> "parent_id"
end

저장 방법은 다음과 같습니다.

# build the parent and the children
a = Foo.new(:name => "bar")
a.children.build(:name => "123")

b = Foo.new("baz")
b.children.build(:name => "zxy")

#save parents and their children in one transaction
Foo.transaction do
  a.save!
  b.save!
end

save부모 개체에 대한 호출은 자식 개체를 저장합니다.


3
내가 찾던 바로 그것. 내 씨앗의 속도를 많이 높입니다. 감사합니다 :-)
Renra

16

insert_all (레일 6 이상)

Rails 6단일 명령문 으로 여러 레코드를 데이터베이스에 삽입 하는 새 메소드 insert_all을 도입했습니다 SQL INSERT.

또한이 메서드는 모델을 인스턴스화하지 않으며 Active Record 콜백 또는 유효성 검사를 호출하지 않습니다.

그래서,

Foo.insert_all([
  { first_name: 'Jamie' },
  { first_name: 'Jeremy' }
])

그것은보다 훨씬 더 효율적입니다

Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])

원하는 것은 새 레코드를 삽입하는 것뿐입니다.


1
앱을 업데이트 할 때까지 기다릴 수 없습니다. Rails 6의 멋진 기능이 너무 많습니다.
Dan

1
한 가지 주목할 점은 insert_all이 AR 콜백 및 유효성 검사를 건너 뜁니다. edgeguides.rubyonrails.org/…
sujay

11

다른 곳에서 찾은 두 가지 답변 중 하나 : by Beerlington . 이 두 가지는 성능을위한 최선의 방법입니다.


성능 측면에서 가장 좋은 방법은 SQL을 사용하고 쿼리 당 여러 행을 대량 삽입하는 것입니다. 다음과 같은 작업을 수행하는 INSERT 문을 작성할 수있는 경우 :

INSERT INTO foos_bars (foo_id, bar_id) VALUES (1,1), (1,2), (1,3) .... 단일 쿼리에 수천 개의 행을 삽입 할 수 있어야합니다. mass_habtm 방법을 시도하지 않았지만 다음과 같이 할 수있는 것 같습니다.


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}")

또한 "some_attribute"로 Bar를 검색하는 경우 해당 필드가 데이터베이스에 색인화되어 있는지 확인하십시오.


또는

여전히 activerecord-import를 볼 수 있습니다. 모델 없이는 작동하지 않는 것이 맞지만 가져 오기를 위해 모델을 만들 수 있습니다.


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

건배


삽입에는 잘 작동하지만 한 트랜잭션에서 여러 레코드를 업데이트하는 경우는 어떻습니까?
Avishai

2
업데이트하려면 upsert를 사용해야합니다 : github.com/seamusabshere/upsert . 환호
Nguyen Chien Cong

SQL 쿼리에 대해서는 매우 좋지 않습니다. ActiveRecord 및 트랜잭션을 사용해야합니다.
Kerozu 2014-06-15

나쁜 생각이 아닙니다. 하나의 삽입을 수행하는 경우 성공하거나 실패 할 것이며 트랜잭션이 필요하지 않습니다. 또는 항상 트랜잭션 블록에 해당 하나의 삽입을 래핑 할 수 있습니다.
Fernando Fabreti

이것은 나쁜 레일 연습입니다
Blair Anderson

0

이 gem "FastInserter"-> https://github.com/joinhandshake/fast_inserter 를 사용해야합니다.

이 gem은 활성 레코드를 건너 뛰고 단일 SQL 원시 쿼리 만 사용하기 때문에 많은 수의 수천 개의 레코드를 삽입하는 것이 빠릅니다.


1
gem에 대한 링크가 유용 할 수 있지만 Asker가 현재 코드 대신 사용할 수있는 코드를 제공하십시오 (질문 참조).
trincot


1
답변에는 필수 정보가 포함되어야 합니다. 답변을 편집하고 거기에 링크를 추가하고 답변 안에 필수 부분을 추가하여 자체 포함되도록하십시오.
trincot

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.