Как мне заставить has_and_belongs_to_many выполнить массовую вставку? - PullRequest
2 голосов
/ 25 марта 2020

У меня есть два класса с отношением has_and_belongs_to_many.

class Customer < ApplicationRecord
  has_and_belongs_to_many :segments

  def rematch_segments
    self.segments = Segment.customer_segments(self)
  end
end

class Segment < ApplicationRecord
  has_and_belongs_to_many :customers

  class << self
    def customer_segments(customer)
      ...returns a collection of Segments...
    end
  end
end

Вызов rematch_segments приводит к вставке для каждого сегмента.

(0.2ms)  BEGIN
Customer::HABTM_Segments Create (0.8ms)  INSERT INTO "customers_segments" ("customer_id", "segment_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4)  [["customer_id", 1], ["segment_id", 1], ["created_at", "2020-03-24 23:42:52.985400"], ["updated_at", "2020-03-24 23:42:52.985400"]]
Customer::HABTM_Segments Create (0.2ms)  INSERT INTO "customers_segments" ("customer_id", "segment_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4)  [["customer_id", 1], ["segment_id", 2], ["created_at", "2020-03-24 23:42:52.987537"], ["updated_at", "2020-03-24 23:42:52.987537"]]
Customer::HABTM_Segments Create (0.2ms)  INSERT INTO "customers_segments" ("customer_id", "segment_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4)  [["customer_id", 1], ["segment_id", 3], ["created_at", "2020-03-24 23:42:52.988610"], ["updated_at", "2020-03-24 23:42:52.988610"]]
(0.3ms)  COMMIT

Как я могу вместо этого сделать монолитная вставка?

Ответы [ 3 ]

1 голос
/ 26 апреля 2020

Вы должны сделать ассоциацию видимой, создав класс класса для своей таблицы соединения:

class Customer < ApplicationRecord
  has_many :segments, through: :customer_segments
  has_many :customer_segments
end

class Segment < ApplicationRecord
  has_many :customers, through: :customer_segments
  has_many :customer_segments
end

class CustomerSegment < ApplicationRecord
  belongs_to :customer
  belongs_to :segment
end

Таким образом, вы можете использовать Rails 6 insert_all() (или гем activerecord-import) для массовой вставки присоединиться к столу напрямую:

CustomerSegment.insert_all([
  { customer_id: 1, segment_id: 1 },
  { customer_id: 1, segment_id: 2 },
  …
])
1 голос
/ 26 апреля 2020

Rails 6 теперь поддерживает функцию массовой вставки, которую можно использовать для вставки нескольких записей в базу данных в одном запросе. Существует метод insert_all, который принимает массив ha sh, представляющий каждую строку для вставки в базу данных. По умолчанию он пропускает повторяющиеся строки

Вставляет несколько записей в базу данных в одном операторе SQL INSERT. Он не создает экземпляры каких-либо моделей и не вызывает обратных вызовов или проверок Active Record. Хотя значения go передаются посредством приведения типов и сериализации в Active Record.

Параметр attribute является массивом хэшей. Каждый Ха sh определяет атрибуты для одной строки и должен иметь одинаковые ключи.

Строки считаются уникальными для каждого уникального индекса в таблице. Любые повторяющиеся строки пропускаются. Переопределите с помощью: unique_by (см. Ниже).

Предполагая, что у вас есть CustomerSegment класс, представляющий вашу модель соединения.

Вы можете сделать что-то вроде этого:

CustomerSegment.insert_all([{ customer_id: 54, segment_id: 2 },{ customer_id: 143, segment_id: 222 }...])
0 голосов
/ 21 апреля 2020

Если вы сосредоточены на производительности, я бы порекомендовал использовать raw SQL вместо ORM, что может иметь относительные недостатки производительности.

Чтобы упростить код, ['creation_at', 'updated_at'] были удалены.

1. Использование необработанного запроса

Вы можете рассмотреть возможность создания INSERT QUERY следующим образом.

INSERT INTO customers_segments (customer_id,segment_id) VALUES (1,1),(1,2),(1,3) ...

В зависимости от размера записей вам может потребоваться установить размер пакета. Как правило, должно быть хорошо управлять тысячами записей без размера пакета.

segments = Segment.find_by_attr(:attr_val) #please do not forget indexing of the attr in DB.
customer = Customer.create
stmt = segments.map {|seg| "(#{customer.id},#{seg.id})"}.join(",")

ActiveRecord::Base.connection.execute("INSERT INTO customers_segments (customer_id, segment_id) VALUES #{stmt}")

2. реализовать необработанный запрос с помощью ORM

class Customer < ApplicationRecord
  ...
  def batch_rematch(attr_array)
    attr_val = attr_array.map{|a| %Q{'#{a}'} }.uniq.join(",")
    self.connection.execute(%Q{insert into customers_segments (customer_id,segment_id) 
                     select distinct customers.id,segments.id 
                     from customers,segments
                     where customers.id = #{self.id} 
                     and segments.<attr> in (#{attr_val})})
  end
end
...