Using Dynamic SQL in CL: Part 1--Running Action Queries PDF Print E-mail
Programming - SQL
Written by Michael Sansoterra   
Sunday, 05 October 2003 18:00

Problems in life are not always simple. This is especially true in the computer programming realm. However, there is one shining exception to this rule, and it lies in the power of SQL's EXECUTE IMMEDIATE statement. This little gem will allow you to unlock untold treasures in your programming world by allowing you to execute dynamic SQL statements anywhere, including CL programs.

As Simple as They Come

EXECUTE IMMEDIATE is grandiose in its simplicity and power. It allows the programmer to run just about any SQL statement that can be executed in a dynamic SQL environment (except for the SELECT statement). I call these kinds of SQL statements "action" statements. Here are the SQL statements that EXECUTE IMMEDIATE can process:

  • CALL
  • DROP

What do you need to harness the power of this statement? Nothing, except the DB2 Query Manager and the SQL Development Kit product (5722ST1), a small high-level language (HLL) program with embedded SQL and an SQL statement to run. For those who don't have the SQL Development Kit, I've included here a save file containing the command and program objects shown in this article. Restoring the SAVF requires a minimum OS/400 level of V5R1.

Figure 1 shows an example of how EXECUTE IMMEDIATE can be used in an RPG program:

D Order_No        S             10I 0
D Inv_No          S             10I 0
D SQL             S           1024    Varying

C                   Eval      SQL='DELETE FROM Invoices '
C                   Select 
C                   When      Inv_No>*Zero 
C                   Eval      SQL=SQL+'WHERE Inv_No='+
C                                     %CHAR(Inv_No) 
C                   When      Order_No>*Zero 
C                   Eval      SQL=SQL+'WHERE Order_No='+
C                                     %CHAR(Order_No)
C                   EndSl


C                   If        SQLCOD<*Zero
 * Error Processing
C                   EndIf


Figure 1: Use EXECUTE IMMEDIATE in an RPG program.

The only parameter EXECUTE IMMEDIATE expects is a character variable containing an SQL statement (limited to those shown in the list above). In the program snippet in Figure 1, the SQL statement is built dynamically and then processed by EXECUTE IMMEDIATE. The character variable must contain a complete SQL statement without parameters; host variables and parameter markers are forbidden.

In embedded SQL programming, an SQLCOD variable (as shown in Figure 1) contains a return code with the status of the SQL statement:

  • A negative SQLCOD indicates that an error occurred with the statement (more on this later).
  • A positive SQLCOD indicates that the statement generated a warning message.
  • An SQLCOD of zero means that the statement ran without incident.

You don't have to define SQLCOD in your D-specs. The SQL precompiler will create this and other variables automatically inside a special data structure called the SQL communication area (SQLCA). For more information on the SQLCA and the information it provides, see Appendix B of the DB2 Universal Database for iSeries SQL Reference guide. For more information on SQLCODs, see the DB2 UDB for iSeries SQLCODEs and SQLSTATEs manual.

You may be wondering why you would use the slower EXECUTE IMMEDIATE statement in an RPG program when you can just use high-performing embedded SQL. Good question. Here are two reasons for using EXECUTE IMMEDIATE:

  • You can use EXECUTE IMMEDIATE when the statement's WHERE criteria is unknown at compile time. In the example above, the DELETE statement's WHERE clause is unknown until run time. Using EXECUTE IMMEDIATE saves the trouble of having to embed SQL statements for every possible condition.
  • You can use EXECUTE IMMEDIATE in an RPG program to allow non-SQL environments, such as a CL program, to run SQL statements.

The RUNSQL Utility

The RUNSQL utility leverages the power of EXECUTE IMMEDIATE to allow SQL statements to be run on a command line or in a CL program. The command accepts one parameter: an SQL statement. The source for the RPG program and command interfaces for this utility are included in this article's downloadable code.

Figure 2 shows excerpts from the code for RPG program RUNSQLR.

D parmSQL         S           5000    Varying

C     *Entry        PList 
C                   Parm                    parmSQL 
 * Execute statement passed from command line
C                   If        SQLCOD<*ZERO
C                   CallP(E)  SQLErrorMsg
C                   EndIf

Figure 2: Here are some snippets from RUNSQLR.

That's about all there is to the program. The program will receive an SQL statement from the command interface and attempt to run it. If the SQL statement fails to execute properly, a subprocedure is called that sends an *Escape message to the calling program. Due to the limitations of the OS/400 command interface, an SQL statement may be no longer than 5,000 characters.

Benefits of RUNSQL

By now, you may be questioning the usefulness of the utility. After all, can't we run SQL statements from a command line or CL by using either the Start Query Management Query (STRQMQRY) or the Run SQL Statement (RUNSQLSTM) command?

Yes, you can, but RUNSQL offers the following advantages over these methods:

  • RUNSQLSTM can't receive parameters or have its contents dynamically modified.
  • RUNSQLSTM has poor error-handling methods.
  • RUNSQLSTM doesn't allow conditional statements to control statement execution.
  • RUNSQLSTM doesn't allow a CL program to retrieve the number of rows affected.
  • Query Management queries can execute only one statement.
  • Query Management queries require the creation of objects external to the CL (in other words, you will have yet more objects on your system, and if you're trying to debug a problem in a CL job stream, you may have to move to the query manager environment just to look at an SQL statement being run).
  • Query Management queries don't report the rows affected by a given data modification statement.

This is not to say RUNSQL should be used all the time. Both RUNSQLSTM and QM queries have their place and advantages. For instance, if you have a single SQL statement that will be executed by several CL programs, it would be better to place the SQL statement in a QM query object than to duplicate the same statement in several places with RUNSQL.

Sample Usage

Here are some potential uses of the utility:

  • Replace read-only OPNQRYF statements. SQL is generally easier to use than Open Query File (OPNQRYF). Rather than build an OPNQRYF string to select data, simply construct an SQL statement. Note that RUNSQL will be useful in this situation only if your OPNQRYF is creating a read-only open data path (ODP).
  • Replace data extraction programs. Before SQL became popular, it was common to have RPG programs build "work files" for extracting and sorting data. These extraction programs can often easily be replaced. Don't ignore the fact that you can use a single SQL statement to replace many aging RPG programs that often run in CL job streams. For example, a few years ago, I was involved in a project to upgrade a client's homegrown software. This software featured many RPG programs that did nothing but loop through records to update a flag--a good candidate for replacement with SQL. I was able to eradicate many of these RPG programs by replacing them with a single SQL statement in the CL.
  • Do dynamic file processing in CL. CL programs often loop through a file to process data. If you have work that needs to be done on an existing file (records eliminated or updated or perhaps a view created), RUNSQL can perform these tasks; just issue the RUNSQL command before executing the first RCVF on the file. Keep in mind that CL programs can only process files that limit column data types to CHAR, NUMERIC, or DECIMAL and don't contain NULLs. Additionally, CL can normally just read files, but RUNSQL adds the ability to update files by issuing an UPDATE statement! Further, RUNSQL will allow you to create a work file based on multiple files by doing a join.
  • Process several successive SQL statements. For those without the SQL Dev Kit, this CL can do light processing work where an embedded SQL program or several successive QM queries might normally be used.

Figures 3 and 4 show excerpts from two sample CL programs that demonstrate RUNSQL.

/* Create Work File QTemp/RegionData +
NOTE: Prior to V5R2 you would have to execute a +
CREATE TABLE and an INSERT INTO statement */

  (SELECT ShipState, ShipCity, SUM(Quantity) AS TotQty -
     FROM Orders -
 GROUP BY ShipState, ShipCity -
 ORDER BY ShipState, ShipCity) -

       ''Sales Summary by Region'''

CALL RPTPGM /* Create Report from work file */

Figure 3: These statements can be used as a replacement for an OPNQRYF or as a file to be processed by an RPG program. If you use this technique to process a file in CL, the CREATE TABLE command needs to be executed before compiling.


/* Drop temp view in case it exists    */ 
MONMSG SQL0204                      /* Object not found */

/* Make view to calc elapsed days using today's date */ 
RUNSQL 'CREATE VIEW QTEMP/OPENAR AS (                       -
        SELECT CustomerID, Amount,                          -
               DAYS(CURRENT_DATE) - DAYS(TranDate) AS Days  -
          FROM ARTrans                                      -
         WHERE TranDate<=Current_Date)'                      
/* Prepare for transaction processing */

/* Clear Customer Master Balances     */
           SET (CurrentDue, Over30, Over60, Over90) = (0,0,0,0)'

/* Update Balances for unpaid trans   */ 
           SET (CurrentDue, Over30, Over60, Over90) =                 -
                   (SELECT SUM(CASE WHEN Days BETWEEN  0 AND 30       -
                                    THEN Amount ELSE 0 END) AS Cur,   -
                           SUM(CASE WHEN Days BETWEEN 31 AND 60       -
                                    THEN Amount ELSE 0 END) AS Past30,-
                           SUM(CASE WHEN Days BETWEEN 61 AND 90       -
                                    THEN Amount ELSE 0 END) AS Past60,-
                           SUM(CASE WHEN Days > 90                    -
                                    THEN Amount ELSE 0 END) AS Past90 -
                      FROM OpenAR                                     -
                     WHERE OpenAR.CustomerID=CustMast.CustomerID)     -
         WHERE EXISTS                                                 -
            (SELECT *                                                 -
               FROM OpenAR                                            -
              WHERE OpenAR.CustomerID=CustMast.CustomerID)'

/* Rollback Trans if error occured,   +
   otherwise, commit changes         */ 

Figure 4: This month-end job clears and recalculates AR balances on the customer master.

Unfortunately, the formatting of SQL statements inside of a CL program will not always be pretty.

Dynamically Build SQL Statements

As with OPNQRYF, a variable or expression can be used with RUNSQL. Let's look at how this can improve the example in Figure 4. Instead of running the SQL statements against the CURRENT_DATE register, the operator will pass the month-end date via a command (these dates will be formatted as a 7A in CYYMMDD format).


You can convert this work date to an ISO date with separators (10A in YYYY-MM-DD format), which can then be inserted in the SQL statement that controls the date calculation.

/* Convert command date CYYMMDD to ISO */

/* Calc elapsed days on user supplied month end date */ 
RUNSQL ('CREATE VIEW QTEMP/OPENAR AS (                       -
         SELECT CustomerID, Amount,                          - 
                DAYS('''||&MEDATE||''') - DAYS(TranDate) AS Days  -
           FROM ARTrans                                      -
          WHERE TranDate<='''||&MEDATE||''')')

As with other command parameters that can accept expressions, make sure to enclose the expression in parentheses, and watch out for the ominous single-quote snafus!

Determining the Number of Rows Affected

Sometimes, it may be necessary to determine how many rows were processed for a given data modification statement (DELETE, INSERT, UPDATE). To facilitate this need, a second command, called RUNSQLRA, is included in the downloadable code.

RUNSQLRA is the same as the RUNSQL command with the exception that it has a second parameter. This second parameter is a DEC(15,0) "return variable" parameter called ROWSAFFECT that will return the rows affected by an SQL statement into a CL variable. Figure 5 shows an example of how it might be used in a nightly processing program where, due to a holiday or some other circumstance, there is no data to be processed:

DCL &ROWSCHG *DEC (15 0)             

                 SET IN_PROCESS=''Y''   + 
               WHERE IN_PROCESS=''N''   + 
                 AND STATUS=''RDYTOINV''') +


/* Inform user there's nothing to do today... */

/* Perform nightly order processing */


Figure 5: RUNSQLRA's ROWSAFFECT parameter returns the rows affected by an SQL statement.

When the RPG program issues an EXECUTE IMMEDIATE data modification statement, such as an UPDATE, the number of rows changed by the statement are reported back through an array variable in the SQLCA called SQLErrD. Element three of the array holds the rows affected. The RPG program returns the contents of this variable to the command. For SQL statements that don't modify data, ROWSAFFECT will return a zero.

The RPG program handles returning the rows affected back to the CL program via a second program parameter called parmRowsAffect. The %PARMS built-in function is used to sense the number of parameter passed. If two parameters are passed to the program, it sets the parmRowsAffect variable as follows:

*Entry        PList 
              Parm                    parmSQL 
              Parm                    parmRowsAffect

If        %Parms>=2 
Eval      parmRowsAffect=SQLErrD(3)

When an RPG program is called, not all of the parms have to be passed. However, the RPG program can't reference a parm that hasn't been passed without causing an error, which is why parmRowsAffect is modified only on the condition that it was passed to the program.

Error Handling

As mentioned earlier, the SQLCOD variable will return a negative number in the event of an error condition. How can you make use of this negative number to communicate an intelligent message to the calling CL program? When an SQL statement fails, SQLCOD contains the message number of a specific error message found in the QSQLMSG message file. The only problem is that the number is negative. However, by ignoring the sign and prefixing the literal 'SQL', you can come up with the message ID of the error message as follows:

Eval      MsgID='SQL'+%Subst(%EditW(%Abs(
                SQLCOD):'0         '):7)

Now that you have the message identifier, the SQL communications area supplies you with the message data and message data length in the SQLERM and SQLERL variables. These three pieces of information allow you to successfully relay the SQL error as an *ESCAPE message to the calling program via the QMHSNDPM (Send Program Message) API.

Default Options

The RUNSQLR program has been set with several precompiler options:

  • Commit=*NONE--This option specifies that, by default, the SQL statements executed will not be under any form of commitment control. Use RUNSQL to issue the SET TRANSACTION ISOLATION LEVEL statement to override this setting.
  • Naming=*SYS--The naming convention controls how qualified table names are coded in an SQL statement. With the *SYS naming convention, the forward slash (/) is used to separate the library and table names. The alternative *SQL naming convention requires that a period (.) be used as the separator.
  • DynUsrPrf=*OWNER--This setting instructs SQL to run dynamic SQL statements under the program owner's authority rather than the user's authority. For example, if program RUNSQLR is owned by QPGMR, then all SQL statements will run under QPGMR's authority. Be careful who is allowed to run this command from the command line. For example, a user executing RUNSQL('CALL QCMD') owned by QPGMR will adopt authority to do anything on the command line that QPGMR can do. If this option is set to *USRPRF, then the SQL statements will run under the user's authority. Also, DynUsrPrf is not affected by the UsrPrf keyword, which controls whether the program itself is run under the user's or the owner's authority.
  • DATFMT and TIMFMT--The DATFMT and TIMFMT precompiler options are left defaulted to *JOB. Keep this in mind, because it affects how you specify date and time constants in your SQL statements.

Downloading the Utility

As mentioned earlier, the objects for this utility are available for download in a save file for the benefit of those who don't have the DB2 SQL Development Kit installed. (This product is required to compile the RUNSQLR embedded SQL program but not to run it.) The objects were saved with QPGMR ownership so take careful note of the security issues. Be sure to change the object ownership to a profile with the security restrictions necessary for your environment.

To restore the utility on your system, upload the save file to your system using FTP and execute the RSTOBJ command:


For help on using FTP to upload a save file to your iSeries, see FTP: Tricks of the Transfer.

Combining SQL and CL

Using RUNSQL to unleash the power and utility of SQL directly within a CL program will enhance your environment by...

  • Eliminating old RPG programs or unnecessary QM query objects
  • Allowing you to view the SQL statements directly within a CL program
  • Providing a way to dynamically build statements
  • Giving the ability to monitor for error messages
  • Allowing transaction-based updates to occur
  • Letting the program know how many rows were affected by a given statement

If you're familiar with SQL and do much CL programming, this utility is likely to achieve a prominent place in your CL programs. The best news is that we're not through. In Part 2 of this series, I'll cover another dynamic SQL statement that will allow a CL to dynamically construct and run a limited SELECT statement.

Last Updated on Monday, 20 September 2010 10:44
User Rating: / 5



See What i Can Do With Modern RPG Development

Your applications deserve more than just a new look! Change the perception of the IBM i as “old” with modern development tips from IBM and Profound Logic Software.
Watch as Barbara Morris, Brian May and Alex Roytman dive beneath the GUI surface to show the benefits of:


  • Working with free-format RPG in IBM i TR7
  • Breaking free from “The Cycle” of traditional RPG development
  • Going beyond the 10 character limit in display and database files
  • And more!

You'll also see a live demonstration of these techniques as the presenters create a modern web application before your eyes! 


Watch the Webinar Now!



MS Office Connector for Query/400

NGS' Qport Office enables Windows users to run IBM Query/400 queries to: 

 - Create and update Excel spreadsheets and Access databases

 - Create Word documents

 - Send to Windows screen and PC printers

No query conversion is required. Works with i5/OS V5R1 & above. Installs in minutes!

If you don’t have a budget to replace IBM Query/400, but want your users to have one click enhanced output of their queries.… Request the online license agreement and product download instructions today!

Offer good through December 31, 2013.