695

I'm switching from MySQL to PostgreSQL and I was wondering how can I have an INT column with AUTO INCREMENT. I saw in the PostgreSQL docs a datatype called SERIAL, but I get syntax errors when using it.

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
Ian
  • 24,116
  • 22
  • 58
  • 96
  • 13
    if you'd provide the query and error you're getting - perhaps someone could tell you what is wrong with the query. –  Apr 25 '09 at 09:42
  • 2
    My first hit too Mich' and as it's a question that gets enough views to be relevant, why not vote it up. PS it's not trivial if you don't know how to do it. – baash05 Jan 06 '12 at 00:54
  • 1
    SERIAL is the preferred choice if your client driver is Npgsql. The provider is internally selecting new values after an INSERT using SELECT currval(pg_get_serial_sequence('table', 'column')). This will fail if the underlying column is not of type serial (numeric type + explicit sequence for instance) –  Jul 11 '12 at 12:33
  • Just for curiousity... Why does someone have to migrate from MySQL which is very good, to PostgreSql? – villamejia Mar 01 '16 at 19:54
  • 34
    ...which is even better. – tim-phillips Nov 21 '17 at 02:44

11 Answers11

805

Yes, SERIAL is the equivalent function.

CREATE TABLE foo (
    id SERIAL,
    bar varchar
);

INSERT INTO foo (bar) VALUES ('blah');
INSERT INTO foo (bar) VALUES ('blah');

SELECT * FROM foo;

+----------+
| 1 | blah |
+----------+
| 2 | blah |
+----------+

SERIAL is just a create table time macro around sequences. You can not alter SERIAL onto an existing column.

yakirchi
  • 11
  • 2
Trey
  • 11,032
  • 1
  • 23
  • 21
  • Since which version could this datatype be found? I've been using sequences.nextval in pgsql 'cause I haven't noticed such a thing. Thanks. – Alfabravo Feb 01 '10 at 20:54
  • 3
    as he said, it is just a macro around sequences. the SERIAL type is just an integer, and a sequence, with the column default to the sequences next value. Unlike MySQL there is nothing special and voodooey that goes on. – Evan Carroll Feb 01 '10 at 21:07
  • 30
    because unless you have a table `"Table"` and `"table"` then just leave it unquoted and canonicalize it to `table`. The convention is simply never to use quotes in Pg. You can, if you want, use mixed case names for appearance, just don't require it: `CREATE TABLE fooBar ( .. ); SELECT * FROM fooBar;` will work, as will `SELECT * FROM foobar`. – Evan Carroll Jun 14 '11 at 05:04
  • 7
    plus...the standard for most relational db implementations is to not have quotes. It makes it simpler to swap database implementations if ever necessary. – vinnybad Aug 31 '11 at 22:54
  • It is a bad idea to use upper case characters for identifiers in postgresql to begin with. If you refrain from such folly, you'll never have to quote an identifier. – Erwin Brandstetter Sep 19 '11 at 19:25
  • 11
    @EvanCarroll try `INSERT INTO user` without quotes, please. – OZ_ Apr 29 '13 at 20:25
  • 32
    Per postgres doc, either consistently quote or unquote: http://www.postgresql.org/docs/current/interactive/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS – Καrτhικ Sep 23 '13 at 13:28
  • 2
    Evan Carroll: If you have a table "Table" and a table "table", I would suspect that you have some problematic masochistic tendencies, and I'd recommend that you speak to a psychiatrist. – dlp Apr 08 '16 at 01:54
  • 5
    There is nothing wrong with quoting. In fact, it's Postgres that's the weird one here with a caseless convention for schema. Migrating from any other database requires this. – Michael Brown Sep 01 '16 at 01:50
  • 1
    "Per postgres doc, either consistently quote or unquote" But what if you have to quote to escape a reserved word one day you are left with a choice. To either make en exception to the rule just this once (which is NOT full consistency) or retroactively changes all other sql to use quotes (big waste of time). Why not start with quotes from the get go? Works in ever situation where as not quoting does not. Be consistent with the method that will work in ALL cases. OR just not care about perfect consistency. What advantage is there to being consistent here? None. Move on to bigger problems. – mBrice1024 Sep 24 '17 at 16:42
  • 2
    While this answer has been the right one for many years, since Postgres 10 you can use `IDENTITY` instead, as described by @a_horse_with_no_name at https://stackoverflow.com/a/47035250/968244 – isapir Dec 18 '17 at 17:32
  • quoting the column and names of tables is GOOD practice. For example `CREATE TABLE \`table\` (\`id\` SERIAL, \`bar\` varchar)`. Without it the SQL is not correct (table is reserved word). But... Please note of type of quoting. Not double quote but single reverse quote. – 18C Feb 09 '18 at 14:01
  • Not really, this answer should not be the accepted one, read the answers by @Programster. – Lucas Alonso Jul 14 '22 at 15:03
  • and @a_horse_with_no_name – Lucas Alonso Jul 14 '22 at 15:03
255

You can use any other integer data type, such as smallint.

Example :

CREATE SEQUENCE user_id_seq;
CREATE TABLE user (
    user_id smallint NOT NULL DEFAULT nextval('user_id_seq')
);
ALTER SEQUENCE user_id_seq OWNED BY user.user_id;

Better to use your own data type, rather than user serial data type.

user272735
  • 10,473
  • 9
  • 65
  • 96
Ahmad
  • 4,224
  • 8
  • 29
  • 40
  • 13
    I'd say this is actually the better answer because it allowed me to modify a table I had just created in PostgreSQL by setting the columns default (after reading up on `CREATE SEQUENCE` http://www.postgresql.org/docs/8.1/interactive/sql-createsequence.html ). HOWEVER, I'm not quite sure why you changed the owner. – JayC Dec 15 '11 at 21:48
  • 12
    @JayC: From [documentation](http://www.postgresql.org/docs/9.1/static/datatype-numeric.html#DATATYPE-SERIAL): _Lastly, the sequence is marked as "owned by" the column, so that it will be dropped if the column or table is dropped._ – user272735 May 18 '12 at 15:34
  • 12
    why doesn't postgres community just reinvent the autoincrement keyword? – Dr Deo May 27 '12 at 10:21
  • 2
    @Dr Deo : they use serial instead autoincrement keyword, i don't know why :) – Ahmad Jul 22 '12 at 07:16
  • 4
    There's also smallserial if you just want a smaller data type. – beldaz May 16 '13 at 23:57
  • There's also `bigserial` if you want a bigger data type that will never run out of unique values. – doug65536 Aug 06 '17 at 04:15
  • 1
    As a MySQL fan I can't imaging the life without `UNSIGNED` and `AUTO_INCREMENT`, those give u flexibility to choose how to define any column and for sure how to `ALTER` the latter. I'm also do not like the `CREATE SEQUENCE` approach neither it's ability to increment notwithstanding on failures of an `INSERT` query. – Arthur Kushman Sep 04 '17 at 07:40
  • 2
    Why is it better to use your own data type? In the current implementation, using SERIAL is the same as doing what you showed using type integer: https://www.postgresql.org/docs/9.1/datatype-numeric.html#DATATYPE-SERIAL – Marcos Pereira Jun 26 '19 at 11:05
  • Should `user_id` in the example be a `PRIMARY KEY`? I think so. – Rick-777 Mar 12 '20 at 10:32
  • i.e. `user_id smallint NOT NULL PRIMARY KEY DEFAULT nextval('user_id_seq')` – Rick-777 Mar 12 '20 at 10:33
  • 4
    I never understood why some developers reinvent the wheel or complicate things. If Postgres already has an internal mechanism optimized and created specifically for this problem (Serial), why make everything more complicated by creating a sequence? – Genarito Jun 15 '20 at 19:11
122

If you want to add sequence to id in the table which already exist you can use:

CREATE SEQUENCE user_id_seq;
ALTER TABLE user ALTER user_id SET DEFAULT NEXTVAL('user_id_seq');
Felipe
  • 16,649
  • 11
  • 68
  • 92
sereja
  • 1,356
  • 1
  • 8
  • 6
68

Starting with Postgres 10, identity columns as defined by the SQL standard are also supported:

create table foo 
(
  id integer generated always as identity
);

creates an identity column that can't be overridden unless explicitly asked for. The following insert will fail with a column defined as generated always:

insert into foo (id) 
values (1);

This can however be overruled:

insert into foo (id) overriding system value 
values (1);

When using the option generated by default this is essentially the same behaviour as the existing serial implementation:

create table foo 
(
  id integer generated by default as identity
);

When a value is supplied manually, the underlying sequence needs to be adjusted manually as well - the same as with a serial column.


An identity column is not a primary key by default (just like a serial column). If it should be one, a primary key constraint needs to be defined manually.

57

Whilst it looks like sequences are the equivalent to MySQL auto_increment, there are some subtle but important differences:

1. Failed Queries Increment The Sequence/Serial

The serial column gets incremented on failed queries. This leads to fragmentation from failed queries, not just row deletions. For example, run the following queries on your PostgreSQL database:

CREATE TABLE table1 (
  uid serial NOT NULL PRIMARY KEY,
  col_b integer NOT NULL,
  CHECK (col_b>=0)
);

INSERT INTO table1 (col_b) VALUES(1);
INSERT INTO table1 (col_b) VALUES(-1);
INSERT INTO table1 (col_b) VALUES(2);

SELECT * FROM table1;

You should get the following output:

 uid | col_b 
-----+-------
   1 |     1
   3 |     2
(2 rows)

Notice how uid goes from 1 to 3 instead of 1 to 2.

This still occurs if you were to manually create your own sequence with:

CREATE SEQUENCE table1_seq;
CREATE TABLE table1 (
    col_a smallint NOT NULL DEFAULT nextval('table1_seq'),
    col_b integer NOT NULL,
    CHECK (col_b>=0)
);
ALTER SEQUENCE table1_seq OWNED BY table1.col_a;

If you wish to test how MySQL is different, run the following on a MySQL database:

CREATE TABLE table1 (
  uid int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  col_b int unsigned NOT NULL
);

INSERT INTO table1 (col_b) VALUES(1);
INSERT INTO table1 (col_b) VALUES(-1);
INSERT INTO table1 (col_b) VALUES(2);

You should get the following with no fragementation:

+-----+-------+
| uid | col_b |
+-----+-------+
|   1 |     1 |
|   2 |     2 |
+-----+-------+
2 rows in set (0.00 sec)

2. Manually Setting the Serial Column Value Can Cause Future Queries to Fail.

This was pointed out by @trev in a previous answer.

To simulate this manually set the uid to 4 which will "clash" later.

INSERT INTO table1 (uid, col_b) VALUES(5, 5);

Table data:

 uid | col_b 
-----+-------
   1 |     1
   3 |     2
   5 |     5
(3 rows)

Run another insert:

INSERT INTO table1 (col_b) VALUES(6);

Table data:

 uid | col_b 
-----+-------
   1 |     1
   3 |     2
   5 |     5
   4 |     6

Now if you run another insert:

INSERT INTO table1 (col_b) VALUES(7);

It will fail with the following error message:

ERROR: duplicate key value violates unique constraint "table1_pkey" DETAIL: Key (uid)=(5) already exists.

In contrast, MySQL will handle this gracefully as shown below:

INSERT INTO table1 (uid, col_b) VALUES(4, 4);

Now insert another row without setting uid

INSERT INTO table1 (col_b) VALUES(3);

The query doesn't fail, uid just jumps to 5:

+-----+-------+
| uid | col_b |
+-----+-------+
|   1 |     1 |
|   2 |     2 |
|   4 |     4 |
|   5 |     3 |
+-----+-------+

Testing was performed on MySQL 5.6.33, for Linux (x86_64) and PostgreSQL 9.4.9

Programster
  • 12,242
  • 9
  • 49
  • 55
  • 12
    You're giving a comparison but I don't see any solution here! Is it an answer? – Anwar Dec 23 '16 at 14:37
  • 4
    @Anwar it simply extends the various answers that state that the answer is to use a serial/sequence. This provides some important context to take into consideration. – Programster Apr 19 '17 at 16:11
  • 4
    Coming from 10 years with MSSQL and Mysql, Point 2 is just utterly ridiculous. A random, manual insert from a user can entirely break an application – dsturbid Aug 05 '20 at 08:33
  • 1
    @dsturbid A code bug or data quality problem might sometimes legitimately call for a manual insert in a production situation. That might be enough of a reason for some application developers to avoid using this feature. – WhyGeeEx Oct 13 '21 at 11:48
  • @dsturbid I agree this is bad, but "entirely break an application" is an overstatement. My testing shows that points 1 and 2 interact. That is, if you manually inserted id 5, you'll get an insertion failure when the autoincrement reaches 5, **but** that failure still increments the sequence. If you then retry the insert, it will succeed in creating the row with id 6. So the **application** isn't permanently broken, you just get a single insertion failure. – Nathan Long Jan 27 '22 at 15:16
  • 1
    "Failed Queries Increment The Sequence/Serial" - my testing with two `psql` instances shows that this also applies to failed transactions. If you `begin` two transactions, the first one to `INSERT` claims the first id. If the second transaction commits but the first rolls back, the first id will be skipped in the table. – Nathan Long Jan 27 '22 at 15:31
39

Sorry, to rehash an old question, but this was the first Stack Overflow question/answer that popped up on Google.

This post (which came up first on Google) talks about using the more updated syntax for PostgreSQL 10: https://blog.2ndquadrant.com/postgresql-10-identity-columns/

which happens to be:

CREATE TABLE test_new (
    id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
);

Hope that helps :)

Zhao Li
  • 4,936
  • 8
  • 33
  • 51
  • 1
    This is indeed the way to go in PostgreSQL 10 and it is the same syntax as other database software like DB2 or Oracle. – adriaan Feb 25 '18 at 15:19
  • 2
    @adriaan Actually the `GENERATED … AS IDENTITY` commands are standard SQL. First added in *SQL:2003*, then clarified in *SQL:2008*. See features # T174 & F386 & T178. – Basil Bourque May 04 '18 at 21:49
17

You have to be careful not to insert directly into your SERIAL or sequence field, otherwise your write will fail when the sequence reaches the inserted value:

-- Table: "test"

-- DROP TABLE test;

CREATE TABLE test
(
  "ID" SERIAL,
  "Rank" integer NOT NULL,
  "GermanHeadword" "text" [] NOT NULL,
  "PartOfSpeech" "text" NOT NULL,
  "ExampleSentence" "text" NOT NULL,
  "EnglishGloss" "text"[] NOT NULL,
  CONSTRAINT "PKey" PRIMARY KEY ("ID", "Rank")
)
WITH (
  OIDS=FALSE
);
-- ALTER TABLE test OWNER TO postgres;
 INSERT INTO test("Rank", "GermanHeadword", "PartOfSpeech", "ExampleSentence", "EnglishGloss")
           VALUES (1, '{"der", "die", "das", "den", "dem", "des"}', 'art', 'Der Mann küsst die Frau und das Kind schaut zu', '{"the", "of the" }');


 INSERT INTO test("ID", "Rank", "GermanHeadword", "PartOfSpeech", "ExampleSentence", "EnglishGloss")
           VALUES (2, 1, '{"der", "die", "das"}', 'pron', 'Das ist mein Fahrrad', '{"that", "those"}');

 INSERT INTO test("Rank", "GermanHeadword", "PartOfSpeech", "ExampleSentence", "EnglishGloss")
           VALUES (1, '{"der", "die", "das"}', 'pron', 'Die Frau, die nebenen wohnt, heißt Renate', '{"that", "who"}');

SELECT * from test; 
trev
  • 171
  • 1
  • 2
16

In the context of the asked question and in reply to the comment by @sereja1c, creating SERIAL implicitly creates sequences, so for the above example-

CREATE TABLE foo (id SERIAL,bar varchar);

CREATE TABLE would implicitly create sequence foo_id_seq for serial column foo.id. Hence, SERIAL [4 Bytes] is good for its ease of use unless you need a specific datatype for your id.

Prince
  • 20,353
  • 6
  • 39
  • 59
15

Since PostgreSQL 10

CREATE TABLE test_new (
    id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    payload text
);
6

This way will work for sure, I hope it helps:

CREATE TABLE fruits(
   id SERIAL PRIMARY KEY,
   name VARCHAR NOT NULL
);

INSERT INTO fruits(id,name) VALUES(DEFAULT,'apple');

or

INSERT INTO fruits VALUES(DEFAULT,'apple');

You can check this the details in the next link: http://www.postgresqltutorial.com/postgresql-serial/

webtechnelson
  • 330
  • 1
  • 7
  • 13
3

Create Sequence.

CREATE SEQUENCE user_role_id_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 3
  CACHE 1;
ALTER TABLE user_role_id_seq
  OWNER TO postgres;

and alter table

ALTER TABLE user_roles ALTER COLUMN user_role_id SET DEFAULT nextval('user_role_id_seq'::regclass);