| Calling CL Programs as Stored Procedures Dear Howard,  I'm stuck on a problem and I thought 
		  maybe you could help. I have an Excel spreadsheet running a SQL 
		  statement retrieving data from the as/400. 
		  Works great.  
		  However, I need to cleanse the data before bringing it to the 
		  spreadsheet.  I have a CL 
		  program running on the AS/400 that if you supply it with 2 dates 
		  (mmddyy format) it will filter the records, massage some data and 
		  create a file called oqtd02pf. 
		  Once this file is created then I want the SQL statement in 
		  excel to run.  Can I run a 
		  CL program passing parameters from the excel spreadsheet? 
		  The spread sheet has the Dates on it already in cells A1 & A2 
		  on a sheet called "Source Data". 
		  Any help would be greatly appreciated. 
		  Al~  Dear Al, 
		  
		   Yes, you can do all of these things and more from inside 
		  the Excel spreadsheet using SQL, ADO and a macro. 
		  First of all, lets talk about calling CL programs on the 
		  AS/400.  There are two 
		  ways to call a program, as a declared stored procedure or using the 
		  QCMDEXC API to call any program on the iSeries. 
		  A declared stored procedure is when you have a program on your 
		  iSeries and you then run the CREATE PROCEDURE command to define the 
		  program to SQL.  This 
		  causes entries to be made in your system catalogue that describe the 
		  external program and the parameters that the program takes. Once the 
		  procedure is described, SQL based programs can inquire about the 
		  procedures available on your iSeries and execute procedures on your 
		  box. This is a really cool way to reuse your business logic. 
		   Use an API!One way to call a CL program on the iSeries is to 
		  use the QCMDEXC API. This allows you to call ANY program, whether it 
		  is a command, stored procedure or whatever. The QCMDEXC API takes two 
		  parameters, the command string to execute and the length of the 
		  command string as a decimal 10,5 number. Here is an example of how you 
		  could call the STRDBG (start debug) command using the QCMDEXC API from 
		  a VBScript macro.   
		  
		  Dim Con1 as new ADODB.Connection 
		  
		  Dim Cmd1 as new ADODB.Command 
		  
		  Con1.Open “DSN=MY400;UID=HARNER;PWD=SECRET” 
		  
		  Cmd1.ActiveConnection = Con1 
		  
		  Dim Stmt as String 
		  
		  Stmt = “Call QSYS.QCMDEXC(‘STRDBG UPDPROD(*YES)’,0000000020.00000) 
		  
		  Cmd1.CommandText = Stmt 
		  
		  Cmd1.Execute   Lets walk through the command one line at a time. 
		  The first two lines declare our ADO Connection and Command 
		  objects.  They are used to 
		  communicate with the iSeries and execute commands. The next line uses 
		  the Open method of the Connection object to open a connection to the 
		  iSeries.  In this case, I 
		  am using an ODBC data source called MY400 and passing the user ID and 
		  password in order to connect. 
		  The next line of code associates the command object CMD1 with 
		  the open connection object Con1. 
		  I then declare a string variable call Stmt and on the next line 
		  place the text of the command I want to execute into the string 
		  variable.  Notice that I 
		  use the SQL CALL command. CALL is how you let SQL know that you want 
		  to execute a stored procedure. 
		  The stored procedure that I want to execute is actually the 
		  QCMDEXC API that is located in the QSYS library. 
		  I pass this API the command string to start debug and the 
		  length of the command string as the second argument. 
		  Next line of code, I set the CommandText property of the 
		  command object to the value of the Stmt variable and then on the last 
		  line I use the Execute method to cause the command object to send the 
		  command to the iSeries. The iSeries then dutifully places my job into 
		  debug mode. QED.   Now, it is easier to call a program if it is 
		  declared as a stored procedure because ADO will help you pass 
		  different data types to the program. Lets create a small CL program 
		  and illustrate both ways to calling it. 
		  First, create a source physical file and then add the code from 
		  the following example:   
		  
		  PGM        
		  PARM(&LIB &FIL) 
		  
		  DCL        
		  VAR(&LIB) TYPE(*CHAR) LEN(10) 
		  
		  DCL        
		  VAR(&FIL) TYPE(*CHAR) LEN(10) 
		  
		  DSPDBR     
		  FILE(&LIB/&FIL) OUTPUT(*OUTFILE) 
		  + 
		  
		             
		     
		  OUTFILE(QTEMP/MYDSPDBR)   Use the CRTCLPGM program to compile this source 
		  into a CL program called SQLBOOK/CLDSPDBR1 and then you are ready to 
		  rock and roll. This simple little CL program takes a library and 
		  filename and runs DSPDBR command against the supplied information. 
		  The output of the command is redirected to the QTEMP library 
		  and places in a file called MYDSPDBR. 
		  Because my job is in the same activation group as the CL 
		  program, I will be able to read any files created by the CL program in 
		  QTEMP. Below is a simple VB program, along with two function 
		  definitions, that uses the QCMDEXC api to call our command. 
		  Note the function Rpad is used to padd a string on the right 
		  with a specified character and Run4Cmd is used to properly format the 
		  call to QCMDEXC.    
		  
		  Private Sub Form_Load() 
		  
		  Dim Con1 As New ADODB.Connection 
		  
		  Dim Cmd1 As New ADODB.Command 
		  
		  Dim Rs As New ADODB.Recordset 
		  
		  Con1.Open "DSN=MY400;UID=HARNER;PWD=SECRET;" 
		  
		  Cmd1.ActiveConnection = Con1 
		  
		  Cmd1.CommandType = adCmdText Cmd1.CommandText = Run4Cmd("CALL SQLBOOK/CLDSPDBR1 
		  PARM(MCEIS BRANCHES)") 
		  
		  Cmd1.Execute 
		  
		  Cmd1.CommandText = "select * from qtemp.mydspdbr" 
		  
		  Rs.CursorLocation = adUseClient 
		  
		  Rs.CacheSize = 100 
		  
		  Rs.Open Cmd1 
		  
		  While Not Rs.EOF 
		  
		    Debug.Print 
		  Rs.Fields(“WHRFI”).Value 
		  
		    Rs.MoveNext 
		  
		  Wend 
		  
		  Con1.Close 
		  
		  End Sub 
		  
		    
		  
		  Function Rpad(x As String, l As Long, c As String) As String 
		  
		  Dim sl As Long 
		  
		  sl = Len(Trim$(x)) 
		  
		  If sl < l Then 
		  
		    Rpad = String$(l - sl, 
		  c) & Trim$(x) 
		  
		  Else 
		  
		    Rpad = x 
		  
		  End If 
		  
		  End Function 
		  
		    
		  
		  Public Function Run4Cmd(ctxt As String) As String 
		  
		  On Error Resume Next 
		  
		  ctxt = "call qsys.qcmdexc('" & ctxt & "'," & _  
		  
		  Rpad(CStr(Len(ctxt)), 10, "0") & ".00000)" 
		  
		  Run4Cmd = ctxt 
		  
		  End Function   This program connects to the iSeries and then 
		  uses the Run4Cmd function to prepare a call to the QCMDEXC API. 
		  Once the call is complete, the program reuses the command 
		  object to select all of the files from the physical file the program 
		  created in QTEMP. Finally, the program goes into a loop reading the 
		  records form the physical file and writing the contents of the field 
		  WHRFI that was returned by the select statement to the interactive 
		  debug window. Doing it with a procedure definitionNow, lets create a stored procedure definition 
		  for our command and show you how it makes it a little easier to call 
		  the CL program. First, start an interactive SQL session or use my 
		  SQLThing tool and enter the following command:   
		  CREATE 
		  PROCEDURE SQLBOOK.CLDSPFDP 
		  ( IN LIB 
		  CHARACTER(10), 
		  
		    IN FIL CHARACTER(10)) 
		  
		   RESULT SETS 0 
		  
		   EXTERNAL NAME 
		  SQLBOOK.CLDSPDBR1 
		  
		   LANGUAGE CL 
		  
		   PARAMETER STYLE GENERAL   Once this command is executed, you have caused 
		  the procedure definition to be written to the QSYS2.SYSROUTINES file 
		  and the definition of the procedure parameters to be written to the 
		  QSYS2.SYSPARMS file.  This 
		  definition defines how to call the CL program CLDSPDBR1. Note that I 
		  called the procedure CLDSPFDP. The procedure name has nothing to do 
		  with the name of the command I want to call. The name of the command 
		  is defined by the EXTERNAL NAME definition in the CREATE PROCEDURE 
		  statement.  I could have 
		  called this procedure BUPKIS if I had so desired. 
		  The name of the procedure is irrelevant.    Now that these system catalogue entries have been 
		  recorded, you can use the program as an SQL procedure. 
		  The advantage of an SQL procedure is that SQL will handle the 
		  marshaling of parameter data to and from the procedure. 
		  Here is an example of the code needed to call the procedure:   
		  Private 
		  Sub Form_Load() 
		  Dim Con1 
		  As New ADODB.Connection 
		  Dim Cmd1 
		  As New ADODB.Command 
		  Dim Rs As 
		  New ADODB.Recordset 
		  Con1.Open 
		  "DSN=GEM2;UID=HARNER;PWD=HARNER;" 
		  
		  Cmd1.ActiveConnection = Con1 
		  
		  Cmd1.CommandType = adCmdText 
		  
		  Cmd1.CommandText = "Call SQLBOOK.CLDSPFDP (?,?)" 
		  
		  Cmd1.Parameters.Append  
		  Cmd1.CreateParameter("LIB", adChar, adParamInput, 10, "MCEIS") 
		  
		  Cmd1.Parameters.Append  
		  Cmd1.CreateParameter("FIL", adChar, adParamInput, 10, "BRMASTER") 
		  
		  Cmd1.Execute 
		  
		  Cmd1.CommandText = "select * from qtemp.mydspdbr" 
		  
		  Rs.CursorLocation = adUseClient 
		  
		  Rs.CacheSize = 100 
		  Rs.Open 
		  Cmd1 
		  While Not 
		  Rs.EOF 
		  
		    Debug.Print 
		  Rs.Fields("WHRFI").Value 
		  
		    Rs.MoveNext 
		  Wend 
		  Con1.Close 
		  End Sub   Notice that in this version of the code I am 
		  still using a command object, but I am not using the QCMDEXC API. 
		  Instead, I am calling the program using the SQL CALL statement to call 
		  a recognized stored procedure on the iSeries called CLDSPFDP. 
		  If you look in the SQLBOOK library, you will not see a program 
		  or object called CLDSPFDP. That is because the object does not exist, 
		  only an entry in the system catalogue exists telling SQL that anyone 
		  attempting to run a procedure called CLDSPFDP should caue the program 
		  CLDSPDBR1 to be executed.   After formatting the call statement, I am using 
		  the command object to create two parameter objects so that I can pass 
		  the expected Library and Filename to the stored procedure. I could 
		  have used a method of the command object called Parameters.Refresh to 
		  have the parameters collection automatically instantiated from the 
		  iSeries system catalog, but by creating the parameters manually I save 
		  a round trip between client and server which cuts down on processing 
		  time.  Notice that during 
		  the creation of the parameter objects I also set their value. See, the 
		  CreateParameter method takes 5 arguments: Parameter name, data type, 
		  parameter direction, parameter length and the initial value. 
		  So, when I create the parameter and append it to the parameters 
		  collection I am also setting the value that I want to pass to the 
		  iSeries.  Other than the 
		  difference in the command text property and the creation of 
		  parameters, the performance of the two example pieces of code should 
		  be identical. However, the second method will outperform the first 
		  because the procedure is declared and does not have to go through the 
		  command API.  Back to the futureBack to your initial question, to get your 
		  spreadsheet working you will need to write a VBScript macro to grab 
		  the values from the cells A1 and B1 and format the values into 
		  something that the iSeries expects. 
		  Since this tip has gone on so long, I will leave that as an 
		  exercise for the reader of this tip. 
		  Just kidding! Since I hate not answering a question, the next 
		  tip published by me will be an example of how to create an Excel macro 
		  that calls a stored procedure, passes values from the spreadsheet to 
		  the procedure and writes the results into another spreadsheet tab. 
		  Until the next time, cheerio! |