Guild Companies, Inc.  
 
Midrange Programmer - How-To Advice & Free Code
OS/400 Edition
Volume 1, Number 2 - January 31, 2002

Coding SQL Functions in OS/400 V5R1

by Howard F. Arner Jr.

Along with all of the other cool things in OS/400 V5R1 comes a lovely thing called the Persistent Stored Modules (PSM) language, which you can use to develop stored procedures, triggers, and functions. This is the language that was introduced in V4 as the stored procedure language on the AS/400. But many people do not know that it's available for writing both triggers and user-defined functions. In this article, I'll explain what PSM is and show you how to write a basic user-defined function.

BCD
The advantage of the PSM language is that it is rooted in the ISO/ANSO SQL standard and is similar to the stored procedure languages offered by other RDBMS systems. So if you learn PSM on the AS/400, you will be familiar with PSM language implementations on other database systems. You will find it a useful and simple language for implementing procedures, user-defined function, and triggers on your AS/400. To use PSM, you need V5R1 and the SQL Development Kit installed on your AS/400. The version of the C compiler that is needed to create PSM programs is included on the AS/400 in V5. With that said, let's take a look at how to use PSM to create a user-defined function.

A while back, a reader sent me a query on how to convert date information from his legacy package into SQL date data types. A date was stored in his system as four DECIMAL(2,0) fields in a physical file. The fields correspond to the month, day, century, and year of the date. The century field contains a 19 for the 1900s and a 20 for years 2000 and above. The year fields contain a number representing the year, like 1 for 2001 or 99 for 1999. So the following SQL statement would work to convert these fields into an SQL date (assuming that the fields are named MO, DY, CEN, and YR):

SELECT DATE( TRIM(CHAR(MO))||'/'|| 
    TRIM(CHAR(DY))||'/'|| 
    TRIM(CHAR((CEN*100)+YR))) AS MYDATE 
FROM 

By using this simple formula, you could convert the legacy fields into a DB2 DATE data type and then be able to use all of the built-in date functions, like DAYOFWEEK and WEEK. Another advantage of the DATE data type is that there is native support for date math built into SQL, so you could use the resulting data in expressions to calculate dates or time lapsed since the event in the record.

Now, the problem with the SQL statement is that it is boring to type and looks very confusing. Also, you might use it a lot in your SQL statements, as you may need to convert several legacy fields and then compare them. One solution is to make a user-defined function that converts legacy dates into SQL dates, which we will now proceed to do.

Here is a first-run coding of the function. Make sure you change SQLTHING in the SQL to a library on your system:

CREATE FUNCTION SQLTHING.MINCRON_DATE
	(CEN DECIMAL(2,0),
	 YR DECIMAL(2,0),
	 MO DECIMAL(2,0),
	 DY DECIMAL(2,0))
	 RETURNS DATE
	 LANGUAGE SQL
	 BEGIN
	 DECLARE F_OUTPUT DATE;
	 SET F_OUTPUT = 
DATE(TRIM(CHAR(MO))||'/'||TRIM(CHAR(DY))||'/'||TRIM(CHAR((CEN*100)+YR)));
	 RETURN F_OUTPUT;
	 END

To create the function, you can use Operations Navigator Run SQL Scripts, the interactive SQL interpreter on the AS/400 (STRSQL), or my product, SQLThing (available from www.sqlthing.com). To use the Run SQL Scripts function from OpsNav, you'll need V5R1 of Client Access Express installed on your PC. Right-click on the OpsNav tree item Databases and select Run SQL Scripts. Once you've chosen a method of running your script, you simply log on to the AS/400, execute the above SQL, and--viola!--you have a user-defined function on your AS/400 (assuming that you have V5R1 and the SQL Development Kit installed on your AS/400).

Anatomy of the Function

Now let's examine each line of the above function to see what the PSM language is doing.

First, the function starts with the CREATE FUNCTION x keyword. This lets SQL know that you are about to create a user-defined function called x. In our case, x is a function called MINCRON_DATE, which lives in the library SQLTHING. Note that user-defined function names can be quite long; if they are over 10 characters, the AS/400 will generate a system name for the created program consisting of only 10 characters.

The next line starts with an open parenthesis, which indicates the start of the arguments to this function. When naming arguments to the function, you specify the argument name and data type. (In this function, I have four arguments, called CEN, YR, MO, and DY.) You then specify the data type for each. The close parenthesis, after the declaration of the DY argument, indicates the end of my argument declarations.

The RETURN DATE line indicates that the function will be returning a value of the data type DATE. Any SQL data type--or user-defined data type, for that matter--may be used for the return of data to the calling program. In this case, I want the program to calculate the date represented by the legacy fields and return an SQL date, so I specified DATE as the returned data type.

Language SQL indicates that this will be an SQL function. As you may know, you can create functions in any supported language on the AS/400--such as C, RPG, or COBOL--and you can also create sourced functions. A sourced function is a function that is built upon an existing function already defined in DB2. You might use this feature to do something like redefining the DAYOFWEEK function to return character representations of the day of the week, instead of numbers. (I'll get to that in another article!)

Finally, we get to the last keyword before the guts of the function, BEGIN. BEGIN lets the SQL interpreter know that code for the user-defined function lies from this point on.

The next line of the function declares a variable called F_OUTPUT, of the data type DATE. I will be using this variable to return the calculated date value to the calling program. Note that the statement ends in a semicolon. The semicolon lets the SQL interpreter know that it has reached the end of a statement. In this manner, I could split complex statements between multiple lines of code, if I were so inclined. Each statement must be ended in a semicolon.

The next line of the function uses the SET statement to place the value of the date conversion expression into the F_OUTPUT variable.

The last line of the function (before the END statement) indicates that the function should return the value of F_OUTPUT.

Testing the Function

The function is quite useful, as long as the stored data indicates a valid date. Let's create a table and see what happens when the function is executed. Execute the following SQL statements to create the test table and add test data:

CREATE TABLE sqlthing.testdata
	(cen DECIMAL(2,0),
	 yr DECIMAL(2,0),
	 mo DECIMAL(2,0),
	 da DECIMAL(2,0),
	 good CHAR(1));
	 	
INSERT INTO sqlthing.testdata VALUES (20,1,1,1,'Y');
INSERT INTO sqlthing.testdata VALUES (20,1,1,10,'Y');

Next, execute the following statement to ensure that you have the SQL FUNCTION PATH set correctly. The SQL FUNCTION PATH is used by SQL to find user-defined functions. If you do not set the path, you will have to qualify your user-defined functions with the name of the library where they exist.

 
SET CURRENT FUNCTION PATH=SQLTHING;  

The above statement tells the SQL interpreter to look in the SQLTHING library for any user-defined functions used in an SQL statement. Now execute the following SQL statement:

SELECT MINCRON_DATE(cen,yr,mo,da),good FROM sqlthing.testdata;

This statement should yield two rows, which contain a date, the combination of the legacy fields from above, and the good flag. Next, let's add another record and see what happens to the user-defined function. Execute the following SQL statement:

INSERT INTO sqlthing.testdata VALUES (20,1,20,1,'N');

Note that 20 in the MO column is going to result in an invalid date. Try selecting the data from the table with the previous SQL Select statement and notice that SQL does not return any results. This is because of the invalid date now contained in the TESTDATA table. If you were to look at the job log (of the job that executed the select statement), you would find that an SQL System Error has occurred, which is why no data was returned. To get around the error, I need to add some exception-handling abilities to the user-defined function.

Adding Error-Handling

Error-handling can be accomplished in many ways using the PSM language. Probably the easiest is to declare a HANDLER for the error you are attempting to trap. A handler is a routine or piece of code that is invoked when a condition is met. The condition can be any SQL State that you may wish to trap. The following rewrite of the procedure has an added error handler, which allows the procedure to return a null value when a legacy date would result in an invalid date:

CREATE FUNCTION SQLTHING.MINCRON_DATE
	(CEN DECIMAL(2,0),
	 YR DECIMAL(2,0),
	 MO DECIMAL(2,0),
	 DY DECIMAL(2,0))
	 RETURNS DATE
	 LANGUAGE SQL
	 BEGIN
	 DECLARE F_OUTPUT DATE;
	 DECLARE EXIT HANDLER FOR SQLEXCEPTION
	 	 RETURN NULL;
	 SET F_OUTPUT = DATE( TRIM(CHAR(MO))||'/'|| TRIM(CHAR(DY))||'/'||      
TRIM(CHAR((CEN*100)+YR)));
     RETURN F_OUTPUT;
	 END

Note that the main difference is the addition of the DECLARE EXIT HANDLER line. DECLARE EXIT HANDLER tells SQL that you are declaring that a handler should be handled as an EXIT handler. Handlers can be tagged as CONTINUE, EXIT, or UNDO. An UNDO handler will cause a rollback of any statements or operations executed by the function. A CONTINUE handler will cause the system to ignore the condition and continue processing with the next statement. An EXIT handler causes the program to end the processing of the current segment after execution of the code in the handler body. In this case, I am declaring an EXIT handler for the condition SQLEXCEPTION. SQLEXCEPTION is a keyword that basically is a catch all for any SQL error that may happen during evaluation of my function. The next part of the handler indicates the operation that should be performed if this handler is invoked. In this case, I want to return a NULL value to the calling program.

So, basically, my handler tells the AS/400 that if it encounters an error during execution of the function, it should stop processing and return a null value. This is about the simplest type of error recovery that you can encode in a program.

Here is another example of the same function, this time using a CONTINUE handler to cause the program to set an error indicator. An IF condition then tests the value of the variable and returns the calculated date if no error has occurred, or returns NULL if an error did occur. This example just illustrates that there is always more than one way to skin the proverbial cat when programming with the PSM language.

CREATE FUNCTION SQLTHING.MINCRON_DATE
	(CEN DECIMAL(2,0),
	 YR DECIMAL(2,0),
	 MO DECIMAL(2,0),
	 DY DECIMAL(2,0))
	 RETURNS DATE
	 LANGUAGE SQL
	 BEGIN
	 DECLARE F_OUTPUT DATE;
	 DECLARE F_TEST INTEGER;
	 DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
	 	 SET F_TEST=1;
	 SET F_TEST=0;
	 SET F_OUTPUT = DATE( TRIM(CHAR(MO))||'/'|| TRIM(CHAR(DY))||'/'|| 
TRIM(CHAR((CEN*100)+YR)));
	 IF F_TEST=0 THEN
	 	RETURN F_OUTPUT;
	 ELSE
	 	RETURN NULL;
	 END IF;
	 END

How Does PSM Work?

Finally, I'll tell you a little bit about how PSM is implemented on the AS/400. When you issue the CREATE FUNCTION statement, the AS/400 interprets your function into a C program with embedded SQL that it places in the QTEMP library of your active job. The AS/400 then uses the CRTSQLCI (Create Structure Query Language ILE C) program to compile the generated C code into a service program object. The AS/400 then uses CRTSRVPGM to turn the object into a service program in the named library. Finally, the AS/400 makes entries into the system catalogue tables SYSROUTINES and SYSPARMS in the QSYS2 library. The SYSROUTINES table identifies all of the stored procedures and user-defined functions on your AS/400. The SYSPARMS entries identify each parameter to the function and the return parameter information.

Happy Coding!

This has been a very simple introduction to using the PSM language for defining user-defined functions. If there is an interest, I will elaborate on PSM and user-defined functions in a future article, so please send me an e-mail at harner@itjungle.com if you are interested in this topic. Otherwise, see the iSeries SQL Reference for more information on user-defined functions.

Howard F. Arner Jr. is vice president of Client Server Development, in Jacksonville, Florida, and author of iSeries and AS/400 SQL at Work. He consults with many companies and develops books and products targeted to the AS/400 community. You can reach Howard at harner@itjungle.com.

Sponsored By
BCD SOFTWARE INTERNATIONAL

See why WebSmart beat out 26 other vendors and was
Voted the BEST iSeries -- AS/400
e-Business / e-Commerce tool in the marketplace.

Free Download of the New Version. Build your own, or use / customize the 70+ Free templates and e-Commerce applications. Develop professional state-of-the-art applications in a day not months, all while using your existing skill set. Extend legacy applications.

Forget complex, expensive products that take months and months to learn, or bare bone products with minimal tools & features that make you do the work manually without HTML editing, restrict the look of your web apps, and restrict growth. Restrictions like no IFS, email or Graphics functions, session ids, persistent CGI handling and more.

WebSmart is a proven, state-of-the-art tool that is easy-to-use, requires little or no Web or iSeries400 programming, is packed with features to automate the work for you, and best of all it’s affordably priced.

Quickly deploy elegant, robust and secure B2B, B2C and browser based apps. Including: Inquiries, reporting, maintenance, wireless (XML, WML...), ordering, product catalogs, shopping carts, EIS. . . .

State-of-the-art, portable PC-based design tool. Develop anywhere: Work, on the road & home and enjoy the freedom of choice.

Simply email objects to yourself or others. Unlimited end-users and unlimited developer seats. Automatically produces dynamic HTML CGI programs written in ILE/RPG. 128 bit AES encryption. Utilizes iSeries400 database and security features for scalability and reliability.

Ask for BCD’s 15-point opinion e-mail on why WebSmart is better than WebSphere TM, 1. Ability to run on smaller iSeries -- AS/400’s without having to upgrade hardware. 2. Significantly faster apps. . . .

WebSmart users include: Affinity Ins., Airways Freight, Arrow Environmental, Behr Process, Broward County Schools, Calvin Klein, D.J. Powers, Formica Corp., Goodyear, Hoshizaki America, Kauai Electric, Legacy Partners, Midwest Trophy, MSU, Oregon Dept., of Veterans Affairs, State of California, Testor Corp., Weigh-Tronix. . .

Also try CATAPULT, voted best e-mail / Automated Report Distribution Tool. FREE Downloads: WebSmart or CATAPULT

Visit http://www.BCDsoftware.com or call 630-986-0800, e-mail sales@bcdsoftware.com Trust BCD, Winner of 18 Industry Awards 20,000+ product installations - 9,000 World-wide customers.

THIS ISSUE
SPONSORED BY:
Help/Systems
SoftLanding Systems
BCD Software Int'l
Jacada Ltd.
RJS Software Systems
WorksRight Software
BACK ISSUES
TABLE OF CONTENTS
The Midrange Programmer Philosophy
Prototyping and Calling Java Methods from RPG
Coding SQL Functions in OS/400 V5R1
Subprocedures: Better than Subroutines
Five Cool Things You Can Do with OpsNav
Let Your Hair Down With Free-Formed C-Specs
  Newsletters | Subscribe | Advertise | About Us | Contact | Search | Home  
  Last Updated: 1/14/02
Copyright © 1996-2008 Guild Companies, Inc. All Rights Reserved.