Summary: in this tutorial, you’ll learn how to use the PostgreSQL identity column that automatically generates unique integers using an implicit sequence.
Getting Started with the PostgreSQL Identity Column #
In PostgreSQL, the identity column is a special feature that brings convenience by automatically generating unique integers using an implicit sequence.
To define an identity column, you use one of two the following constraints:
GENERATED ALWAYS AS IDENTITY
: This ensures the column always uses generated integer numbers. If a column has this constraint, you cannot provide an explicit value in the insert or update statement, except if you use theOVERRIDING SYSTEM VALUE
in theINSERT
statement.GENERATED BY DEFAULT AS IDENTITY
: This constraint, similar to the above, offers flexibility by allowing you to provide an explicit integer for insertion.
It’s typical to use the identity column as the primary key column of a table, as it automatically generates unique integers, simplifying the management of primary keys.
The following statement shows how to define a column as an identity column as a primary key column:
CREATE TABLE table_name (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
column1 data_type,
...
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Besides the INT
data type, you can use the SMALLINT
and BIGINT
data types.
Since the id
column uses the GENERATED AS ALWAYS IDENTITY
constraint, you cannot provide an id when inserting a row into the table.
The following statement will fail:
INSERT INTO
table_name (id, column1)
VALUES
(1, value1);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
But if you have a good reason to use an explicit id value, the OVERRIDING SYSTEM VALUE
clause in the INSERT
statement gives you the control:
INSERT INTO
table_name (id, column1)
OVERRIDING SYSTEM VALUE
VALUES
(1, value1);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
In this syntax, the OVERRIDING SYSTEM VALUE
clause allows you to insert value one into the id column explicitly.
The following shows how to define an identity column that uses the GENERATED BY DEFAULT AS IDENTITY
constraint:
CREATE TABLE table_name (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
column1 data_type,
...
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
In this table, you can let the sequence generate unique integers or explicitly provide an integer in the INSERT
statement:
INSERT INTO table_name(id, column1)
VALUES(1, value1);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Using the PostgreSQL identity column as the primary key column #
First, create a table sales_orders
that stores sales order headers:
CREATE TABLE sales_orders(
order_no INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_name VARCHAR(255) NOT NULL,
status VARCHAR(25) NOT NULL,
requested_date DATE NOT NULL,
created_date TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The id
column of the sales_orders
table is an identity column.
Second, insert two rows into the sales_orders
table:
INSERT INTO
sales_orders (customer_name, status, requested_date)
VALUES
('John Doe', 'Pending', '2024-12-15'),
('Jane Doe', 'Completed', '2024-12-20')
RETURNING order_no;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
order_no
----------
1
2
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Since the order_no
is an identity column, PostgreSQL automatically generates unique integers.
The id
column of the sales_orders
table uses the GENERATED ALWAYS AS IDENTITY
constraint. Therefore, it will not accept an implicit value.
Third, attempt to insert a new row into the table with an implicit id:
INSERT INTO sales_orders (order_no , customer_name, status, requested_date)
VALUES(3, 'Adam Smith', 'Pending', '2024-12-17');
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Error:
Column "order_no" is an identity column defined as GENERATED ALWAYS.
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
To insert a value into the GENERATED ALWAYS AS IDENTITY
column, you can use the OVERRIDING SYSTEM VALUE
clause in the insert statement:
INSERT INTO sales_orders (order_no , customer_name, status, requested_date)
OVERRIDING SYSTEM VALUE
VALUES(3, 'Adam Smith', 'Pending', '2024-12-17')
RETURNING order_no;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
order_no
----------
3
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Using the PostgreSQL identity column as a non-primary key column #
First, create a new table called sales_order_items
that stores the items of sales orders:
CREATE TABLE sales_order_items (
order_no INT,
item_no INT GENERATED BY DEFAULT AS IDENTITY UNIQUE,
product_name VARCHAR(255) NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
price DEC(15, 2) NOT NULL CHECK (price > 0),
PRIMARY KEY (order_no, item_no),
FOREIGN KEY (order_no) REFERENCES sales_orders (order_no) ON DELETE CASCADE
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Second, add two items to the order #1:
INSERT INTO
sales_order_items (order_no, product_name, quantity, price)
VALUES
(1, 'iPhone 16 Pro', 5, 1299.99),
(1, 'Screen Protector', 5, 9.99)
RETURNING order_no, item_no;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
order_no | item_no
----------+---------
1 | 1
1 | 2
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, add a new item into to the sales order one but provide the item_no
value explicitly:
INSERT INTO
sales_order_items (order_no, item_no, product_name, quantity, price)
VALUES
(1, 3, 'Charger', 5, 29.99)
RETURNING order_no, item_no;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
order_no | item_no
----------+---------
1 | 3
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Since the item_no
column uses the GENERATED BY DEFAULT AS IDENTITY
constraint, we can explicitly insert an integer into it.
Implicit Sequences #
When you create an identity column for a table, PostgreSQL automatically generates an implicit sequence and associates it with the column.
The sequence will have the following naming convention:
<table_name>_<column_name>_seq
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
For example, PostgreSQL creates the following sequence for the order_no
column of the sales_orders
table:
sales_orders_order_no_seq
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Similarly, it creates the following sequence for the item_no
column of the sales_order_items
table:
sales_order_items_item_no_seq
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
PostgreSQL allows you to configure the sequence when defining an identity column.
For example, the following creates a table sales_order_item_schedules
that stores the scheduling of delivery dates and quantities for each line item in a sales order.
CREATE TABLE sales_order_item_schedules (
item_no INT,
schedule_no INT GENERATED BY DEFAULT AS IDENTITY (INCREMENT 10 START 10),
delivery_qty INT NOT NULL,
delivered_on DATE NOT NULL,
PRIMARY KEY (item_no, schedule_no),
FOREIGN KEY (item_no)
REFERENCES sales_order_items (item_no)
ON DELETE CASCADE
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The schedule_no
column is an identity column. Additionally, we configure the underlying sequence so the number starts at ten and increments by ten for each sales order line item.
The following statement inserts three rows into the sales_order_item_schedules
table:
INSERT INTO
sales_order_item_schedules (item_no, delivery_qty, delivered_on)
VALUES
(1, 1, '2024-12-16'),
(1, 2, '2024-12-17'),
(1, 2, '2024-12-17')
RETURNING item_no, schedule_no ;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
item_no | schedule_no
---------+-------------
1 | 10
1 | 20
1 | 30
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The output indicates that the values in the schedule_no
column start at ten and increment by for each row.
Configuring an existing column as an identity column #
To change a column to an identity column, you can use the ALTER TABLE
statement:
ALTER TABLE table_name
ALTER COLUMN column_name
ADD GENERATED { ALWAYS | BY DEFAULT }
AS IDENTITY
[sequence_option];
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The column_name
must have a NOT NULL
constraint or you’ll encounter an error.
Let’s take an example:
First, create a table taxes
to store sales taxes:
CREATE TABLE taxes (
id INT PRIMARY KEY,
rate DECIMAL(5, 2) NOT NULL
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Second, set the id
column as an identity column:
ALTER TABLE taxes
ALTER COLUMN id
ADD GENERATED ALWAYS AS IDENTITY;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, insert some rows into the taxes
table:
INSERT INTO
taxes (rate)
values
(0.05),
(0.08)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
id | rate
----+------
1 | 0.05
2 | 0.08
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Setting identity column characteristics #
PostgreSQL allows you to configure the characteristics of an identity column:
- Switching between the
GENERATED ALWAYS
andGENERATED BY DEFAULT
. - Change the parameters for the implicit sequence.
Here’s the syntax:
ALTER TABLE table_name
ALTER COLUMN column_name
SET GENERATED { ALWAYS | BY DEFAULT}
[SET sequence_option]
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
For example:
First, change the id column of the taxes table from GENERATED ALWAYS
to GENERATED BY DEFAULT
:
ALTER TABLE taxes
ALTER COLUMN id
SET GENERATED BY DEFAULT;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Second, insert an explicit integer value into the id column of the taxes table:
INSERT INTO taxes(id, rate)
VALUES(3, 0.1)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
id | rate
----+------
3 | 0.10
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, set the increment value by 5, instead of using 1 by default:
ALTER TABLE taxes
ALTER COLUMN id
SET INCREMENT BY 5;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Finally, insert a new row into the taxes
table:
INSERT INTO taxes(rate)
VALUES(0.12)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The id value is seven instead, not 8 (3 + 5), because the sequence did not generate the number 3. It returns the next sequence value, 7 ( 2 + 5 ).
Removing GENERATED ALWAYS (or BY DEFAULT) AS IDENTITY constraints #
To drop a GENERATED ALWAYS
(or BY DEFAULT
) AS IDENTITY
constraint from a column, you use the ALTER TABLE DROP IDENTITY
statement:
ALTER TABLE table_name
ALTER COLUMN column_name
DROP IDENTITY [ IF EXISTS ];
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The IF EXISTS
clause conditionally drops the identity constraint only if it exists.
For example, the following statement removes the GENERATED ALWAYS
(or BY DEFAULT
) AS IDENTITY
constraint from the id
column of the taxes
table:
ALTER TABLE taxes
ALTER COLUMN id
DROP IDENTITY IF EXISTS;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Summary #
- Use the identity column to generate unique integers using an implicit sequence.
- Set up an identity column using the
GENERATED AWAYS AS IDENTITY
orGENERATED BY DEFAULT AS DEFAULT
constraint.