使用Rails 4.2中的UPSERT PostgreSQL

时间:2016-04-04 13:20:49

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

我如何使用" UPSERT"或者" INSERT INTO喜欢(user_id,person_id)VALUES(32,64)ON CONFLICT(user_id,person_id)DOHING"在PostgreSQL 9.5 on Rails 4.2?

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

您可以通过使用活动记录内部函数创建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