BULK INSERTの戻り値にPRIMARY KEYは返らない（activerecord-import）

あるテーブルに対して複数のレコードを同時に登録したい場合、**BULK INSERT**を使ってデータを挿入することが多いと思います。
Railsの場合、[activerecord-import](https://github.com/zdennis/activerecord-import)というGEMがよく使われます。

# 問題点
データを挿入したあと、挿入したデータを取得して処理したい場合、データの値によっては絞りきれない場合があります。
その場合、あとの処理がうまく実行できない場合が・・・😿

下記のように、UNIQ制約が張ってそうなテーブルに対しては、このようなSQLで良いでしょう。

```rb:UNIQ制約が貼ってあるデータを挿入→取得する方法
users = profile_ids.map do |profile_id|
  User.new(profile_id: profile_id)
end

# BULK INSERTでデータを挿入
User.import users

# 挿入したデータを取得する
User.where(profile_id: profile_ids)
```


以下のようにUNIQ制約が貼ってないような、曖昧なデータの場合どうしますか？

```rb:UNIQ制約が貼ってないデータを挿入→データを取得する方法
# UserのnameはUNIQ制約がない
users = names.uniq.map do |name|
  User.new(name: name)
end

# BULK INSERTでデータを挿入
User.import users

# 挿入したデータを取得する
User.where(name: names)
```

同じnameのレコードがすでに挿入されていた場合、余計なレコードまで取得してしまいます


# そもそもの問題
activerecord-importとMySQLを組み合わせると、挿入したデータの`ids`が格納されないためです。

```rb:
# idsが空っぽ！
[1] pry > User.import users
=> #<struct ActiveRecord::Import::Result failed_instances=[], num_inserts=5, ids=[]>
```

なんで、`ids`に挿入したデータの情報が入らないのか？と思い、Gemを読んでみると・・・


```rb:lib/activerecord-import/import.rb
class ActiveRecord::Base
  class << self
    # = Returns
    # This returns an object which responds to +failed_instances+ and +num_inserts+.
    # * failed_instances - an array of objects that fails validation and were not committed to the database. An empty array if no validation is performed.
    # * num_inserts - the number of insert statements it took to import the data
    # * ids - the primary keys of the imported ids if the adapter supports it, otherwise an empty array.
    def import(*args)
      if args.first.is_a?( Array ) && args.first.first.is_a?(ActiveRecord::Base)
        options = {}
        options.merge!( args.pop ) if args.last.is_a?(Hash)

        models = args.first
        import_helper(models, options)
      else
        import_helper(*args)
      end
    end
  end
end
```

「もし、アダプターがサポートしていたら、primary keyが入るよ、でも、**サポートしてなかったら空配列を返すね**」

というコメントが😇

ここで、処理を追ってみます。

1. `import`メソッドが呼ばれる
2. `import_helper`メソッドが呼ばれる
3. `import_without_validations_or_callbacks`メソッドが呼ばれる
4.  上記のメソッド内で、各アダプター共通の`insert_many`メソッドが呼ばれる
5.  SQLが実行され、importメソッドの結果が返る

では、MySQLアダプター内の`insert_many`メソッドをみてみます。

```rb:lib/activerecord-import/adapters/mysql_adapter.rb
module ActiveRecord::Import::MysqlAdapter
  def insert_many( sql, values, options = {}, *args ) # :nodoc:
    # the number of inserts default
    number_of_inserts = 0

    base_sql, post_sql = if sql.is_a?( String )
      [sql, '']
    elsif sql.is_a?( Array )
      [sql.shift, sql.join( ' ' )]
    end

    # 〜（略）〜

    [number_of_inserts, []]
  end
end
```

空配列が固定で返ってますね・・・😇 というわけで、MySQLは対応していません。
現在、`ids`に**対応してるDBはpostgresqlだけ**です。

# 解決策
UNIQ制約が貼ってないような、曖昧なデータを取得する方法です。
挿入前後のIDを取得し、最後のデータ取得ではidでフィルターし、データを取得するだけです・・・。

```rb:UNIQ制約が貼ってあるデータを挿入→取得する方法
# UserのnameはUNIQ制約がない
users = names.uniq.map do |name|
  User.new(name: name)
end

# BULK INSERTが挿入する前のIDを取得し、+1する
before_id = User.last.try(:id).to_i + 1

# BULK INSERTでデータを挿入
User.import users

# BULK INSERTが挿入した後のIDを取得する
after_id = User.last.id

# 挿入前後のidで絞り、挿入したデータを取得する
User.where(id: before_id..after_id, name: names)
```

# まとめ
DBにMySQLを使い、[activerecord-import](https://github.com/zdennis/activerecord-import)を使ったBULK INSERTは、戻り値にPRIMARY KEYは返らないです（タイトルに書いてあるまま・・・）

BULK INSERTしたデータをのちに使いたい場合は、データ取得方法を考えなければなりません！
気をつけましょう。