Sequences and Synonyms
This chapter covers two small but essential Oracle schema objects: sequences (which generate unique numbers for primary keys) and synonyms (which give an object an alternative name).
They are independent features but are commonly introduced together because both belong to the "schema utility" family of objects you create alongside tables.
Part 1 โ Sequences
A sequence is a database object that generates a stream of unique numbers. The most common use is to feed those numbers into a primary key column.
Why You Need Sequences
Imagine you're inserting employees into a table:
INSERT INTO employees (employee_id, first_name, last_name) VALUES (1, 'Alice', 'Adams');
INSERT INTO employees (employee_id, first_name, last_name) VALUES (2, 'Bob', 'Brown');
INSERT INTO employees (employee_id, first_name, last_name) VALUES (3, 'Carol', 'Clark');
What happens with 100 concurrent users inserting at once? You can't have each one query MAX(employee_id) + 1 โ they'd all see the same value and collide. Sequences solve this by handing out numbers atomically, one at a time, with no two callers ever getting the same number.
Creating a Sequence
CREATE SEQUENCE emp_seq
START WITH 1000
INCREMENT BY 1
MINVALUE 1000
MAXVALUE 9999999
NOCACHE
NOCYCLE;
| Clause | Meaning | Default |
|---|---|---|
START WITH n |
First value | 1 |
INCREMENT BY n |
Step between values (negative allowed) | 1 |
MINVALUE n / NOMINVALUE |
Lower bound | 1 (or -10^27 for descending) |
MAXVALUE n / NOMAXVALUE |
Upper bound | 10^27 - 1 |
CACHE n / NOCACHE |
Pre-allocate n values in memory | CACHE 20 |
CYCLE / NOCYCLE |
Wrap around at MAXVALUE | NOCYCLE |
ORDER / NOORDER |
Guarantee strict order in RAC | NOORDER |
A minimal sequence is just:
CREATE SEQUENCE order_seq;
This creates a sequence starting at 1, incrementing by 1, no cycle, with a cache of 20.
Using NEXTVAL and CURRVAL
Two pseudocolumns interact with a sequence:
sequence_name.NEXTVALโ advances the sequence and returns the new valuesequence_name.CURRVALโ returns the most recent value retrieved by this session
-- Get the next value
SELECT emp_seq.NEXTVAL FROM dual;
Result: 1000
-- Inspect the current value (only valid AFTER you've called NEXTVAL in this session)
SELECT emp_seq.CURRVAL FROM dual;
Result: 1000
-- Each call to NEXTVAL increments:
SELECT emp_seq.NEXTVAL FROM dual; -- 1001
SELECT emp_seq.NEXTVAL FROM dual; -- 1002
CURRVAL is per-session. If session A has called NEXTVAL and session B has not, then session B calling emp_seq.CURRVAL raises ORA-08002: sequence is not yet defined in this session. Always call NEXTVAL at least once before CURRVAL.
Using a Sequence in INSERT
The typical pattern is to call NEXTVAL in the column list:
INSERT INTO employees (employee_id, first_name, last_name, email)
VALUES (emp_seq.NEXTVAL, 'Diane', 'Davis', 'diane@company.com');
You can also call NEXTVAL inside a SELECT-driven insert:
INSERT INTO new_employees (employee_id, first_name, last_name)
SELECT emp_seq.NEXTVAL, first_name, last_name
FROM raw_employee_data;
Sequences with a BEFORE INSERT Trigger (Legacy Pattern)
Before Oracle 12c, the canonical pattern was a trigger that auto-fills the PK:
CREATE OR REPLACE TRIGGER emp_bi
BEFORE INSERT ON employees
FOR EACH ROW
WHEN (NEW.employee_id IS NULL)
BEGIN
:NEW.employee_id := emp_seq.NEXTVAL;
END;
/
Now any INSERT that omits employee_id gets one assigned automatically:
INSERT INTO employees (first_name, last_name) VALUES ('Eve', 'Evans');
-- employee_id is automatically set to emp_seq.NEXTVAL
IDENTITY Columns (Oracle 12c+)
In Oracle 12c and later, you can skip the explicit sequence + trigger and use an IDENTITY column:
CREATE TABLE employees (
employee_id NUMBER GENERATED ALWAYS AS IDENTITY
(START WITH 1000 INCREMENT BY 1),
first_name VARCHAR2(50),
last_name VARCHAR2(50)
);
-- INSERT does not mention employee_id
INSERT INTO employees (first_name, last_name) VALUES ('Frank', 'Foster');
Three modes are available:
| Mode | Behaviour |
|---|---|
GENERATED ALWAYS AS IDENTITY |
Auto-generated; manual values are rejected |
GENERATED BY DEFAULT AS IDENTITY |
Auto-generated when omitted; manual values allowed |
GENERATED BY DEFAULT ON NULL AS IDENTITY |
Auto-generated when omitted or explicitly NULL |
Under the hood, Oracle still creates a sequence to back the IDENTITY column โ you can see it in USER_SEQUENCES with a system-generated name like ISEQ$$_12345.
Modifying and Dropping Sequences
-- Change increment, max, or cycle:
ALTER SEQUENCE emp_seq INCREMENT BY 10;
ALTER SEQUENCE emp_seq MAXVALUE 100000 CYCLE;
-- Reset? Oracle does NOT support "reset to N" directly.
-- You drop and recreate, or use a trick with negative increment:
ALTER SEQUENCE emp_seq INCREMENT BY -999 MINVALUE 0;
SELECT emp_seq.NEXTVAL FROM dual; -- consumes the negative step
ALTER SEQUENCE emp_seq INCREMENT BY 1;
-- Drop:
DROP SEQUENCE emp_seq;
CACHE vs NOCACHE โ The Trade-off
CACHE n tells Oracle to pre-allocate n numbers in memory. Subsequent NEXTVAL calls are very fast (no disk access) until the cache is exhausted.
Pros of CACHE:
- Much faster under high insert load
- Default
CACHE 20is fine for most cases; large bulk loads useCACHE 1000or more
Cons of CACHE:
- If the instance crashes, all cached but unused numbers are lost โ you'll see gaps
- Not suitable if you need a strict, gapless sequence (e.g., invoice numbers for tax compliance)
-- Strict gapless sequence โ slow but no gaps from cache loss
CREATE SEQUENCE invoice_seq NOCACHE ORDER;
Inspecting Sequences
SELECT sequence_name, min_value, max_value, increment_by, cache_size, last_number
FROM user_sequences;
Result:
| sequence_name | min_value | max_value | increment_by | cache_size | last_number |
|---|---|---|---|---|---|
| EMP_SEQ | 1000 | 9999999 | 1 | 20 | 1020 |
LAST_NUMBER is the next value to be issued after the cache (not the most recently issued one). Don't read it as "the current value".
Part 2 โ Synonyms
A synonym is an alias โ an alternative name โ for another database object: a table, view, sequence, procedure, or another synonym.
Why Use Synonyms?
Without synonyms, accessing another user's table requires the fully qualified name:
SELECT * FROM hr.employees;
Synonyms let you write the simpler form:
SELECT * FROM employees;
This matters for:
- Cross-schema access โ apps connecting as
app_usercan readhr.employeesas justemployees - Backwards compatibility โ rename a table and create a synonym to the new name so old code keeps working
- Abstraction โ point a synonym at a view or remote table without changing application code
- Database links โ synonyms hide that the object lives on a remote database
Private Synonyms
A private synonym is visible only to the user who created it. It is stored in the user's own schema.
-- I'm logged in as 'analyst'. I create a synonym for hr.employees:
CREATE SYNONYM emp FOR hr.employees;
-- Now I can query simply:
SELECT * FROM emp;
analyst.emp exists. Other users can't see it.
Public Synonyms
A public synonym is visible to every user in the database. It's not stored in any user's schema โ it lives in the special PUBLIC namespace.
-- Requires CREATE PUBLIC SYNONYM privilege (usually DBA only):
CREATE PUBLIC SYNONYM countries FOR hr.countries;
Every user can now write SELECT * FROM countries and get hr.countries, provided they have SELECT privilege on the underlying table.
GRANT SELECT ON hr.countries TO PUBLIC, the synonym is useless to other users.
Name Resolution Order
When Oracle sees an unqualified name like employees, it resolves in this order:
- A column of the FROM tables
- An object in the current user's schema
- A private synonym the user owns
- A public synonym visible to everyone
- Otherwise โ
ORA-00942: table or view does not exist
If analyst has both a local table employees and a public synonym employees, the local table wins.
CREATE OR REPLACE SYNONYM
You can update a synonym to point at a different object without dropping it:
CREATE OR REPLACE SYNONYM emp FOR hr.employees_v2;
This is useful during a deployment where you're rolling out a new version of a table or view: keep the synonym name stable, switch the target.
Dropping a Synonym
DROP SYNONYM emp;
DROP PUBLIC SYNONYM countries;
Dropping a synonym does not affect the underlying object โ only the alias is removed.
Synonyms for Sequences
A common pattern is a public synonym for an application-wide sequence:
-- DBA setup:
CREATE SEQUENCE app.global_id_seq START WITH 1;
GRANT SELECT ON app.global_id_seq TO PUBLIC;
CREATE PUBLIC SYNONYM global_id_seq FOR app.global_id_seq;
-- Any user:
INSERT INTO my_table (id, ...) VALUES (global_id_seq.NEXTVAL, ...);
Inspecting Synonyms
-- All synonyms I can see:
SELECT synonym_name, table_owner, table_name, db_link
FROM all_synonyms
WHERE synonym_name = 'EMP';
-- Only my private synonyms:
SELECT * FROM user_synonyms;
-- Only public synonyms:
SELECT * FROM dba_synonyms WHERE owner = 'PUBLIC';
TABLE_OWNER and TABLE_NAME describe the target โ they don't have to be a table; that column name is just historical.
When Synonyms Can Bite You
- Stale synonym โ the target table was dropped or renamed. The synonym remains and points at nothing. Queries fail with
ORA-00980: synonym translation is no longer valid. - Naming collisions โ a local table named the same as a public synonym silently hides the synonym. New developers may not realise they're querying a different object.
- Performance illusions โ a synonym pointing at a view that points at another view that points at a remote database can be very slow without the user realising why.
-- Detect stale synonyms:
SELECT s.synonym_name
FROM all_synonyms s
LEFT JOIN all_objects o
ON o.owner = s.table_owner
AND o.object_name = s.table_name
WHERE s.owner = USER
AND o.object_name IS NULL;
Sequences and Synonyms Together โ Worked Example
A clean schema pattern that many production apps use:
-- 1. Centralised app schema owns the sequence:
CREATE SEQUENCE app.customer_seq START WITH 1000 CACHE 1000;
-- 2. Grant access:
GRANT SELECT ON app.customer_seq TO web_user;
-- 3. Public synonym so the app uses a simple name:
CREATE PUBLIC SYNONYM customer_seq FOR app.customer_seq;
-- 4. Table with IDENTITY-style default referencing the synonym:
CREATE TABLE web_user.customers (
customer_id NUMBER DEFAULT customer_seq.NEXTVAL PRIMARY KEY,
name VARCHAR2(100),
created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
-- 5. Insert without ever naming the sequence:
INSERT INTO customers (name) VALUES ('Acme Corp');
DEFAULT seq.NEXTVAL on a column (Oracle 12c+) is the simplest way to attach a sequence to a column without a trigger.
Common Errors
| Error | Cause | Fix |
|---|---|---|
| ORA-02289: sequence does not exist | Wrong name, dropped, or not granted to your user | Check USER_SEQUENCES/ALL_SEQUENCES; ask DBA for grant if cross-schema |
| ORA-08002: sequence X.CURRVAL is not yet defined in this session | Called CURRVAL before any NEXTVAL in this session | Call NEXTVAL at least once first |
| ORA-04007: MINVALUE cannot be made to exceed the current value | Tried to ALTER MINVALUE above current | Drop and recreate, or adjust value with ALTER ... INCREMENT BY negative |
| ORA-08004: sequence X.NEXTVAL exceeds MAXVALUE and cannot be instantiated | Sequence is exhausted with NOCYCLE | Increase MAXVALUE or add CYCLE |
| ORA-00955: name is already used by an existing object | Synonym name conflicts with a table/view in the same schema | Choose a different name or drop the conflicting object |
| ORA-00980: synonym translation is no longer valid | The synonym's target was dropped or renamed | Recreate the synonym pointing at the new object, or drop it |
| ORA-01735: invalid ALTER TABLE option | Tried to ALTER TABLE on a synonym instead of the base table | ALTER the underlying table, not the synonym |
Interview Corner
โถ Show answer
No. Sequences guarantee uniqueness, not gaplessness. Three things cause gaps:
- CACHE loss on crash โ
CACHE 20reserves 20 numbers in memory; if the instance crashes, those unused numbers are gone. - Rolled-back transactions โ
NEXTVALis independent of transactions. If you call it then ROLLBACK, the number is still consumed. - RAC instances โ with
NOORDER, each RAC instance has its own cache; numbers are interleaved unpredictably.
If gapless is mandatory (invoice numbers in some tax jurisdictions, audit logs), use a counter table with locking:
CREATE TABLE invoice_counter (next_id NUMBER NOT NULL);
INSERT INTO invoice_counter VALUES (1);
-- Inside the transaction:
UPDATE invoice_counter SET next_id = next_id + 1
RETURNING next_id - 1 INTO :new_id;
This serialises inserts (a performance penalty) but guarantees no gaps because the counter is rolled back with the transaction.
โถ Show answer
IDENTITY columns (12c+) are sugar over a sequence โ same plumbing underneath. Use IDENTITY when:
- You want a single-table primary key
- You don't need to share the sequence across tables
- You're starting fresh (no legacy code expects an explicit sequence name)
Use an explicit sequence when:
- Multiple tables share one sequence โ common for "global ID" patterns
- You need a synonym for the sequence (you can't make a synonym for IDENTITY's hidden sequence)
- You need a specific name for the sequence (IDENTITY creates one like
ISEQ$$_12345) - You need
CURRVALfor a follow-on insert โ easier with an explicit sequence - You need to fetch a batch of IDs in advance (call NEXTVAL N times before inserting)
- You're on Oracle 11g or earlier โ no IDENTITY available
In practice: most new tables use IDENTITY; multi-table shared keys still use explicit sequences.
โถ Show answer
Several common causes:
The user has a SELECT privilege via a role, and they're running the query from PL/SQL. Roles do not grant privileges inside named PL/SQL blocks (definer rights). The fix is a direct grant:
GRANT SELECT ON hr.employees TO user_x;.A public synonym exists, but the user lacks SELECT on the underlying table. The synonym resolves, but the privilege check fails โ and Oracle returns the same
ORA-00942either way.The user has a local object with the same name that they don't realise exists, and they expected a synonym to be used. Local objects always win in name resolution.
Case sensitivity โ the table was created with quotes:
CREATE TABLE "Employees". It is only addressable as"Employees", notemployeesorEMPLOYEES.The user's default schema is wrong โ they connected as
analystand forgot the table is inhr. Either qualify the name (hr.employees) or create a synonym.
Debug query the user can run:
SELECT * FROM all_objects WHERE object_name = 'EMPLOYEES';
SELECT * FROM all_tab_privs WHERE table_name = 'EMPLOYEES' AND grantee = USER;
Related Topics
- DDL Commands โ
CREATE TABLEwithIDENTITYandDEFAULT seq.NEXTVAL - Constraints โ primary keys are the most common use of sequences
- DML Commands โ
INSERTwithseq.NEXTVAL - Views โ synonyms are often used in front of views for stability
- Performance โ CACHE size affects insert performance significantly