LoginSignup
2
1

More than 3 years have passed since last update.

ActiveRecord で auto increment な ID を特定の数値の時にスキップする

Last updated at Posted at 2019-02-16

成果はこちら oieioi/ketsuban

使い方

class User < ApplicationRecord
  include Ketsuban
  ketsuban [4, 5]
end

5.times.map { User.create.id }
# => [1,2,3,6,7]

lambda も渡せる

class Book < ApplicationRecord
  include Ketsuban
  ketsuban -> next_id { next_id.odd? }
end

5.times.map { Book.create.id }
# => [2,4,6,8,10]

実装

  1. before_create フックで
  2. 次に振られそうなIDを調べ
  3. それが設定値にマッチする場合はそのIDをスキップし
  4. 採番テーブルをインクリメントする

これだけだけど、 自動採番なカラムは PostgreSQL でも MySQL でも気軽にさわれない感じになっていて不安だった。

PostgreSQL

ドキュメント

次のシーケンスを取得する

以下の方法を検討したが、一番実装が簡単だった last_value を使う方法で実装した。ID重複を起こす可能性があるけど、その場合はあきらめる。

max(id)
select max(id) from users;
  • max(id) はレコードが削除されていたとき正しくなくなる。
  • ちゃんとシーケンスを見たほうがいい。
currval
select currval('users_id_seq');
  • 現在のセッション中に該当のシーケンスに対して操作を行っていないときエラーになる。
last_value
select last_value, is_called from users_id_seq;
  • シーケンスを直接調べる方法。
  • last_value は一度も使用されていなくても1を返すが、使われていない値の場合は is_called が偽になるので判別可能。

シーケンスを進める

手動でシーケンスを使ったら、進める必要がある。 ActiveRecord がメソッドを用意しているのでそれを使った。
ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements#set_pk_sequence!

MySQL

ドキュメント

次の auto_incremant な値を取得する

select auto_increment from information_schema.tables where table_name = users;
  • auto_increment なカラムの次に振られる値を取得できる。
  • 直前に auto_increment の値を指定して insert していた場合、この値は更新されていない。
    • 1を欠番にし、2を指定して insert した直後のとき、このSQLの返り値は1になる。
select max(id) from users;
  • 上記のため、 max(id) を併用して現在のDBの最高値と、 auto_increment な値を調べるようにした。
    • この状態の時に末尾レコードを削除すると、詰め番が発生してしまう。直したい。
      • 1を欠番にし、2を指定して User.create し、 2を destroy すると、次に create された User の id は 2 になる。

auto_incremant な値を更新する

alter table users auto_increment = 42;
  • before_created が属するトランザクションの中で alter table は無理では?
  • auto_increment のカラムの値を指定せずにいつも通りの User.create を行うと自動で一番でかい採番をしてくれるので結果的に不要だった。

SQLite

次の auto_incremant な値を取得する

select seq from sqlite_sequence where name = 'users';

で現在の最高値を取得できる。対象のテーブルにまだ insert が行われていない時はレコード自体が存在しない。また、対象のテーブルのシーケンスに特定の値を指定して insert した場合、こちらの値は自動でインクリメントされる。

auto_incremant な値を更新する

上記の通りなので、更新する必要がない。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1