Oracle Database
Sequences & Identity Columns
Every table in both schemas has a numeric primary key. So far those keys have been inserted manually — which works in examples but fails in real applications where two sessions inserting simultaneously would collide on the same value. Oracle solves this with sequences: independent database objects that generate a guaranteed unique number each time they are called, regardless of how many sessions are running concurrently. This lesson covers creating and using sequences, the NEXTVAL and CURRVAL pseudocolumns, and Oracle 12c's identity column syntax which wraps a sequence directly into a column definition.
To follow along with the examples in this lesson, use the course dataset from Lesson 8.
Creating a Sequence
A sequence is a schema object defined once and then called whenever a new unique number is needed. It has no direct relationship to any table — the same sequence could theoretically supply values to multiple tables, though in practice each table gets its own sequence for clarity. The key parameters are START WITH, INCREMENT BY, MINVALUE, MAXVALUE, and whether it cycles or caches values.
CACHE is one of the most important performance parameters. Oracle pre-allocates a block of sequence numbers in memory — by default 20. When a session requests NEXTVAL Oracle returns the next cached number instantly without a disk read. If the database restarts unexpectedly the cached numbers are lost and a gap appears in the sequence. Gaps are normal and expected — a sequence guarantees uniqueness, not consecutiveness.
-- Basic sequence for customers — starts at 1, increments by 1
CREATE SEQUENCE seq_customer_id
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9999999999
NOCYCLE -- when MAXVALUE is reached raise an error rather than wrapping back to MINVALUE
CACHE 20;
-- Sequence for orders — starts at 5001 to match the dataset
CREATE SEQUENCE seq_order_id
START WITH 5001
INCREMENT BY 1
MINVALUE 5001
MAXVALUE 9999999999
NOCYCLE
CACHE 20;
-- View sequence definitions in the data dictionary
SELECT sequence_name,
min_value,
max_value,
increment_by,
cycle_flag,
cache_size,
last_number -- next value Oracle will use when the cache is refreshed
FROM user_sequences
WHERE sequence_name IN ('SEQ_CUSTOMER_ID', 'SEQ_ORDER_ID')
ORDER BY sequence_name;Sequence SEQ_ORDER_ID created.
SEQUENCE_NAME MIN_VALUE MAX_VALUE INCREMENT_BY CYCLE_FLAG CACHE_SIZE LAST_NUMBER
──────────────── ───────── ────────── ──────────── ────────── ────────── ───────────
SEQ_CUSTOMER_ID 1 9999999999 1 N 20 1
SEQ_ORDER_ID 5001 9999999999 1 N 20 5001
last_numberinuser_sequencesshows the next value Oracle will allocate when the cache block is replenished — it jumps by the cache size each time, so withCACHE 20it increments by 20 after each cache refresh, not by 1NOCYCLEis the right choice for primary keys — if a sequence reaches its maximum and cycles back to 1 it would generate values that already exist as primary keys, causing constraint violations- Gaps in sequence values are normal — they occur on database restarts (cached values lost), rolled-back transactions (the
NEXTVALwas consumed and is not returned), and RAC environments; never design business logic that assumes sequence values are consecutive
Using NEXTVAL and CURRVAL
sequence_name.NEXTVAL increments the sequence and returns the new value. sequence_name.CURRVAL returns the value most recently generated by NEXTVAL in the current session — it does not increment the sequence again. CURRVAL raises an error if NEXTVAL has not been called at least once in the current session.
Both pseudocolumns can be used in SELECT statements (querying from DUAL), in INSERT statements, and in UPDATE statements. They cannot be used in WHERE clauses, GROUP BY, ORDER BY, or subqueries inside SELECT.
-- Call NEXTVAL to get the next sequence value
SELECT seq_customer_id.NEXTVAL FROM DUAL;
-- CURRVAL returns the last value generated in this session — does not increment
SELECT seq_customer_id.CURRVAL FROM DUAL;
-- Use NEXTVAL directly inside an INSERT statement
INSERT INTO customers (customer_id, full_name, email, country, loyalty_tier)
VALUES (seq_customer_id.NEXTVAL, 'Elena Vasquez', 'elena@example.com', 'Spain', 'standard');
-- Insert a related order using CURRVAL to get the customer_id just generated
-- and NEXTVAL on the order sequence for the order_id
INSERT INTO orders (order_id, customer_id, status, total_amount)
VALUES (seq_order_id.NEXTVAL, seq_customer_id.CURRVAL, 'pending', 0);
-- Confirm both rows were created with the correct IDs
SELECT c.customer_id, c.full_name, o.order_id, o.status
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
WHERE c.email = 'elena@example.com';NEXTVAL
───────
1
-- CURRVAL:
CURRVAL
───────
1
-- INSERT customer: 1 row inserted.
-- INSERT order: 1 row inserted.
CUSTOMER_ID FULL_NAME ORDER_ID STATUS
─────────── ───────────── ──────── ───────
2 Elena Vasquez 5001 pending
CURRVALis session-scoped — two concurrent sessions each callingNEXTVALreceive different values, and each session'sCURRVALreflects only its own lastNEXTVALcall; this makes it safe to useCURRVALin multi-user environments- The customer_id in the output is 2 because
NEXTVALwas called once in the SELECT above before the INSERT — this is a common source of confusion; in production always callNEXTVALonly inside the INSERT itself NEXTVALfromDUALis used in examples and testing — in application code the sequence is typically called directly inside the INSERT without a separate SELECT
Creating Sequences for Both Schemas
Each table with a numeric primary key gets its own sequence. The naming convention used throughout the course is seq_columnname — making it unambiguous which sequence feeds which column.
-- DataplexaStore sequences
CREATE SEQUENCE seq_customer_id START WITH 1 INCREMENT BY 1 NOCYCLE CACHE 20;
CREATE SEQUENCE seq_product_id START WITH 1 INCREMENT BY 1 NOCYCLE CACHE 20;
CREATE SEQUENCE seq_order_id START WITH 5001 INCREMENT BY 1 NOCYCLE CACHE 20;
CREATE SEQUENCE seq_order_item_id START WITH 1 INCREMENT BY 1 NOCYCLE CACHE 20;
CREATE SEQUENCE seq_payment_id START WITH 1 INCREMENT BY 1 NOCYCLE CACHE 20;
-- DataplexaHR sequences
CREATE SEQUENCE seq_department_id START WITH 10 INCREMENT BY 10 NOCYCLE CACHE 20;
CREATE SEQUENCE seq_employee_id START WITH 100 INCREMENT BY 1 NOCYCLE CACHE 20;
CREATE SEQUENCE seq_salary_id START WITH 1 INCREMENT BY 1 NOCYCLE CACHE 20;
CREATE SEQUENCE seq_payroll_id START WITH 1 INCREMENT BY 1 NOCYCLE CACHE 20;
-- jobs uses a VARCHAR2 primary key (e.g. 'IT_PROG') — no sequence needed
-- Verify all sequences were created
SELECT sequence_name, min_value, increment_by, cache_size
FROM user_sequences
ORDER BY sequence_name;────────────────── ───────── ──────────── ──────────
SEQ_CUSTOMER_ID 1 1 20
SEQ_DEPARTMENT_ID 10 10 20
SEQ_EMPLOYEE_ID 100 1 20
SEQ_ORDER_ID 5001 1 20
SEQ_ORDER_ITEM_ID 1 1 20
SEQ_PAYMENT_ID 1 1 20
SEQ_PAYROLL_ID 1 1 20
SEQ_SALARY_ID 1 1 20
seq_department_idincrements by 10 — departments are a small, slowly growing list and gaps of 10 between IDs leave room to insert a department between two existing ones in reporting if neededjobshas aVARCHAR2primary key — sequences only generate numbers, so a readable code likeIT_PROGis assigned manually and a sequence would add no value- Sequence start values should reflect the data already in the table — if the dataset already contains customers 1 through 12, the sequence must start at 13 or higher to avoid primary key collisions on the first insert
Identity Columns (Oracle 12c and Later)
Oracle 12c introduced identity columns — a way to attach auto-increment behaviour directly to a column definition without creating a separate sequence object. Oracle creates and manages the underlying sequence automatically. Identity columns come in three variants: GENERATED ALWAYS (Oracle always generates the value — you cannot supply your own), GENERATED BY DEFAULT (Oracle generates a value if you do not provide one), and GENERATED BY DEFAULT ON NULL (Oracle generates a value if you provide NULL).
-- Identity column with GENERATED ALWAYS
-- Oracle generates the ID on every insert — supplying a value raises ORA-32795
CREATE TABLE product_reviews (
review_id NUMBER GENERATED ALWAYS AS IDENTITY,
product_id NUMBER(10) NOT NULL,
rating NUMBER(2,1) NOT NULL,
review_text VARCHAR2(500),
review_date DATE DEFAULT SYSDATE NOT NULL,
CONSTRAINT pk_product_reviews PRIMARY KEY (review_id)
);
-- Insert without specifying review_id — Oracle fills it automatically
INSERT INTO product_reviews (product_id, rating, review_text)
VALUES (1, 4.5, 'Great headphones, very comfortable.');
INSERT INTO product_reviews (product_id, rating, review_text)
VALUES (1, 3.0, 'Good but battery life could be better.');
SELECT review_id, product_id, rating FROM product_reviews;
-- Identity column with GENERATED BY DEFAULT — allows supplying your own value
-- Useful during data migrations where you need to preserve existing IDs
CREATE TABLE product_reviews_v2 (
review_id NUMBER GENERATED BY DEFAULT AS IDENTITY START WITH 100,
product_id NUMBER(10) NOT NULL,
rating NUMBER(2,1) NOT NULL,
CONSTRAINT pk_reviews_v2 PRIMARY KEY (review_id)
);
-- Explicit ID supplied — Oracle accepts it when GENERATED BY DEFAULT is used
INSERT INTO product_reviews_v2 (review_id, product_id, rating) VALUES (999, 2, 5.0);
-- No ID supplied — Oracle uses the sequence
INSERT INTO product_reviews_v2 (product_id, rating) VALUES (3, 4.0);-- Inserts: 2 rows inserted.
REVIEW_ID PRODUCT_ID RATING
───────── ────────── ──────
1 1 4.5
2 1 3.0
Table PRODUCT_REVIEWS_V2 created.
-- Explicit ID insert: 1 row inserted.
-- Sequence insert: 1 row inserted.
GENERATED ALWAYSis the safest choice for new tables — it makes the primary key completely hands-off and prevents accidental manual ID insertion that could clash with the sequenceGENERATED BY DEFAULTis useful during migrations where existing IDs need to be preserved alongside new auto-generated ones — use it only when you have a specific reason to supply values manually- The underlying sequence Oracle creates for an identity column can be inspected in
user_sequences— it appears with a system-generated name likeISEQ$$_12345
Altering and Dropping Sequences
Sequences can be modified after creation with ALTER SEQUENCE. The only parameter that cannot be changed is START WITH — to reset a sequence to a different starting point you must drop and recreate it. All other parameters including INCREMENT BY, MAXVALUE, CACHE, and CYCLE can be altered.
-- Increase the cache size for a high-volume sequence
ALTER SEQUENCE seq_order_item_id CACHE 100;
-- Change increment — useful for resetting a sequence to jump past existing values
-- This advances the sequence by 1000 on the next NEXTVAL call
ALTER SEQUENCE seq_order_id INCREMENT BY 1000;
SELECT seq_order_id.NEXTVAL FROM DUAL; -- returns 6001 if current value was 5001
ALTER SEQUENCE seq_order_id INCREMENT BY 1; -- reset increment back to 1
-- Drop a sequence permanently
DROP SEQUENCE seq_payment_id;
-- Recreate with a corrected start value
CREATE SEQUENCE seq_payment_id
START WITH 100
INCREMENT BY 1
NOCYCLE
CACHE 20;-- ALTER INCREMENT: Sequence SEQ_ORDER_ID altered.
-- NEXTVAL: 6001
-- ALTER INCREMENT reset: Sequence SEQ_ORDER_ID altered.
-- DROP SEQUENCE: Sequence SEQ_PAYMENT_ID dropped.
-- CREATE SEQUENCE: Sequence SEQ_PAYMENT_ID created.
- The increment jump trick — temporarily setting
INCREMENT BYto a large number, callingNEXTVALonce, then resetting — is the standard way to advance a sequence past a gap without dropping and recreating it - Dropping a sequence does not affect any table data — rows that already have values generated by the sequence keep those values; only future
NEXTVALcalls are affected - If a sequence is dropped that backs an identity column, Oracle raises an error — identity column sequences are managed by Oracle and cannot be dropped independently
Summary
| Concept | Syntax | Key Point |
|---|---|---|
| CREATE SEQUENCE | CREATE SEQUENCE name START WITH n | Independent schema object — not tied to a table |
| NEXTVAL | seq_name.NEXTVAL | Increments and returns next value — each call is permanent |
| CURRVAL | seq_name.CURRVAL | Returns last NEXTVAL in this session — does not increment |
| NOCYCLE | NOCYCLE in CREATE SEQUENCE | Raises error at MAXVALUE — always use for primary keys |
| CACHE | CACHE n in CREATE SEQUENCE | Pre-allocates n values in memory — gaps appear on restart |
| GENERATED ALWAYS | col NUMBER GENERATED ALWAYS AS IDENTITY | Oracle always generates the value — manual insert raises ORA-32795 |
| GENERATED BY DEFAULT | col NUMBER GENERATED BY DEFAULT AS IDENTITY | Oracle generates only when no value is supplied |
| ALTER SEQUENCE | ALTER SEQUENCE name parameter value | Cannot change START WITH — drop and recreate to reset |
| user_sequences | SELECT * FROM user_sequences | Shows all sequences — last_number reflects cache block, not exact next value |
Practice Questions
Practice 1. Why do sequences produce gaps, and why is this acceptable for primary keys?
Practice 2. What happens if you call CURRVAL before calling NEXTVAL in a new session?
Practice 3. What is the difference between GENERATED ALWAYS and GENERATED BY DEFAULT on an identity column?
Practice 4. You need to advance seq_order_id from its current value of 5050 to 6000 without dropping and recreating it. How would you do this?
Practice 5. The jobs table uses VARCHAR2(20) as its primary key rather than a NUMBER sequence. When is this appropriate?
Quiz
Quiz 1. What does NOCYCLE do in a sequence definition?
Quiz 2. Which pseudocolumn returns the last sequence value generated in the current session without incrementing the sequence?
Quiz 3. A sequence is defined with CACHE 20. The database restarts unexpectedly when the sequence is at value 45. What value does the next NEXTVAL call return?
Quiz 4. Which identity column variant should you use for a new transactional table where primary keys must always be system-generated?
Quiz 5. Which parameter in CREATE SEQUENCE cannot be changed with ALTER SEQUENCE?
Next up — Inserting Data — Learn how to add rows to the DataplexaStore and DataplexaHR tables using single-row inserts, multi-row inserts, and INSERT INTO SELECT.