在 Rails 中的一次调用中保存多个对象

发布于 2024-08-26 19:00:11 字数 584 浏览 10 评论 0原文

我在 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)

只需两次数据库点击就可以完成这一切。有没有一种简单的方法可以在 Rails 中做到这一点,或者我只能一次做一个?

I have a method in rails that is doing something like this:

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

The problem is this takes longer and longer the more entities I add. I suspect this is because it has to hit the database for every record. Since they are nested, I know I can't save the children before the parents are saved, but I would like to save all of the parents at once, and then all of the children. It would be nice to do something like:

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)

That would do it all in only two database hits. Is there an easy way to do this in rails, or am I stuck doing it one at a time?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

清醇 2024-09-02 19:00:11

由于需要执行多次插入,数据库将被多次命中。您的情况的延迟是因为每次保存都是在不同的数据库事务中完成的。您可以通过将所有操作包含在一个事务中来减少延迟。

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

您的 save 方法可能如下所示:

# 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 调用将保存子对象。

Since you need to perform multiple inserts, database will be hit multiple times. The delay in your case is because each save is done in different DB transactions. You can reduce the latency by enclosing all your operations in one transaction.

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

Your save method might look like this:

# 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

The save call on the parent object saves the child objects.

旧话新听 2024-09-02 19:00:11

您可以尝试使用 Foo.create 而不是 Foo.new。创建“如果验证通过,则创建一个对象(或多个对象)并将其保存到数据库。无论该对象是否成功保存到数据库,都会返回结果对象。”

您可以像这样创建多个对象:

# 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 文档 以及 ActiveRecord 查询接口 上的 Rails 指南 a> 和 ActiveRecord 关联。后者包含声明关联时类获得的所有方法的指南。

You might try using Foo.create instead of Foo.new. Create "Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not."

You can create multiple objects like this:

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

Then, for each parent, you can also use create to add to its association:

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

I recommend reading both the ActiveRecord documentation and the Rails Guides on ActiveRecord query interface and ActiveRecord associations. The latter contains a guide of all the methods a class gains when you declare an association.

浅沫记忆 2024-09-02 19:00:11

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

它比您只想插入新记录的

insert_all (Rails 6+)

Rails 6 introduced a new method insert_all, which inserts multiple records into the database in a single SQL INSERT statement.

Also, this method does not instantiate any models and does not call Active Record callbacks or validations.

So,

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

it is significantly more efficient than

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

if all you want to do is to insert new records.

愁以何悠 2024-09-02 19:00:11

在其他地方找到的两个答案之一:由 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]]

干杯

One of the two answers found somewhere else: by Beerlington.
Those two are your best bet for performance


I think your best bet performance-wise is going to be to use SQL, and bulk insert multiple rows per query. If you can build an INSERT statement that does something like:

INSERT INTO foos_bars (foo_id,bar_id) VALUES (1,1),(1,2),(1,3)....
You should be able to insert thousands of rows in a single query. I didn't try your mass_habtm method, but it seems like you could to something like:


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

Also, if you are searching Bar by "some_attribute", make sure you have that field indexed in your database.


OR

You still might have a look at activerecord-import. It's right that it doesn't work without a model, but you could create a Model just for the import.


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

Cheers

萌能量女王 2024-09-02 19:00:11

你需要使用这个 gem“FastInserter”-> https://github.com/joinhandshake/fast_inserter

并且插入大量和数千条记录很快因为这个 gem 跳过活动记录,并且只使用单个 sql 原始查询

you need to use this gem "FastInserter" -> https://github.com/joinhandshake/fast_inserter

and inserting a large number and thousands of records is fast because this gem skips active record, and only uses a single sql raw query

薄荷港 2024-09-02 19:00:11

您不需要宝石就能快速击中 DB,而且只需一次!

Jackrg 已经为我们解决了这个问题:
https://gist.github.com/jackrg/76ade1724bd816292e4e

You don't need a gem to hit DB fast and only once!

Jackrg has worked it out for us:
https://gist.github.com/jackrg/76ade1724bd816292e4e

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文