Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PostgreSQL's SERIAL vs MySQL's AUTO_INCREMENT?

I have this snippet of MySQL:

CREATE TABLE seq_test (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name TEXT
);

INSERT INTO seq_test(id, name) VALUES (1, 'one');
INSERT INTO seq_test(name) VALUES ('two');

When I try to write this in PostgreSQL:

CREATE TABLE seq_test (
  id SERIAL PRIMARY KEY,
  name TEXT
);

INSERT INTO seq_test(id, name) VALUES (1, 'one');
INSERT INTO seq_test(name) VALUES ('two');

I get the following error:

[23505] ERROR: duplicate key value violates unique constraint "seq_test_pkey"
  Detail: Key (id)=(1) already exists.

This is because in PostgreSQL, inserting one does not increment the id for the next insert. How can I create my table so that it conforms to the MySQL behaviour?

This is ofcourse an artificial example but I am porting a large code base from MySQL to PostgreSQL and parts of the code (that I don't have control over) uses both styles (i.e with and without ids) and they work in MySQL but does not work in PostgreSQL.

An ugly hack would be to always do SELECT setval('my_table_id_seq', (SELECT count(*) FROM my_table), TRUE) ...

like image 232
pathikrit Avatar asked Apr 06 '16 00:04

pathikrit


People also ask

Does serial auto increment Postgres?

As indicated in the official documentation, SERIAL is not a true data type, but is simply shorthand notation that tells Postgres to create a auto incremented, unique identifier for the specified column. Below we'll create our simple books table with an appropriate SERIAL data type for the primary key.

Is Serial the same as auto increment?

In MySQL, both SERIAL and AUTO_INCREMENT are used to define a sequence as a default value for a field. But they are technically different from each other. The AUTO_INCREMENT attribute is supported by all numeric data types except for BIT and DECIMAL.

Does MySQL have auto increment?

MySQL uses the AUTO_INCREMENT keyword to perform an auto-increment feature. By default, the starting value for AUTO_INCREMENT is 1, and it will increment by 1 for each new record. VALUES ('Lars','Monsen'); The SQL statement above would insert a new record into the "Persons" table.

Is Postgres serial unique?

SERIAL data type allows you to automatically generate unique integer numbers (IDs, identity, auto-increment, sequence) for a column.


1 Answers

There is no unhacky solution to this: you either stick to SERIAL functionality or handle this by yourself.

But you got me interested, and I've come up with this (little bit less hacky, I hope) solution: create a trigger to do all the dirty job.

Trigger function

(notices added so we can see what's happening):

CREATE OR REPLACE FUNCTION update_seq_val_seq_test()
  RETURNS TRIGGER AS $$
BEGIN
  RAISE NOTICE 'id is %', NEW.id;
  IF NEW.id > currval('seq_test_id_seq' :: REGCLASS)
  THEN
    RAISE NOTICE 'curval is %', currval('seq_test_id_seq' :: REGCLASS);
    PERFORM setval('seq_test_id_seq' :: REGCLASS, (NEW.id) :: BIGINT);
    RAISE NOTICE 'new curval is %', currval('seq_test_id_seq' :: REGCLASS); END IF;
  RETURN NULL;
END;
$$
LANGUAGE 'plpgsql' COST 1;

Set trigger

CREATE TRIGGER seq_test_update_serial
  AFTER INSERT ON seq_test
  FOR EACH ROW EXECUTE PROCEDURE update_seq_val_seq_test();

Pulling the trigger

Fast'n'dirty testing

tests2=# insert into seq_test (name) values ('first');
NOTICE:  id is 30
INSERT 0 1
tests2=# select * from seq_test;
 id | name  
----+-------
 30 | first
(1 row)

tests2=# select currval('seq_test_id_seq'::regclass);
 currval 
---------
      30
(1 row)

tests2=# insert into seq_test (id, name) values (31, 'thirty one');
NOTICE:  id is 31
NOTICE:  curval is 30
NOTICE:  new curval is 31
INSERT 0 1
tests2=# select currval('seq_test_id_seq'::regclass);
 currval 
---------
      31
(1 row)

tests2=# select * from seq_test;
 id |    name    
----+------------
 30 | first
 31 | thirty one
(2 rows)

tests2=# insert into seq_test (name) values ('thirty dunno what');
NOTICE:  id is 32
INSERT 0 1
tests2=# insert into seq_test (id, name) values (21, 'back to the future');
NOTICE:  id is 21
INSERT 0 1
tests2=# select currval('seq_test_id_seq'::regclass);
 currval 
---------
      32
(1 row)

tests2=# select * from seq_test;
 id |        name        
----+--------------------
 30 | first
 31 | thirty one
 32 | thirty dunno what
 21 | back to the future
(4 rows)

tests2=# insert into seq_test (name) values ('thirty dunno what++');
NOTICE:  id is 33
INSERT 0 1
tests2=# select * from seq_test;
 id |        name         
----+---------------------
 30 | first
 31 | thirty one
 32 | thirty dunno what
 21 | back to the future
 33 | thirty dunno what++
(5 rows)

So, now Postgres is handling this case more like you wanted it to, but there're plenty things to check for you: how's that work with bulk inserts, rollbacks, how's this trigger affect performance, and many more fun for you.

like image 156
Evgeniy Chekan Avatar answered Oct 19 '22 04:10

Evgeniy Chekan