When you design a database table, you often need a unique identifier, such as a user ID or an order number, to serve as a primary key. Managing these IDs manually is tedious and error-prone. To solve this problem, PolarDB for PostgreSQL (Compatible with Oracle) provides two features: sequences and auto-generated columns. Both features can automatically and safely generate unique number sequences. This process simplifies primary key management and ensures data consistency.
Features
PolarDB for PostgreSQL (Compatible with Oracle) offers several ways to generate unique sequence values. These methods meet the needs of different scenarios and are highly compatible with Oracle Database.
Traditional sequences
A traditional sequence is an independent database object created using theCREATE SEQUENCEcommand. When you insert data, you must explicitly retrieve the next value from the sequence by calling theNEXTVALpseudocolumn, such asmy_seq.NEXTVAL. Then, you insert that value into the table. This method is the most flexible because the sequence and the table are decoupled.Auto-generated columns
This feature was introduced in Oracle 12c. It implicitly binds a sequence to a table column. When you create a table, add theGENERATED ... AS IDENTITYclause to the column definition. The database then automatically generates a value for the column when a new row is inserted. You do not need to handle this in theINSERTstatement. This method is more convenient and follows modern database design trends.
Auto-generated columns have three types based on their behavior policy:
GENERATED ALWAYS AS IDENTITY: Forces auto-generation. The system always generates a value for the column. You cannot provide an explicit value for the column in anINSERTstatement. If you attempt to provide a value, an error occurs. This policy is the strictest and ensures that the database completely controls the ID.GENERATED BY DEFAULT AS IDENTITY: Default auto-generation. The system generates a value for the column only when theINSERTstatement does not specify one. If you provide a specific value, the system uses the provided value. However, if you explicitly provideNULL, an error occurs.GENERATED BY DEFAULT ON NULL AS IDENTITY: Default or auto-generate on NULL. This is the most flexible policy. The system automatically generates a value if theINSERTstatement does not specify the column or if you explicitly provide aNULLvalue for the column. This method is useful for data migration or for scenarios that require a mix of custom and auto-generated IDs.
Prerequisites
Your PolarDB for PostgreSQL (Compatible with Oracle) cluster must be minor engine version 2.0.14.17.36.0 or later.
You can view the minor engine version number in the console or check it using the SHOW polardb_version; statement. If the minor engine version requirement is not met, you must upgrade the minor engine version.
Advantages
Simplified development: Auto-generated columns encapsulate the ID generation logic in the table definition. This makes
INSERTstatements simpler because you do not need to call a sequence.Guaranteed uniqueness: The database ensures that generated sequence values are unique. This process avoids conflicts that can occur when the application layer generates IDs.
High flexibility: The
GENERATED BY DEFAULT ON NULLpolicy supports both automatic generation and manual specification. This makes it ideal for complex scenarios, such as data import and historical data migration.
Recommendations
The following table compares the features of different sequence generation methods.
Generation method | Manual value specification | Explicit NULL insert | Flexibility | Data migration friendliness | Recommended use cases |
Traditional sequence ( | Supported | Supported (column must allow NULL) | Highest | High | Sharing a sequence among multiple tables. |
Forced generation ( | Not supported | Not applicable | Lowest | Low | Strict auto-increment. No intervention allowed. |
Default generation ( | Supported | Not supported (returns an error) | Middle | Medium | Allows overwriting IDs, but does not allow NULL. |
Default or on-NULL generation ( | Supported | Supported (triggers auto-generation) | High | Highest | New applications, data migration, and mixed-ID scenarios. |
Best practices
For new applications, use
GENERATED BY DEFAULT ON NULL AS IDENTITY. It offers the most flexibility.GENERATED BY DEFAULT ON NULLis the ideal choice for data migration or for importing data that contains existing IDs.If your business requires the database to strictly control IDs with no manual intervention, use
GENERATED ALWAYS AS IDENTITY.In high-concurrency insert scenarios, set a reasonable
CACHEvalue, such asCACHE 20or higher, for anIDENTITYcolumn or traditional sequence. This can significantly improve performance.
Using traditional sequences
With this method, the sequence and the table are two separate objects.
Create a sequence
First, use the
CREATE SEQUENCEcommand to create a sequence object. You can customize its properties, such as the start value, step size, and maximum or minimum values.CREATE SEQUENCE emp_seq START WITH 1000 -- Start value is 1000 INCREMENT BY 1 -- Increment by 1 MAXVALUE 9999 -- Maximum value is 9999 NOCYCLE -- Do not cycle CACHE 20; -- Cache 20 values to improve performanceParameter descriptions
START WITH: The starting value.INCREMENT BY: The increment step size.MAXVALUE/NOMAXVALUE: The maximum value.MINVALUE/NOMINVALUE: The minimum value.CYCLE/NOCYCLE: Specifies whether the sequence cycles.CACHE/NOCACHE: The number of values to cache.ORDER/NOORDER: Specifies whether to guarantee the order.
Use the sequence in an
INSERTstatementWhen you insert data into the table, call the sequence's
NEXTVALpseudocolumn to obtain a new ID.-- Create a table that uses the sequence CREATE TABLE employees ( emp_id NUMBER(10) PRIMARY KEY, emp_name VARCHAR2(50) NOT NULL ); -- Call emp_seq.NEXTVAL when inserting data INSERT INTO employees (emp_id, emp_name) VALUES (emp_seq.NEXTVAL, 'John Smith'); INSERT INTO employees (emp_id, emp_name) VALUES (emp_seq.NEXTVAL, 'Jane Doe'); -- Query the results SELECT * FROM employees; -- emp_id | emp_name -- --------+------------ -- 1000 | John Smith -- 1001 | Jane DoeQuery the results
SELECT * FROM employees;The following result is returned:
emp_id | emp_name --------+------------ 1000 | John Smith 1001 | Jane Doe (2 rows)Manage the sequence
Query the
user_sequencesview to check the current status of the sequence. You can also use theALTER SEQUENCEcommand to modify its properties.-- View the current value of the sequence (requires calling NEXTVAL once first) SELECT emp_seq.CURRVAL FROM dual; -- Modify the sequence's step size and cache ALTER SEQUENCE emp_seq INCREMENT BY 2 CACHE 50;
Using auto-generated columns (identity columns)
This method binds the sequence directly to the table column. It is more modern and convenient.
Forced generation (GENERATED ALWAYS)
This is suitable for cases where all IDs must be strictly controlled by the database.
Create a table: Create a table with a forced auto-generated column and insert data.
-- Create a table with a forced auto-generated column CREATE TABLE products ( product_id NUMBER GENERATED ALWAYS AS IDENTITY ( START WITH 100 INCREMENT BY 10 ), product_name VARCHAR2(100) NOT NULL ); -- When inserting data, you do not need to and cannot specify the product_id column INSERT INTO products (product_name) VALUES ('Laptop'); INSERT INTO products (product_name) VALUES ('Mouse');Try to insert an ID explicitly.
INSERT INTO products (product_id, product_name) VALUES (999, 'Keyboard');The command fails and returns an error.
ERROR: cannot insert a non-DEFAULT value into column "product_id" DETAIL: Column "product_id" is an identity column defined as GENERATED ALWAYS.Query the results
SELECT * FROM products ORDER BY product_id;The following result is returned:
product_id | product_name ------------+-------------- 100 | Laptop 110 | Mouse (2 rows)
Default generation (GENERATED BY DEFAULT)
This method lets you overwrite the auto-generated ID when needed. Unlike the ON NULL clause, this method does not allow you to explicitly insert NULL.
Create a table.
CREATE TABLE transactions ( trans_id NUMBER GENERATED BY DEFAULT AS IDENTITY, amount NUMBER(10,2) );Insertion method 1: Do not specify an ID to have one automatically generated.
INSERT INTO transactions (amount) VALUES (500.00);Insertion method 2: Explicitly insert a custom ID.
INSERT INTO transactions (trans_id, amount) VALUES (9999, 200.00);Insertion method 3: Explicitly inserting NULL fails.
INSERT INTO transactions (trans_id, amount) VALUES (NULL, 150.00);The command fails and returns an error.
ERROR: null value in column "trans_id" of relation "transactions" violates not-null constraint DETAIL: Failing row contains (null, 150).Query the results.
SELECT * FROM transactions ORDER BY trans_id;The following result is returned:
trans_id | amount ----------+-------- 1 | 500 9999 | 200 (2 rows)
Default or on-NULL generation (GENERATED BY DEFAULT ON NULL)
This is the most flexible method. It is recommended for new applications and data migration scenarios.
Create a table: Create a table with a flexible auto-generated column.
-- Create a table with a flexible auto-generated column CREATE TABLE customers ( customer_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY, customer_name VARCHAR2(100) NOT NULL );Insertion method 1: Do not specify an ID. The database automatically generates it.
INSERT INTO customers (customer_name) VALUES ('John Doe');Insertion method 2: Explicitly insert a custom ID, for example, when migrating historical data.
INSERT INTO customers (customer_id, customer_name) VALUES (5000, 'Jane Smith');Insertion method 3: Explicitly insert NULL. The database automatically generates the ID.
INSERT INTO customers (customer_id, customer_name) VALUES (NULL, 'Bob Wilson');Query the results.
SELECT * FROM customers ORDER BY customer_id;The following result is returned:
customer_id | customer_name -------------+--------------- 1 | John Doe 2 | Bob Wilson 5000 | Jane Smith (3 rows)
Managing sequence
You can manage and maintain both traditional sequences and the internal sequences associated with auto-generated columns.
View sequence information
YQuery the user_sequences view to monitor the status of all user-defined sequences.
SELECT sequence_name, min_value, max_value, increment_by,
last_number, cache_size, cycle_flag
FROM user_sequences;Modify sequence properties
Use the ALTER SEQUENCE command to modify sequence properties, such as the step size, maximum value, and cache size.
-- Change the step size of the emp_seq sequence to 2, the maximum value to 99999, and the cache to 50
ALTER SEQUENCE emp_seq
START WITH 1005
INCREMENT BY 2
MAXVALUE 99999
CACHE 50;Reset a sequence
PolarDB for PostgreSQL (Compatible with Oracle) does not support a command to reset a sequence. The standard method is to delete and then re-create the sequence.
-- Delete the existing sequence
DROP SEQUENCE emp_seq;
-- Recreate the sequence and set a new starting value
CREATE SEQUENCE emp_seq START WITH 5000 INCREMENT BY 1;Use cases
The GENERATED BY DEFAULT ON NULL feature simplifies user registration by automatically generating IDs for new users, while also supporting specified historical IDs for data migration or batch imports.
Create a user accounts table.
CREATE TABLE user_accounts ( user_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY ( START WITH 100000 CACHE 100 ), username VARCHAR2(50) UNIQUE NOT NULL, email VARCHAR2(100) UNIQUE NOT NULL, created_date DATE DEFAULT SYSDATE );Case 1: A new user registers. The ID is automatically generated.
INSERT INTO user_accounts (username, email) VALUES ('john_doe', 'john@example.com');Case 2: Migrate data from an old system. A historical ID is specified.
INSERT INTO user_accounts (user_id, username, email, created_date) VALUES (99999, 'legacy_user', 'leg***@system.com', DATE '2020-01-01');Case 3: Batch import. Use a mix of automatically generated and specified IDs.
INSERT ALL INTO user_accounts (username, email) VALUES ('alice', 'ali**@test.com') INTO user_accounts (user_id, username, email) VALUES (88888, 'system_acct', 'sys***@app.com') SELECT * FROM dual;Query the final results.
SELECT * FROM user_accounts ORDER BY user_id;The following result is returned:
user_id | username | email | created_date ---------+-------------+-------------------+--------------------- 88888 | system_acct | sys***@app.com | 2025-09-17 10:21:38 99999 | legacy_user | leg***@system.com | 2020-01-01 00:00:00 100000 | john_doe | john@example.com | 2025-09-17 10:21:38 100001 | alice | ali**@test.com | 2025-09-17 10:21:38 (4 rows)