ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: | Rails+PostgreSQLシーケンスがずれた場合の対処法

2017年12月22日

先日
こんなエラーに悩まされました。

ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR:
  duplicate key value violates unique constraint "students_pkey"

 

 

エラーの原因はAuto IncrementするIDが重複しています。
という事なのですが、
通常ですとRailsがレコードを追加するたびにこの
シーケンスを引き上げてくれて
これが起こることはありません。

 

が、
RailsやSQLでIDを指定してレコードを作成したりすると
このシーケンスが上がらず、
次にRailsを実行した時に
ユニーク制約に引っかかるということがあります。

 

 

 

ずれているかどうかを確認するコマンドは
以下

select max(id) from [テーブル名];
select last_value from [テーブル名]_id_seq;

一つ目のSQLで最大のIDを取得。
二つ目のSQLで現在のシーケンスIDを取得。

二つ目の結果が一つ目の結果より小さい場合は、
重複エラーが起こる可能性があります。

そして、
万が一エラーになってしまい、
このずれを修正したい場合は

 

 

SELECT setval('[ テーブル名 ]_id_seq', coalesce((SELECT MAX(id)+1 FROM [ テーブル名 ]), 1), false)

これで修正できます。

 

 

実行したら再度

select max(id) from [テーブル名];
select last_value from [テーブル名]_id_seq;

で確認。

 

 

<実演>
まずはscaffoldでテーブル作成
テーブルは以下の形で作成最初は一レコードのみ

# select * from Students;
 id | name  | age | sex |
----+-------+-----+-----|
  1 | John  |  21 |   1 |
  2 | Mary  |  23 |   2 |

 

 

シーケンスを確認

select max(id) from students;
select last_value from students_id_seq;

test=# select max(id) from students;
 max 
-----
   2
(2 row)

test=# 
test=# 
test=# select last_value from students_id_seq;
 last_value 
------------
          2
(2 row)

 

 

シーケンスは揃っているので、
ここでIDを指定したINSERT文を発行。

test=# INSERT INTO Students ( id , name , age ,sex  , created_at , updated_at) VALUES( 3,  'Smith' , 27 , 1 , NOW() , NOW());

select max(id) from students;
select last_value from students_id_seq;

test=# select max(id) from students;
 max 
-----
   3
(3 row)

test=# 
test=# 
test=# select last_value from students_id_seq;
 last_value 
------------
          2
(2 row)

 

 

シーケンスがずれる。
Student.create( :id => 4, :name => ‘Ken’ , :age => 18 , :sex => 1 )

このようにしてもIDがずれる。

この状態でRailsからレコードを追加すると

> Student.create( :name => 'Alex' , :age => 17 , :sex => 21 )
   (0.4ms)  BEGIN
  SQL (1.3ms)  INSERT INTO "students" ("name", "age", "sex", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "Alex"], ["age", 17], ["sex", 21], ["created_at", "2017-05-13 01:56:26.398784"], ["updated_at", "2017-05-13 01:56:26.398784"]]
   (0.8ms)  ROLLBACK
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "students_pkey"
DETAIL:  Key (id)=(3) already exists.

 

 

エラーになる。

先ほどのコマンドで
揃えてあげる。

# SELECT setval('students_id_seq', coalesce((SELECT MAX(id)+1 FROM students), 1), false);
 setval 
--------
      6
(1 row)

test=# 
test=# 
test=# select max(id) from students;
 max 
-----
   5
(1 row)

test=# select last_value from students_id_seq;
 last_value 
------------
          6
(1 row)

test=# 
>   Student.create( :name => 'Alex' , :age => 17 , :sex => 21 )
   (0.4ms)  BEGIN
  SQL (1.0ms)  INSERT INTO "students" ("name", "age", "sex", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "Alex"], ["age", 17], ["sex", 21], ["created_at", "2017-05-13 02:01:03.472230"], ["updated_at", "2017-05-13 02:01:03.472230"]]
   (2.0ms)  COMMIT
 => # 
2.4.0 :004 > 

エラー解消。