tnt400.com - AS/400 Tips And Techniques

Sponsored by news400.com

This page is a discussion on the mentioned topic.
Most of the answers are in their original posted form, including any technical/spelling/grammatical errors.
No guarantees are expressed or implied. :-)
Comments, corrections, concerns about this tip?

Got another AS/400 question? Ask it here


What's New?
See what's new at Tips-N-Tech.

AS/400 Tips-N-Tech
AS/400 tips, techniques, and FAQ. Updated frequently.
CODEPage/400
All the code samples you can eat! RPG, CL, DDS, etc. etc.
AS/400 FAQs
The official news400.com FAQs.






All AS/400 Tip Categories / AS/400 Programming / Dynamic RPGSQL


Question:

Does anyone have an idea how to make an rpg programm which executes an dynamic sql-select-statement where the number and the type of the selected columns are not known. Where are the limits of such an sql-statment ?


Answer(s):



Funny because this same subject just came up on the NEWSLINK400 forum. Yes you can prepare a dynamic select statement for a cursor where you don't basically have to know anything at compile time including the columns, the files, etc. However, if you are planning on doing multiple opens and closes of such cursors and then changing the columns or something else like that, then you must re-prepare the select statement and reopen the cursor.

An SQL Prepare statement is potentially very slow because the system must build an access plan based on available access paths, etc. Therefore, SQL does allow you to place something called a parameter marker (which is a question mark) in any place where static sql allows you to place a host variable. Excluded from this are things like column lists and file names. So SQL wouldn't permit me to prepare a statement that was formed as follows.

EvalSqlStm = 'Select ? from ?'
Too bad! However, SQL would allow me to prepare the following statement.
EvalSqlStm = 'Select * From Customer Where CuName > ?

You substitute for parameter markers with the Using clause of the Open cursor SQL statement. I will show an example below. The following is a program I wrote and checked out with the debugger to make sure it was executing correctly. Notice this example does show how to use parameter markers. Notice the open cursor statement must specify the name of a host variable to substitute for the question marks. Hope this helps.

D GenCust         DS
D  CuName                       25
D  CuStat                        1

D CustAp          DS
D  CuOv30                        9P 2
D  CuOv60                        9P 2
D  CuOv90                        9P 2

D OrdDs         E DS                  ExtName( OrdHdr )

D SelectStm       S            200

D PrmChoice       S              1
D  ChoiceGenCust  C                   'G'
D  ChoiceCustAp   C                   'A'
D  ChoiceOrd      C                   'O'

D GtName          S             25    Inz( 'H' )
D GtCuOv30        S              9P 2 Inz( 100 )
D EqOhStat        S              1    Inz( 'C' )

C/Exec SQL
C+         Declare DynamCsr Cursor for
C+           DynSqlStm
C/End-Exec

C     *Entry        Plist
C                   Parm                    PrmChoice

C                   Select

C                   When      PrmChoice = ChoiceGenCust

C                   Eval      SelectStm = 'Select CuName, CuStat ' +
C                                         'From Customer '         +
C                                         'Where CuName > ?'
C/Exec SQL
C+         Prepare DynSqlStm
C+           From :SelectStm
C/End-Exec
C/Exec SQL
C+          Open DynamCsr
C+            Using :GtName
C/End-Exec

C/Exec SQL
C+          Fetch Next
C+            From DynamCsr
C+            Into :GenCust
C/End-Exec

C                   When      PrmChoice = ChoiceCustAp

C                   Eval      SelectStm = 'Select CuOv30,'        +
C                                         '       CuOv60,'        +
C                                         '       CuOv90 '        +
C                                         'From Customer '        +
C                                         'Where CuOv30 > ?'
C/Exec SQL
C+         Prepare DynSqlStm
C+           From :SelectStm
C/End-Exec
C/Exec SQL
C+          Open DynamCsr
C+            Using :GtCuOv30
C/End-Exec

C/Exec SQL
C+          Fetch Next
C+            From DynamCsr
C+            Into :CustAp
C/End-Exec

C                   When      PrmChoice = ChoiceOrd

C                   Eval      SelectStm = 'Select * '             +
C                                         'From OrdHdr '          +
C                                         'Where OhStat = ?'
C/Exec SQL
C+         Prepare DynSqlStm
C+           From :SelectStm
C/End-Exec
C/Exec SQL
C+          Open DynamCsr
C+            Using :EqOhStat
C/End-Exec

C/Exec SQL
C+          Fetch Next
C+            From DynamCsr
C+            Into :OrdDs
C/End-Exec

C                   EndSl

C/Exec SQL
C+          Close DynamCsr
C/End-Exec

C                   Eval      *INLR = *On

Mike Cravitz NEWS/400 Technical Editor




So far so good, but i think i did not express my problem clear enough - second try: A user enters a sql-statement like: select kskos, kv#01, kv#02 from ks, su where kskos = sukos (or any other valid select statement) My little program does not know which tables were concered and how many (and of course which type) of columns will come out of this. So my question is - is this possible and if it is how is it done.




I was hoping you weren't expecting to do something this dynamic. Oh well. Let's give this a whack. The SQL programming guide suggests that what you want to do is possible with the SQL DESCRIBE statement. I'm going to cut and paste from that manual and then make some comments.

===Start Manual excerpt===
8.3.2 Varying-List Select-Statements

In dynamic SQL, varying-list SELECT statements are ones for which the
number and format of result columns to be returned are not
predictable; that is, you do not know how many variables you need, or
what the data types are.  Therefore, you cannot define host
variables in advance to accommodate the result columns returned.

Note:  In REXX, steps 5b, 6, and 7 are not applicable.

If your application accepts varying-list SELECT statements, your
program has to:

1.  Place the input SQL statement into a host variable.

2.  Issue a PREPARE statement to validate the dynamic SQL statement
and put it into a form that can be run. If DLYPRP (*YES) is specified
    on the CRTSQLxxx command, the preparation is delayed until the
first time the statement is used in an EXECUTE or DESCRIBE statement,
    unless the USING clause is specified on the PREPARE statement.

3.  Declare a cursor for the statement name.

4.  Open the cursor (declared in step 3) that includes the name of the
dynamic SELECT statement.

5.  Issue a DESCRIBE statement to request information from SQL about
the type and size of each column of the result table.

    Notes:

    a.  You can also code the PREPARE statement with an INTO clause to
perform the functions of PREPARE and DESCRIBE with a single
        statement.

    b.  If the SQLDA is not large enough to contain column
descriptions for each retrieved column, the program must determine how
much
        space is needed, get storage for that amount of space, build a
new SQLDA, and reissue the DESCRIBE statement.


6.  Allocate the amount of storage needed to contain a row of
retrieved data.

7.  Put storage addresses into the SQLDA (SQL descriptor area) to tell
SQL where to put each item of retrieved data.

8.  FETCH a row.

9.  When end of data occurs, close the cursor.

10. Handle any SQL return codes that might result.

====End of Manual Excerpt== First of all, doing the things described here is non-trivial to say the least. You have to make sure you have created your SQLDA properly. You have to programatically decipher the outcome of the SQL Describe statement well enough to know the size of the buffer necessary to hold a row (or record). Or you could simply allocate a very large buffer of say 32,767 (or even smaller) if you know the maximum record length of all possible files on your system. But when you get all done, you need to parse the SQLDA to pick up the type and size of each field. If that isn't bad enough, these 10 steps probably left off the hardest part of all.

So I've performed the 10 steps above and I have a record in a dynamically allocated chunk of memory. I have in my SQLDA a description of each field in this chunk of memory. If a particular field is character, I can probably deal with that because of RPG's powerful %Subst and other string-handling BIFs. But what if the field is packed? There are 9,920 possible packed numeric descriptions. I suppose you can have a based data structure where you can overlay these 9,920 possibilities on top of each other. But it seems to me you would also need an RPG Select statement with 9,920 possible When clauses. Since that's not practical, we have to take another approach. Here is what I would do.

I would write a subprocedure which accepts a character string of any length from 1 to 16 bytes and returns the numeric value. The simplest thing to do here is have the subroutine accept a varying length field by value. That way you can pass it a fixed length field and not have to pass the length. The subroutine can use the %Len BIF to pick up the length. This subroutine would ignore the number of decimals and assume that the packed field has 0 decimals. It would then examine the string byte by byte and would build up the actual value from the packed value contained in the string. The procedure then returns this value. The SQLDA gives me the number of decimals. From that point forward you can use RPG's %Dec BIF. For example if the procedure returns a value (ignoring decimals) which you put into a 30,0 field called DecVal. Also suppose you store the number of digits into a field called Digits. And if you have placed the number of decimals into another 2,0 (or 3,0 if you're fussy about a silly performance issue) called NumDecs. Then from this point forward, you can refer to your packed field with %Dec( DecVal: NumDigits: NumDecs ).

Obviously this is a lot of work and my guess is you're going to conclude that it is not worth it. But anyway, it was worth it to learn something.

Mike Cravitz NEWS/400 Technical Editor




I made a mistake (surprise!). >Then from this point forward, you can refer to your packed >field with %Dec( DecVal: NumDigits: NumDecs ). You would actually have to refer to your packed field as follows... %Dec( DecVal / ( 10 ** NumDecs ): NumDigits: NumDecs ) Mike Cravitz NEWS/400 Technical Editor





Other tips in this category:

Click here to see all categories.

Socket Programming And Timeout Issues
Deleting Duplicate Records From A Table
What are data queues and how to use
Retrieving SMTP Name
RPGLE example for Dynamic Screen Manager API
Calling APIs from CL - with examples!
Compare two strings in RPG character by character
How to search all pgms in QCLSRC for a keyword
Retrieve Database File Description (QDBRTVFD) API
How to redirect the output to STDERR from RPG-IV
Using Multiformat Logical To Join 2 Identical File
D-Spec *LIKE DEFN
RPG: Converting Character to Decimal
Example of ILE RPG CGI Program
Handling ILE RPG Numeric Overflow
Help with Subfile Programming in RPG III
Zoned parameter in ILE RPG
What's the best way to do modulus in CL?
RPG Nesting Source Print Utility
More on changing the SIGNON screen
A silly ILE RPG question
Calling Validation List API From ILE RPG
Soft Coding Module Names
RPG Multidimensional Arrays in Action
Build a Page-Equals-Size Lookup Window
Procedures within an ILE RPG program
Break msg from RPG
Source Debugger for batch jobs
RPGLE debugging
Timing out display sessions in DDS
Building Dynamic Stored Procedures
Put Message in System Log
Dynamic RPGSQL
Randomize function for the AS/400
What's the fastest way to do a simple RPG lookup?
First time Data queue application in RPG
Print file overflow in ILE RPG
Mapping Fields To Arrays in ILE RPG
Named inidicators
STRQMQRY: comparision operator '=' isn't correct?!
DSPDTAARA to an outfile - possible?
PCL ESC codes in RPG
Sharing DB files between two AS/400s
Help: AS/400 subfiles
How do you change the signon screen?
Determining Even/Odd Values in queries
Get day of the week in RPG
Convert UPPERCASE to lowercase
RPG IV help using APIs
Help with data area API
Detecing IFS Files from RPG
SQL in a CL program
Calling AS/400 APIs from ILE RPG
CVTDAT command to convert an *MDY to a *LONGJUL
CPYSPLF Automation
Subfile Window background problem
Using IFS APIs w/ ILE RPG
Packed or unpacked fields?
QRYDFN to source and back
What does the "optimize" parm do?
Determining the calling program
Sockets in RPG?
Sorting a user space
OVRDBF and SECURE() keyword in an ILE environment
RPG record locking
RPG Differences: V2R3 to V3R2 upgrade
Getting the relative record number (RRN)
What is the longest parameter usable in RPG?
A C function that returns a string to an RPG pgm
Problems with ZADD *ZEROS
Can you highlight code in SEU?
Reusing deleted records - OK?
Is there an easy way to change edit codes?
Help - Windowed Subfiles
Logical Files and DDS
AS/400 'machine language' - MI Programming
Library lists and performance
How to use ERRSFL
Updating in CL
Record lock wait time
Message subfile problem
Physical File Joins
CL: Copying User Profiles
Commands and PARM
ILE RPG, RPG IV, vs. RPG/400
RPG and subfiles
Multiple subfiles
Field masking for passwords
SFLMODE keyword in an ILE program
Subfile size
Color coding records in a subfile
MSGLINE in windows
Controlling cursor movement on a display file
API returns error! Why?
Put an RPG Program on the Web
Variable length records in RPG
Differences Between RPG400, RPG IV, and ILE RPG


You are at a news400.com site.
Contact Us | Report Bugs | Submit Comments/Suggestions | Read Site Use Agreement | Read Privacy Policy
Copyright © 2000 Duke Communications International.
This site is best viewed with the latest versions of Netscape or Internet Explorer, 800 x 600 resolution (or higher), and at least 256 colors.
Duke Communications   NEWS/400 | 29th Street Press | Business Finance | DominoPro | Selling AS/400 Solutions | SQL Server Magazine | Windows NT Magazine