【问题标题】:How do I retrieve a list of created IDs for bulk insert in Active Record?如何检索创建的 ID 列表以在 Active Record 中批量插入?
【发布时间】:2012-12-05 07:07:19
【问题描述】:

我有三个模型:

class Coupon < ActiveRecord::Base
  belongs_to :event
  has_many :coupon_events, :dependent => :destroy
  has_many :events, :through => :coupon_events
end 

class Event < ActiveRecord::Base
  belongs_to :event
  has_many :coupon_events, :dependent => :destroy
  has_many :coupons, :through => :coupon_events
end

class CouponEvent < ActiveRecord::Base
  belongs_to :coupon
  belongs_to :event
end

我通读了一个 CSV 文件以创建优惠券和优惠券_事件。这是非常低效的,因为一次创建一个记录并导致多个查询,每个查询都包括两个插入语句。

我想使用这样的单个插入查询:

coupon_string = " ('abc','AAA'), ('123','BBB')"
Coupon.connection.insert("INSERT INTO coupons (code, name) VALUES"+coupon_string)

然后我需要为 CouponEvent 模型创建第二个插入查询,但我需要返回的 coupon_id 的列表。是否有在插入时检索 ID 的内置方法?

【问题讨论】:

  • 如果您想要的只是从第一次查询中获得的 ID,那么只需将返回的 ID 存储在哈希中以在插入时使用。

标签: mysql ruby-on-rails activerecord insert has-many


【解决方案1】:

目前,最好的(但不是理想的)解决方案是使用"activerecord-import" 批量导入。不幸的是,该 gem 不会返回插入的 id,因此您必须转身查询才能获取 id。也就是说,您将批量插入事件模型,查询数据库以将它们全部恢复到内存中。现在您有了事件 ID,因此您可以创建优惠券并批量插入它们。为 CouponEvents 重复冲洗泡沫。

与每个 Event、Coupon 和 CouponEvent 一次往返相比 - 对于一个包含数千行的文件可能有数千次往返 - 每个模型只执行 2 次往返 - 一次用于插入事件,一次用于取回事件使用 ids、同上 Coupon 和 CouponEvent - 总共 6 次往返。

【讨论】:

  • 我创建了一个 activerecord-import 版本,它使用@MBO 的答案中的技术返回插入的 id,至少对于 postgres 而言。在这里:github.com/GoodMeasuresLLC/activerecord-import,有正式版的推送请求。
  • 活动记录导入是否使用 1 次往返发送查询?
  • 另外,您确定您的 gem 在多线程环境中返回正确的 IDS 吗?运行 activerecord-import 的 IE 10 不同线程到同一个表中?
  • 是的,只有一次往返 - 这就是重点。对于多线程的事情,它依赖于数据库来创建 id,所以它们不必是顺序的。 db 中的自动增量功能不会因多线程而中断。
  • 你可以使用activerecord-import的标准fork,我的PR上周被接受了。那是github.com/zdennis/activerecord-import
【解决方案2】:

实际上我不确定这个 colud 是否有效(如果它创建一个插入查询),但您可以尝试使用带有参数数组的 #create 方法:

new_coupons = Coupon.create([
  { :code => "abc", :name => "AAA" },
  { :code => "123", :name => "BBB" }
])

CouponEvent.create([
  { :enevt_id => ..., coupon_id: ...},
  ...
])

要为 CouponEvent 创建参数列表,您需要将返回的 new_coupons 集合映射到 id,并根据优惠券代码/名称添加 event_id(取决于它存储在 CVS 文件中的方式)。

更新:

我自己检查了,如果第一个解决方案不起作用(我的代码中没有简单的模型没有唯一性约束,所以我没有检查),并且你使用 PostgreSQL,你总是可以做类似的事情这个:

res = Coupon.connection.execute(<<-EOSQL)
  INSERT INTO coupons (code, name)
  VALUES #{values}
  RETURNING id, code
EOSQL

您需要最后一个“Returning”子句,因此您可以获取插入的 id 以及插入行的代码。您需要映射结果集:

res.map {|row|
  { :coupon_id => row["id"],
    :event_id => events.find { |e| e.coupon_code == row["code"] }
  }
}

SQL中没有标准的方法来返回插入行的列,“RETURNING”子句只在PostgreSQL中有效,所以如果你使用不同的数据库,你需要检查文档或一一插入行。

您也不能使用connection.insert,因为在 ActiveRecord 中它只返回一个插入行的 id,而不是所有行。

【讨论】:

  • 唉,我用的是 MySQL,但我发现我可能能够得到 last_insert_id。见dev.mysql.com/doc/refman/5.0/en/getting-unique-id.html
  • 使用 Coupon.create 的版本不是很有用 - 它为每个优惠券创建并执行一个 sql 查询。
  • 但是 Coupon.connection.execute() 答案的第二个版本很棒,正是我所需要的。
【解决方案3】:

这样做的方法是插入具有唯一import_id 值的记录。步骤是:

  1. 将import_id 列添加到表中。可能是 INT 或 VARCHAR,具体取决于您生成随机 ID 的方式。

  2. 在第一个INSERT之前,生成一个随机ID。

  3. 执行第一个多值INSERT,对每一行使用相同的import_id。

  4. SELECT id FROM first_table WHERE import_id=&lt;the random import ID&gt;

  5. 使用返回的 ID 生成第二个多值 INSERT。

【讨论】:

    【解决方案4】:

    connection.insert 只返回一个 id,公用表表达式适用于我

        insert_sql = <<-SQL
          WITH inserted_ids AS (
            INSERT INTO clients (email, name) VALUES #{array.join(', ')}
            RETURNING id
          )
          SELECT * FROM inserted_ids
        SQL
        result = ActiveRecord::Base.connection.execute(insert_sql)
    

    【讨论】:

    • MySQL 不支持返回?
    【解决方案5】:

    如果您使用 mysql 并且没有在另一个脚本/进程中插入更多行,则可以使用 last_insert_id() 获取插入的第一行的 id

        first_id = ActiveRecord::Base.connection.execute("select last_insert_id()").first[0]
    

    然后依次生成其他记录的id。

    即

        data = %w(one two three)
        to_insert = "('" + data.join("'), ('") + "')"
        Model.connection.insert("INSERT INTO models(name) VALUES #{to_insert}")
        first_id = ActiveRecord::Base.connection.execute("select last_insert_id()").first[0].to_i
        hash = {}
        data.each_with_index {|d, i| hash[first_id + i] = d}
    

    【讨论】:

    • 你的first_id值实际上是从MySQL AR适配器的#insert语句返回的。最后我是这么想的,但我没有使用 MySQL,所以无法确认。
    • 这是一个糟糕的通用解决方案,因为它依赖于数据库生成顺序 ID。虽然数据库经常这样做,但如果有其他用户插入到同一个表中,它们就不会这样做。锁定表只会降低性能,我假设您首先使用批量导入来提高性能。同样,还有其他原因导致 db 可能不会生成顺序 id,例如 id 分区(按范围,其中分配给每个 db 的 id 范围,不一定相邻甚至增加,也可以通过偶数/奇数)
    • 实际上,在我在回答的第一行评论的条件下,这是一个很好的解决方案:“如果您使用的是 mysql,并且您没有在另一个脚本/进程中插入更多行,您可以获得使用 last_insert_id() 插入的第一行的 id”,最重要的是,它回答了以下问题:“如何检索创建的 ID 列表以在 Active Record 中批量插入?”。你假设太多了。随意用真实的代码和真实的解决方案写一个答案。
    • @rorra 实际上,这通常是一个糟糕的解决方案。我认为如果他做了这样的事情,OP 自己可能会遇到难以调试的问题。我会问 OP:你需要 IDS 做什么?如果要手动激活模型的回调,请考虑使用一个单独的线程来查询表以查看新插入的 ID,并以这种方式对这些 IDS 执行操作。
    • 不,在我所说的条件下它是一个很好的解决方案,它快速而实用,随意证明我在我为描述的问题编写的条件下编写的解决方案,将成为一个很难的错误调试。随意在新答案中编写更好的解决方案,使用没人问过的回调或没人问过的线程。明确一点:“如果您使用 mysql 并且没有在另一个脚本/进程中插入更多行,则可以使用 last_insert_id() 获取插入的第一行的 id”
    猜你喜欢
    • 2013-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多