【问题标题】:Using UPSERT PostgreSQL from Rails 4.2从 Rails 4.2 使用 UPSERT PostgreSQL
【发布时间】:2016-07-24 01:34:49
【问题描述】:

如何在 PostgreSQL 9.5 on Rails 4.2 中使用“UPSERT”或“INSERT INTO likes (user_id,person_id) VALUES (32,64) ON CONFLICT (user_id,person_id) DO NOTHING”?

【问题讨论】:

  • 如果您有机会更新,我为 Rails 5 制作了一个 gem:github.com/jesjos/active_record_upsert
  • 另外:我想要一个支持 Rails 4.2 的 PR
  • @Rocco 因为它不是原子的,我更喜欢数据库来尽可能多地处理我的数据。
  • @user3384741 我们在我工作的地方将它与 jruby 一起使用。需要注意的是,您必须使用 activerecord-jdbc 的修补版本:github.com/jensnockert/activerecord-jdbc-adapter/tree/…
  • find_or_create_by 也不处理竞争条件。投入线程(独角兽)或多系统,而 find_or_create 并没有削减它。也许应该将其更改为使用 upsert。

标签: ruby-on-rails ruby postgresql ruby-on-rails-4


【解决方案1】:

您可以通过使用活动记录内部创建 SQL 来实现。您可以使用 arel_attributes_with_values_for_create 提取具有 Rails 属性的插入 sql 的每个 active_record 的值,例如:

INSERT_REGEX = /(INSERT INTO .* VALUES) (.*)/

def values_for(active_record)
  active_record.updated_at = Time.now if active_record.respond_to?(:updated_at)
  active_record.created_at ||= Time.now if active_record.respond_to?(:created_at)

  sql = active_record.class.arel_table.create_insert.tap do |insert_manager|
    insert_manager.insert(
      active_record.send(:arel_attributes_with_values_for_create, active_record.attribute_names.sort)
    )
  end.to_sql

  INSERT_REGEX.match(sql).captures[1]
end

然后您可以将所有值添加到一个大“值”字符串中:

def values(active_records)
  active_records.map { |active_record| values_for(active_record) }.join(",")
end

对于 SQL 的插入部分,您可以使用与用于值提取的代码类似的代码:

INSERT_REGEX = /(INSERT INTO .* VALUES) (.*)/

def insert_statement(active_record)
  sql = active_record.class.arel_table.create_insert.tap do |insert_manager|
    insert_manager.insert(
      active_record.send(:arel_attributes_with_values_for_create, active_record.attribute_names.sort)
    )
  end.to_sql
  INSERT_REGEX.match(sql).captures[0]
end

对于冲突部分,您可以执行以下操作:

def on_conflict_statement(conflict_fields)
  return ';' if conflict_fields.blank?

  "ON CONFLICT (#{conflict_fields.join(', ')}) DO NOTHING"
end

最后只需将它们连接在一起执行:

def perform(active_records:, conflict_fields:)
  return if active_records.empty?

  ActiveRecord::Base.connection.execute(
    <<-SQL.squish
        #{insert_statement(active_records.first)}
        #{values(active_records)}
        #{on_conflict_statement(conflict_fields)}
    SQL
  )
end

您可以查看here最终解决方案。

这是我的输出 - 使用我的 balance 活动记录模型并删除执行 SQL(ActiveRecord::Base.connection.execute...) 的代码:

> puts BatchCreate.perform(active_records: [balance], conflict_fields: [:bank_account_id, :date])
INSERT INTO "balances" ("amount", "created_at", "currency", "date", "id", "bank_account_id", "updated_at") VALUES (0, '2018-11-28 15:42:44.312954', 'MXN', '2018-11-28', 15169, 26300, '2018-11-28 16:05:07.465402') ON CONFLICT (bank_account_id, date) DO NOTHING 

【讨论】:

    【解决方案2】:

    在这里查看active_record_upsert gem:https://github.com/jesjos/active_record_upsert。这确实是 upsert,但显然只在 Postgres 9.5+ 上。

    【讨论】:

    • 这没有回答问题 - 他正在寻找ON CONFLICT DO NOTHING。那个宝石只做ON CONFLICT DO UPDATE。大不同!
    猜你喜欢
    • 2014-05-18
    • 2017-11-22
    • 2015-11-17
    • 1970-01-01
    • 2015-04-30
    • 2015-07-02
    • 2022-08-04
    • 2011-11-12
    • 2017-06-30
    相关资源
    最近更新 更多