Best Practices for Inserting Data and Retrieving Generated Sequence IDs in Oracle Database

Dec 03, 2025 · Programming · 13 views · 7.8

Keywords: Oracle sequences | triggers | stored procedures | RETURNING INTO | multi-threaded safety

Abstract: This article provides an in-depth exploration of various methods for retrieving auto-generated sequence IDs after inserting data in Oracle databases. By comparing with SQL Server's SCOPE_IDENTITY mechanism, it analyzes the comprehensive application of sequences, triggers, stored procedures, and the RETURNING INTO clause in Oracle. The focus is on the best practice solution combining triggers and stored procedures, ensuring safe retrieval of correct sequence values in multi-threaded environments, with complete code examples and performance considerations provided.

Introduction

In database operations, immediately retrieving auto-generated identifiers after inserting data is a common requirement. SQL Server provides the SCOPE_IDENTITY() function for this purpose, but Oracle database mechanisms differ. This article systematically explores multiple methods for achieving similar functionality in Oracle, with emphasis on best practice solutions.

Oracle Sequence Fundamentals

Unlike SQL Server's identity columns, Oracle uses sequence objects to generate unique numeric values. Sequences are database objects independent of tables, accessible through NEXTVAL and CURRVAL pseudo-columns. Basic usage is as follows:

CREATE SEQUENCE batch_seq START WITH 1 INCREMENT BY 1;

Sequences can be directly referenced when inserting data:

INSERT INTO batch(batchid, batchname) VALUES(batch_seq.NEXTVAL, 'Test');

To retrieve the last generated sequence value in the current session, CURRVAL can be used:

SELECT batch_seq.CURRVAL FROM dual;

However, this approach carries risks in multi-threaded environments, as CURRVAL returns the value from the last NEXTVAL call in the current session, not necessarily the value generated by a specific insert operation.

RETURNING INTO Clause

Oracle provides the RETURNING INTO clause, allowing insertion of data and return of generated column values within a single SQL statement. This method most closely resembles SQL Server's SCOPE_IDENTITY() mechanism:

DECLARE
    l_batchid NUMBER;
BEGIN
    INSERT INTO batch(batchid, batchname) 
    VALUES(batch_seq.NEXTVAL, 'Test')
    RETURNING batchid INTO l_batchid;
    
    DBMS_OUTPUT.PUT_LINE('Generated ID: ' || l_batchid);
END;

The primary advantage of this method is atomicity—insertion and value retrieval occur within the same database operation, avoiding race conditions. However, it still requires explicit management of sequence values.

Integrated Trigger and Stored Procedure Solution

To provide an experience closer to auto-increment columns, triggers and stored procedures can be combined. This solution hides the complexity of sequence management while ensuring thread safety.

Creating Tables and Sequences

First, define the table structure and sequence:

CREATE TABLE batch (
    batchid NUMBER PRIMARY KEY,
    batchname VARCHAR2(30),
    batchtype CHAR(1),
    source CHAR(1),
    intarea NUMBER
);

CREATE SEQUENCE batch_seq START WITH 1 INCREMENT BY 1;

Creating BEFORE INSERT Trigger

The trigger automatically assigns sequence values to the batchid column before insertion:

CREATE OR REPLACE TRIGGER batch_bi
BEFORE INSERT ON batch
FOR EACH ROW
BEGIN
    SELECT batch_seq.NEXTVAL INTO :new.batchid FROM dual;
END;

This trigger ensures each insert receives a new sequence value without explicit specification in the INSERT statement.

Creating Stored Procedure

The stored procedure encapsulates insertion logic and returns the generated ID:

CREATE OR REPLACE PROCEDURE insert_batch(
    v_batchname IN batch.batchname%TYPE,
    v_batchtype IN batch.batchtype%TYPE,
    v_source IN batch.source%TYPE,
    v_intarea IN batch.intarea%TYPE,
    v_batchid OUT batch.batchid%TYPE
)
AS
BEGIN
    INSERT INTO batch(batchname, batchtype, source, intarea)
    VALUES(v_batchname, v_batchtype, v_source, v_intarea)
    RETURNING batchid INTO v_batchid;
END;

Advantages of the stored procedure approach include:

  1. Encapsulation of data access logic
  2. Direct return of generated ID through OUT parameter
  3. Maintenance of interface simplicity similar to raw SQL

Invocation Examples

Calling from a PL/SQL block:

DECLARE
    l_batchid batch.batchid%TYPE;
BEGIN
    insert_batch(
        v_batchname => 'Batch 1',
        v_batchtype => 'A',
        v_source => 'Z',
        v_intarea => 1,
        v_batchid => l_batchid
    );
    DBMS_OUTPUT.PUT_LINE('Generated ID: ' || l_batchid);
END;

Calling from SQL*Plus or similar tools:

VARIABLE l_batchid NUMBER;
EXEC insert_batch('Batch 21', 'C', 'X', 7, :l_batchid);
PRINT l_batchid;

Thread Safety in Multi-Threaded Environments

In concurrent execution environments, ensuring each session retrieves the correct sequence value is crucial. The described solution guarantees thread safety through these mechanisms:

  1. Sequence NEXTVAL ensures each call returns a unique value
  2. Triggers operate at row level, ensuring each row insertion receives an independent sequence value
  3. The RETURNING INTO clause returns values within the same context as the insert operation, preventing interference between sessions

This aligns with the consideration in SQL Server of avoiding &&IDENTITY in favor of SCOPE_IDENTITY().

Alternative Approach Comparison

Beyond the best practice, other viable approaches exist:

Direct Sequence Usage

The simplest approach uses sequences directly in INSERT statements:

INSERT INTO batch(batchid, batchname) VALUES(batch_seq.NEXTVAL, 'Test');
SELECT batch_seq.CURRVAL FROM dual;

This method requires two statements, and CURRVAL may be unreliable in concurrent environments.

RETURNING INTO Only

Without using triggers:

DECLARE
    l_seq_val NUMBER;
    l_batchid NUMBER;
BEGIN
    l_seq_val := batch_seq.NEXTVAL;
    INSERT INTO batch(batchid, batchname) 
    VALUES(l_seq_val, 'Test') 
    RETURNING batchid INTO l_batchid;
END;

This approach requires manual sequence value management, increasing code complexity.

Performance Considerations

While the trigger solution provides good encapsulation, it introduces additional overhead:

  1. Trigger execution adds overhead to each INSERT operation
  2. Stored procedure calls are slightly slower than direct SQL
  3. Sequence access requires cache maintenance and synchronization

In most applications, this overhead is negligible. For extremely high-concurrency scenarios, consider:

  1. Increasing sequence cache size to reduce contention
  2. Evaluating whether triggers are necessary
  3. Considering bulk operation optimizations

Cross-Platform Compatibility

For applications requiring support across multiple database platforms, maintaining a unified interface can be challenging. While the stored procedure solution provides a clear interface, it adds platform-specific code. Consider these strategies:

  1. Using data access layers to abstract database differences
  2. Maintaining separate SQL files for each platform
  3. Utilizing ORM tools to handle platform variations

Oracle 12c and Later Enhancements

Starting with Oracle 12c, identity column functionality was introduced, with syntax closer to other databases:

CREATE TABLE batch (
    batchid NUMBER GENERATED ALWAYS AS IDENTITY,
    batchname VARCHAR2(30)
);

No specification of identity column is needed during insertion:

INSERT INTO batch(batchname) VALUES('Test');

Retrieving generated IDs can still use the RETURNING INTO clause. Identity columns still use sequences internally but provide cleaner syntax.

Conclusion

For inserting data and retrieving generated sequence IDs in Oracle databases, the best practice combines triggers, sequences, and stored procedures. Triggers automatically assign sequence values, stored procedures encapsulate insertion logic and return generated IDs through OUT parameters, and the RETURNING INTO clause ensures operational atomicity. This solution provides excellent encapsulation, thread safety, and usability, making it a reliable choice for multi-threaded environments. For applications requiring cross-platform compatibility, database implementation differences can be managed through data access layers or configuration files.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.