[ruby-on-rails] 레일에서 단일 호출로 여러 개체 저장

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

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)

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



답변

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 연관을 모두 읽는 것이 좋습니다 . 후자는 연관을 선언 할 때 클래스가 얻는 모든 메소드에 대한 가이드를 포함합니다.


답변

여러 삽입을 수행해야하므로 데이터베이스가 여러 번 히트합니다. 귀하의 경우 지연은 각 저장이 다른 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부모 개체에 대한 호출은 자식 개체를 저장합니다.


답변

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' }])

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


답변

다른 곳에서 찾은 두 가지 답변 중 하나 : 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]]

건배


답변

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

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


답변

DB를 빠르게 치기 위해 보석이 필요하지 않습니다.

Jackrg는 우리를 위해 그것을 해결했습니다 :
https://gist.github.com/jackrg/76ade1724bd816292e4e


답변