PostgreSQL Auto Increment: SERIAL vs IDENTITY Guide
PostgreSQL auto increment explained for beginners: learn SERIAL vs IDENTITY, RETURNING id, gaps and resetting the counter, with simple copy-paste examples.
PostgreSQL auto increment explained for beginners: learn SERIAL vs IDENTITY, RETURNING id, gaps and resetting the counter, with simple copy-paste examples.
PostgreSQL auto increment means the database fills in a new, unique number for you every time you add a row. You do not type the number yourself. In PostgreSQL there is no keyword called AUTO_INCREMENT. Instead you use either SERIAL (the older way) or GENERATED ... AS IDENTITY (the modern way). This guide explains both in plain words, with small examples you can copy into psql.
You need PostgreSQL installed and a way to run commands, such as psql. If you have not done that yet, start with our guide on creating a table in PostgreSQL first.
Imagine a ticket machine at a bakery. Each person gets the next number: 1, 2, 3, and so on. Nobody picks their own number, and no two people get the same one. Auto increment works the same way for the rows in a table.
This is useful for an id column. An id is a number that identifies one row, like a student number. Each row needs a different id, and typing them by hand is slow and easy to get wrong.
SERIAL is a shortcut. When you use it, PostgreSQL makes an integer column and a hidden counter (called a sequence) behind the scenes. Here is a table of students.
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);Line 1 creates a table named students. Line 2 adds a column called id. SERIAL makes it count up on its own, and PRIMARY KEY says every id must be unique and never empty. Line 3 adds a name column that must have a value. Remember to end the command with a semicolon.
Since PostgreSQL 10, there is a standard way to do the same thing. It follows the official SQL rules, so it is the better choice for new tables.
CREATE TABLE teachers (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL
);The only change is the id line. INTEGER is the data type, a whole number. GENERATED ALWAYS AS IDENTITY tells PostgreSQL to create the number itself. PRIMARY KEY works as before.
Now add rows. Leave the id out and PostgreSQL fills it in.
INSERT INTO students (name) VALUES ('Ana');
INSERT INTO students (name) VALUES ('Ben');
SELECT * FROM students;The first two lines each add one student, and we only give the name. The third line shows all rows. You will see Ana with id 1 and Ben with id 2.
INSERT INTO students (name)
VALUES ('Cara')
RETURNING id;RETURNING id asks PostgreSQL to tell you the id it just made. This is handy when your app needs to know the new row's number. Here it returns 3.
| Feature | SERIAL | IDENTITY |
|---|---|---|
| Standard SQL | No, PostgreSQL only | Yes |
| Available since | Very old versions | PostgreSQL 10 |
| Blocks manual ids | No | Yes, with ALWAYS |
| Best for | Old tutorials and old projects | New tables |
For a beginner, the simple rule is: use IDENTITY for new tables, and do not worry if you see SERIAL in older tutorials. They behave almost the same day to day.
There are two kinds of identity columns. GENERATED ALWAYS stops you from setting the id by hand, which protects you from mistakes. GENERATED BY DEFAULT lets you set your own id if you really want to.
CREATE TABLE courses (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL
);
INSERT INTO courses (id, title) VALUES (100, 'SQL Basics');BIGINT is a bigger whole number type, good for tables that may grow very large. Because we used BY DEFAULT, the insert with id 100 works. With ALWAYS, PostgreSQL would show an error. One warning: after you insert your own id, the counter does not know about it, so it may later try to use the same number and fail. Letting the database choose is safest.
Sometimes you will see ids like 1, 2, 4, 5. The number 3 is missing. This is normal. The counter never goes backward. If an insert fails, or you delete a row, that number is not reused. Gaps do not hurt anything, and you should not rely on ids having no gaps.
While learning, you may want to empty a table and start from 1 again.
TRUNCATE TABLE students RESTART IDENTITY;TRUNCATE removes every row from the table very quickly, and RESTART IDENTITY sets the counter back to 1. Be careful: this deletes all your data, so only use it on practice tables.
If your table already exists without it, you can change the column. This works when the column is a whole number type.
ALTER TABLE notes
ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY;ALTER TABLE changes a table that already exists. ALTER COLUMN id picks the column, and ADD GENERATED ALWAYS AS IDENTITY turns on the counter. If the table already has rows, check the counter starts after your highest id.
No. That keyword is from MySQL. PostgreSQL uses SERIAL or GENERATED AS IDENTITY to get the same result.
Use IDENTITY for new tables because it follows the SQL standard and is safer. SERIAL still works and you will see it in many older guides.
Add RETURNING id at the end of your INSERT statement. PostgreSQL sends back the new id right away.
The counter never goes backward. Failed inserts and deleted rows still use up numbers, so gaps are normal and harmless.
Run TRUNCATE TABLE your_table RESTART IDENTITY; to empty the table and start at 1. This deletes all rows, so use it only on practice data.
Want to understand why every table needs a unique id? Read our beginner guide to primary keys and foreign keys next.
Read: Primary Keys and Foreign KeysLearning to code and want friendly help with your next project? Talk to the Fepiq team.
Contact FepiqOccasional, no-fluff notes on shipping modern software — startups, automation, Laravel, Shopify and more. No spam, unsubscribe anytime.
Keep reading
SQL subquery explained for beginners: learn what a subquery is and how to use it in WHERE, FROM and SELECT with simple, copy-paste examples you can run.
What is an arrow function in JavaScript? Learn the => syntax step by step with simple examples, plain English and a quick practice quiz. Start here.
Let's build something
Book a free discovery call. We'll listen, ask sharp questions, and send you a proposal within 3 business days.