【问题标题】:How do I get has_and_belongs_to_many to do a bulk insert?如何让 has_and_belongs_to_many 进行批量插入?
【发布时间】:2020-03-24 23:47:42
【问题描述】:

我有两个具有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

我怎样才能改为进行单个批量插入?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-6


    【解决方案1】:

    Rails 6 现在支持批量插入功能,可用于在单个查询中在数据库中插入多条记录。有一个insert_all 方法,它接受代表要插入数据库的每一行的哈希数组。默认情况下它会跳过重复的行

    在单个 SQL INSERT 语句中将多条记录插入数据库。它不会实例化任何模型,也不会触发 Active Record 回调或验证。虽然传递的值要经过 Active Record 的类型转换和序列化。

    attributes 参数是一个哈希数组。每个 Hash 确定单行的属性,并且必须具有相同的键。

    表上的每个唯一索引都认为行是唯一的。跳过任何重复的行。用 :unique_by 覆盖(见下文)。

    假设您有一个代表您的连接模型的 CustomerSegment 类。

    你可以这样做:

    CustomerSegment.insert_all([{ customer_id: 54, segment_id: 2 },{ customer_id: 143, segment_id: 222 }...])
    

    【讨论】:

    • 感谢您的回答。我可以用它来优化热点。你会得到赏金,但桑尼比你早几分钟就回答了。
    【解决方案2】:

    您可以通过为您的联接表创建一个类来使关联可见:

    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 gem)直接批量插入连接表:

    CustomerSegment.insert_all([
      { customer_id: 1, segment_id: 1 },
      { customer_id: 1, segment_id: 2 },
      …
    ])
    

    【讨论】:

    • 感谢您的回答。这是最好的,我可以优化一个热点。你得到了赏金! ...在 24 小时内,它会让我。
    【解决方案3】:

    如果您关注性能,我建议您使用原始 SQL 而不是 ORM,因为它可能相对具有性能缺陷。

    为了简化代码,删除了 ['created_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
    

    【讨论】:

    • 感谢您的回答。是的,我可以手动完成,但我更愿意让 ORM 来完成。 ORM 应该能够尽可能高效地完成它。请注意,Rails 6 引入了insert_all,它可以有效地进行批量插入。对于以前的版本,有activerecord-import。
    • @Schwern,是的,我以前读过 insert_all 文档,但它对关系用例的解释不多。它说“它不会实例化任何模型,也不会触发 Active Record 回调或验证。”。
    • 如果为连接表制作模型,那么应该可以CustomerSegment.insert_all([{ customer: customer, segment: segment }, ...]) 避免编写原始SQL。我可能会这样做来优化这个特定的方法。
    猜你喜欢
    • 2011-07-28
    • 2018-06-10
    • 2010-12-09
    • 1970-01-01
    • 2018-11-19
    • 2017-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多