Збереження кількох об’єктів за один виклик у рейках


93

У мене є метод в 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)

Це зробило б усе лише за два звернення до бази даних. Чи є простий спосіб зробити це в рейках, чи я застряг робити це по одному?

Відповіді:


69

Ви можете спробувати використовувати 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 та Посібники Rails на інтерфейсі запитів ActiveRecord та асоціації ActiveRecord . Останній містить посібник з усіх методів, які отримує клас при оголошенні асоціації.


78
На жаль, ActiveRecord генерує по одному запиту INSERT для кожної створеної моделі. OP хоче один виклик INSERT, чого ActiveRecord не робить.
Франсуа Босолей

Так, я сподівався отримати все це за один вставний дзвінок, але якщо activerecord не такий розумний, то, мабуть, це не дуже просто.
captncraig

@ FrançoisBeausoleil, не могли б ви поглянути на питання stackoverflow.com/questions/15386450/… , чи не тому я не можу вставити кілька записів одночасно?
Річлевіс 13.03.13

3
Це правда, ви не можете отримати AR для створення одного ВСТАВЛЕННЯ або ОНОВЛЕННЯ, але за допомогою ActiveRecord::Base.transaction { records.each(&:save) }або подібного ви можете принаймні помістити всі ВСТАВЛЕННЯ або ОНОВЛЕННЯ в одну транзакцію.
yuval

1
Насправді операційна програма хоче менше вражати базу даних, щоб пришвидшити доступ до БД, а ActiveRecord насправді дозволить вам це зробити, групуючи всі дзвінки в одній транзакції. (Див. Відповідь Харіша, яка повинна бути прийнятою відповіддю.) Що ActiveRecord не дозволить вам зробити, це змусити БД створити один запит INSERT для кожної транзакції, але це не так важливо, оскільки затримка відбувається від роботи в мережі доступ до БД, а не всередині самої БД, коли вона робить запити INSERT.
Magne

99

Оскільки вам потрібно виконати кілька вставок, база даних буде вражена кілька разів. Затримка у вашому випадку полягає в тому, що кожне збереження здійснюється в різних транзакціях БД. Ви можете зменшити затримку, включивши всі свої операції в одну транзакцію.

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
Саме те, що я шукав. Багато пришвидшує моє насіння. Дякую :-)
Ренра

16

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

якщо все, що ви хочете зробити, це вставити нові записи.


1
Я не можу дочекатися, поки ми оновимо наш додаток. Стільки цікавих речей у Rails 6.
Ден,

1
одне, на що слід звернути увагу: insert_all пропускає зворотні виклики та перевірки AR: edgeguides.rubyonrails.org/…
sujay

11

Одну з двох відповідей, знайдених десь ще: Берлінгтон . Ці два найкращі варіанти для виступу


Я думаю, що найкращим вибором ефективності буде використання SQL та масового вставлення декількох рядків на запит. Якщо ви можете створити оператор INSERT, який робить щось на зразок:

ВСТАВИТИ В foos_bars (foo_id, bar_id) ЦІННОСТІ (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", переконайтеся, що це поле проіндексоване у вашій базі даних.


АБО

Ви все ще можете поглянути на 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

Це не погана ідея. Якщо ви робите ОДНУ вставку, вона буде успішною або невдалою, я не думаю, що потрібна транзакція. Або ви завжди можете обернути цю ОДНУ вставку в блок транзакцій.
Фернандо Фабреті

це погана практика рейок
Блер Андерсон

0

вам потрібно використовувати цей самоцвіт "FastInserter" -> https://github.com/joinhandshake/fast_inserter

і вставити велику кількість і тисячі записів швидко, тому що цей самоцвіт пропускає активний запис і використовує лише один необроблений запит sql


1
Незважаючи на те, що посилання на дорогоцінний камінь може бути корисним, будь-ласка, надайте код, який Запитувач міг би використовувати замість поточного коду (див. Питання).
trincot


1
Відповіді повинні мати необхідну інформацію , впроваджену . Будь ласка, відредагуйте свою відповідь і додайте туди посилання, а також додайте основні її частини всередину відповіді, щоб вона була самодостатньою.
trincot

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.