Create an IBM i RPGLE Stored Procedure

Create a Basic IBM i RPGLE Stored Procedure

Want to reuse a commonly used RPGLE Procedure? Learn how to create stored procedures written in a native IBM i language - RPGLE. Save time. Run CALL procedure_name instead of having multiple program calls in your application, web interfaces and SQL Scripts.

How I Create an IBM i RPGLE Stored Procedure

Welcome to a quick lesson on building an IBM i RPGLE stored procedure, this time using modern RPG code with native DB2 IO access.

Step 1: Plan Your Procedure

Let's start by outlining inputs, outputs, and result sets.

Just like our SQL example, we take an employee number as input and return a name and a city. If the employee hides, we handle that gracefully.

Things to remember for Native File IO in RPGLE


Referencing long file names in RPGLE (especially those created via SQL with names longer than 10 characters) requires a few extra steps, since traditional DDS and RPG fixed-format declarations assume short names. Here's how to handle it:

In your F-spec or DCL-F declaration, we will use the actual 10char-named file on the system. In this example, we will use EMPLO00001 as the internal name used in our RPG code, even though 'employee_master' is the actual sql file name on the system.

We created this table with a binary key. Now, reading a DB2 file with a BIN(9) key in RPGLE is totally doable, you just need to be precise with your data types and file declarations. Here's how to handle it step-by-step:

What Is BIN(9)?

  • BIN(9) is a binary field with a length of 9 digits.

  • Internally, it's stored as a 4-byte signed integer (same as INT(10)).

  • You’ll need to match this with an RPGLE variable of type INT(10) or ZONED(9) depending on how you want to handle it.

Step 2: Write the RPGLE Code

This RPGLE (Report Program Generator Language Extended) code demonstrates a simple stored procedure on IBM i (AS/400) systems. It's written in free-format RPG, which removes fixed-column constraints for better readability. The program acts as a database stored procedure to fetch employee details (name and city) from a file based on an input employee ID. If no match is found, it returns default messages.

This SQLRPGLE program is a clean, modular example of an IBM i stored procedure written in free-format RPG that retrieves employee details based on a given employee ID.

Think of this as a beginner-friendly example of using native I/O (input/output) in RPG to interact with physical files, similar to querying a database table but without SQL. 

For this example, we are reading all the rows in the file to find the one that matches the employee id - it's inefficient but good for this example! In real life you would use a keyed access path, a logical file, to chain the file.

**free
//
// program name: storeprcr-stored_procedure_rpg_employee.pgm.sqlrpgle
// description: simple example of an rpg stored procedure to retrieve
// employee details based on employee id.
//
// modification history:
// v.000 2025.10.14 njl created for online example
//

ctl-opt
  main(mainline)
  optimize(*full)
  option(*nodebugio:*srcstmt:*nounref)
  pgminfo(*pcml:*module)
  actgrp(*new)
  indent('| ')
  alwnull(*usrctl)
  copyright('STOREPRCR | V.000 | Stored Procedure using native IO RPG');


Dcl-Proc mainline;
 Dcl-Pi mainline;
  emp_id_in int(10) const;
  emp_name_out char(50);
  city_out char(30);
 end-pi;

 dcl-f EMPLO00001 usage(*input) extfile('NICKLITTEN/EMPLO00001') rename(EMPLO00001:EMPLREC) usropn;

 dcl-ds ds_employee_master likerec(EMPLREC:*INPUT) inz;

 dcl-s employeeFound ind;

 open EMPLO00001;

 read EMPLO00001 ds_employee_master;
 dow not %eof(EMPLO00001);
  if emp_id_in = ds_employee_master.emp_id;
   employeeFound = *on;
   leave;
  endif;
  read EMPLO00001 ds_employee_master;
 enddo;

if employeeFound;
 emp_name_out = ds_employee_master.emp_name;
 city_out = ds_employee_master.city;
else;
 emp_name_out = 'no employee found';
 city_out = 'n/a';
endif;

close EMPLO00001;

return;

end-proc;

Header and Control Options:

  • **free: Enables free-format mode.
  • Comments describe the program, purpose, and history (e.g., created in 2025).
  • ctl-opt: Sets program-wide options:
    • main(mainline): Defines the entry point procedure.
    • optimize(*full): Optimizes for performance.
    • option(*nodebugio:*srcstmt:*nounref): Controls debugging and unused variable handling.
    • pgminfo(*pcml:*module): Generates PCML (Program Call Markup Language) for calling as a stored procedure.
    • actgrp(*new): Runs in a new activation group for isolation.
    • indent('| '): Sets indentation for generated listings.
    • alwnull(*usrctl): Allows user control over null values.
    • copyright(...): Adds a copyright notice.

    These options configure how the program compiles and runs, emphasizing it's designed as a callable module.

Procedure Definition:

  • Dcl-Proc mainline;: Declares the main procedure.
  • Dcl-Pi mainline;: Procedure interface (like parameters in other languages):
    • emp_id_in int(10) const;: Input parameter (constant integer for employee ID).
    • emp_name_out varchar(50); and city_out varchar(30);: Output parameters (variable-length strings).

This setup allows the procedure to be called like: CALL PROCEDURE_NAME(123, ?, ?)

where ? are output placeholders.

File and Data Declarations:

  •  dcl-f EMPLO00001 usage(*input) rename(EMPLO00001:EMPLREC) usropn;: Declares a physical file (like a table) for input only. usropn means the program controls opening/closing. Renames the record format to EMPLREC.
  • dcl-ds employee_master likerec(EMPLREC:*INPUT);: Defines a data structure matching the file's record layout for input fields (e.g., emp_id, emp_name, city).
  • dcl-s success ind inz(*off);: An indicator (boolean-like) initialized to false.

Files in RPG are treated like datasets; this one assumes EMPLO00001 contains employee records.

Main Logic:

  •  open EMPLO00001;: Opens the file for reading.
  • dou %eof();: "Do Until End of File" loop.
    • read EMPLO00001 employee_master;: Reads the next record into the data structure.
    • if %found() and emp_id_in = employee_master.emp_id;: Checks if a record was read and if IDs match.
      • Sets success = *on; and leave; the loop if matched.
  • After loop: If success, copies matched data to outputs; else, sets error messages.
  • close EMPLO00001;: Closes the file.
  • return;: Ends the procedure, passing outputs back.

Best Practices

RPG I/O Basics: RPG excels at file handling. Operations like open, read, close mimic database cursors. Built-ins like %eof() (end of file) and %found() (record found) simplify checks.

Stored Procedures in RPG: By using pgminfo(*pcml), this can be registered as an SQL stored procedure (e.g., via CREATE PROCEDURE). It's great for encapsulating business logic.

Error Handling: Basic here (just a success flag and defaults). Add exceptions or SQLSTATE for production.

Improvements: For efficiency, add keys to the file or switch to embedded SQL (e.g., EXEC SQL SELECT ... INTO ...)

Step 3: Compile the Code

We should compile this - Run CRTBNDRPG

If errors pop up, we fix them before blaming the copy/paste, me or your keyboard. 

So, now we have an RPGLE program that can be called from anywhere. It will consume that single parameter of employee id number and return the name and city of the employee, if found. The obvious question is - how do we register this program as a stored procedure?

Step 4: Register in SQL

Now we need to use SQL, and the CREATE PROCEDURE statement to add our stored procedure and to make it official.

NOTE I am qualifying the schema name (NICKLITTEN) against the long STORED PROCEDURE NAME (GET_EMPLOYEES_SQL) and the RPGLE Program name (STOREPRCS):

create or replace procedure NICKLITTEN.GET_EMPLOYEES_RPG (
        in EMP_ID_IN int,
        out EMP_NAME_OUT char(50),
        out CITY_OUT char(30)
      )
    language RPGLE
    parameter style general
    external name 'NICKLITTEN/STOREPRCR'
    not deterministic
    ;
create RPGLE Stored Procedure Succesfull

Step 5: Call the Procedure

From SQL, we run CALL NICKLITTEN.GET_EMPLOYEES_RPG ( 12345 , ? , ? ) 

The ? grabs the output like a catcher’s mitt.

Let's test this using a similar script, we generated for the testing lesson previously. This is a little more friendly - if you want to test with different employee number simply change the value of the V_EMP variable.

Remember to use our new procedure name: GET_EMPLOYEES_RPG

-- Declare variables to hold the OUT parameters
declare global temporary table SESSION.RESULTS (
      NAME char(50),
      CITY char(30)
    )
  with REPLACE;

-- Declare host variables (ACS supports SET or VALUES INTO)
begin
  declare V_EMP numeric(10default 1;
  declare V_NAME char(50default ' ';
  declare V_CITY char(30default ' ';
  -- Call the procedure NOTE I AM SELECTING EMPLOYEE(1)
  call NICKLITTEN.GET_EMPLOYEES_RPG(
    V_EMP,
    V_NAME,
    V_CITY
  );
  -- Insert results into temp table to view them
  insert into SESSION.RESULTS
    values
      (
        V_NAME,
        V_CITY
      );
end;

-- Now view the results
select *
  from SESSION.RESULTS;

If I paste this into my IBM i ACS screen, I see this:

SQLRPGLE Stored Proc Results

Excellent!

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
>