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


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.