Introduction and Overview Over the last few years, a number of papers have been presented at SUGI and regional conferences discussing the problem of how to extract data from one or more DB2 tables when the list of keys you want is in SAS. See Loren & Dickson, SUGI 19 Proceedings, for an extensive review of techniques and some guidelines for when to use each. At the end of that paper we suggested a technique which was not completely developed at the time. The purpose of this paper is to document the macro for that technique and thereby expand the options available to the SAS programmer trying to get to DB2 data. In choosing a method to access DB2 data, you look at: I) Whether you are allowed to load your list of keys into DB2 for a join there; Retrieving Data from Large DB2 Tables Based on Keys in SAS : The Sequel Judy Loren, Independent Consultant Jack Shoemaker, Independent Consultant The limitation on this technique is the 32K statement size limit. If your list of keys is long enough to cause your WHERE clause to go over that limit, the job fails. The PARTDB2 macro described in this paper allows you to break up your list of keys into pieces of any size. It then executes multiple queries, putting the number of keys you specified into each WHERE clause and looping until the list of keys is exhausted. When you're done you have one SAS dataset with the results from all the queries appended. An Example To illustrate the points in this paper, we can use the example of looking up information about customers in a DB2 database. Suppose we have customer id numbers and need to look up name, address and phone number. With one id number the code might look like this: 2) How large your list of keys is; 3) How large the DB2 table(s) is(are); 4) How often you will need access to the same data between updates. The macro described in this paper is most useful in the cases where you are not allowed to load your list of keys into DB2. It also works well when your list of keys contains several thousand but not tens of thousands of records. The PARTDB2 Macro This macro was built to expand the utility of the SQLJMAC routine written by Paul Kent (and discussed in the SUGI 17 Proceedings). (This macro resides in the SAS Sample Library.) Craig Gavin wrote about a version of this routine specifically for DB2 pass-through in the SESUG '93 Proceedings. This technique takes advantage of the almost magically fast access time you see when you put the key values you want right into SQL and use pass-through to extract data from DB2 based on an indexed field. The macro takes a list of keys in a SAS dataset and generates a list you can use in a WHERE DB2V AR IN (... ) clause. PROC SOL; CONNECT TO DE2 (SSID=DBOZ); SelECT FROM CONNECTION TO DB2 (SELECT FROM PREF,CUST-ADDR ADDR. PREF.CUST~AME NAME. PREF,CUST_PHONE PHONE WHERE ADDR,CUST_ID = '12345' AND NAME,CUST_ID = ADDR,CUST_ID AND PHONE,CUST_ID = ADDR,CUST_ID ) ; DISCONNECT FROM DB2; With more than one id number, the WHERE ADDR.CUST_ID = would change to WHERE ADDR,CUST_ID IN (".) To save you typing the list of keys you want in the parentheses, you can use a macro (discussed above) to generate this list automatically from a SAS dataset containing the values you want. Supposing this macro is called WRITE, the WHERE clause would look like: WHERE ADDR,CUST_ID IN (7.WRITE) It's when this list gets too long for DB2 to contemplate that you need PAR TDB2. 57
How to Use the PARTDB2 Macro Exhibit I at the end of this paper shows the actual code for the PAR TDB2 macro. First we will discuss how to use the macro, then explain how it works. PARTDB2 assumes you have a list of keys in a SAS dataset. It requires the following parameters: Incoming Data Set (IDS-) You can use a two-level name to point to a stored dataset, or you can read in a WORK dataset coming from a previous DATA or PROC step. Output Data Set (ODS=) Tell PARTDB2 where you want to put the result of the queries. How many keys at a time (N=) You can experiment with the number you get best results with. The limit depends on the number of bytes in your key values and whether they are character (requiring a set of quotes per value) or numeric. The name of the SAS key variable (SASV AR=) PARTDB2 will take the values of this variable and put them into the WHERE clause. No other variables from the incoming dataset can be carried through to the output dataset. The type of the SAS key variable (TYPE'") CHAR(acter) or NUMeric. (Actually the macro just looks for CHAR; if the value of TYPE is not CHAR, it assumes numeric.) The format of the SAS key variable (V ARFMT=) The format only matters. if the key variable is numeric. It uses the format to put the numeric values into the WHERE clause. Character values are put in default format with single quotes around them. The name of the DB2 key field (DB2V AR=) This is the field PARTDB2 uses in the WHERE clause to compare to the SAS key values. Note that if this field name appears in more than one table in your FROM list, you must specify a prefix (table name or alias) to decide which table's field will be used in the WHERE clause. You can join the two DB2 tables in the WHERE parameter (below). The DB2 subsystem your table resides in (SSID=) This is the value that will be supplied in the CONNECT TO DB2 (SSlD=value) statement. No, even PARTDB2 can't access more than one subsystem at a time. Your SELECI statement (SELECT=O/OSTR(» Note that the list of fields you want to extract from DB2 should be enclosed in a %STR( ) function. Note also that this is the DB2 SELECT and should contain full DB2 field names. Your FROM statement (FROM=%STR(» Here's where you identify the fully qualified DB2 table name(s) containing the fields you want to extract. When you have more than one table in this list, aliases help in identifying the field sources. Renaming the DB2 fields for SAS (AS=%STR(» This parameter allows you to rename the DB2 fields as they come into the resulting SAS dataset (ODS, above). It operates positionally with the SELECT parameter; the first field in the SELECT parameter receives the first variable name in the AS list, the second field receives the second variable name, etc. This avoids you having to figure out which tie-breaker was used for the DB2 names that match for the flfst 8 characters. Additional WHERE restrictions (WHERE=%STR(» The match of the SAS key variable with one DB2 field is taken care of for you. This parameter is for specifying additional restrictions or joining criteria, especially needed if you are accessing more than one table. Restarting (FO=) If the macro fails in the middle, the results of completed queries remain saved to the output dataset. You can use FO= (stands for FIRSTOBS=) to tell P ARTDB2 where to start (which observation to start with) in the incoming dataset. Appending results to an existing dataset (FIRSTACT=) Independently of restarting, you decide whether you want to create a new dataset with this execution of PAR TDB2 or append to an existing dataset. FIRSTACT=CREA TE will cause the FIRST query to use CREATE TABLE AS; all subsequent queries in that execution ofpartdb2 will use INSERT INTO. If you 58
specify FIRSTACT=INSERT, even the first query will use INSERT INTO. Controlling the number ofloops (NLOOPS=) You can limit the number of queries P ARTDB2 initiates by coding a number here. You would probably be interested in the TRACK= parameter below. Keeping TRACK of where you are (TRACK-) Here you specify the name of a dataset that will store the observation number you should start with if you want to complete the list of keys after either a failure part way through the list or a stop caused by hitting the NLOOPS limit you specified. An Example To illustrate the use of the parameters, suppose as in our previous example we have a list of customer ids and wish to retrieve name, address and phone numbers for each customer. This time, our list of desired id numbers resides in a SAS dataset called WANTED with a variable name ofcid. Using PARTDB2, we would code: //MAC DD DISP=SHR.DSN=MACRO.LIB //IN DD DISP=OLD,DSN=SAS.WANTED OPTIONS NOMPRINT SASAUTOS=CMAC) OBS=MAX; ~PARTDB2CIDS=IN.WANTED,ODS=IN.ADDRS, N=180e. SASVAR=CID,DB2VAR=A.CUST_ID.SELECT=~STRCA.CUST_ID. CITY, STATE, ADD-LINE1, ADD_LINE2, ZIP_CODE, NAME. PHONE_NUMBER, AREA-CODE),AS = ~STR(CID, CITY, STATE, ADDR1. ADDRZ, ZIP, NAME, PHONE, AREACODEl,FROM=~STR( PREF.CUST_ADDR A. PREF.CUST_PHONE B, PREF.CUST_NAME C),WHERE=%STRCA.CUST_ID = B.CUST_ID AND A.CUST_ID = C.CUST_IDl SSID=DBPl. l; Note that some parameters are not required at all unless you want to take advantage of a particular feature. Other parameters can be allowed to default, such as the TYPE=CHAR, if the default suits your application. Defaults You can establish the PARTDB2 macro at your site with whatever defaults you find most useful. Although it is not shown this way in Exhibit I, you can even use default values for DB2V AR, FROM, SASV AR, ODS, etc. Suegestions for use Because of the volume oflog generated, it is best to specify the NOMPRINT option. To further limit the lines of feedback, also use NOFULLSTATS and NONOTES. The values of the FROM, WHERE, SELECT, and AS parameters should be enclosed in the %STRO. For more complex strings, subqueries for example, you may have to resort to %NRBQUOTEO (no rescan blind quote) or another macro as described below. Overview Explanation of the macro code The PARTDB2 macro creates a SAS data set specified by the &ODS parameter from the DB2 table specified by the &FROM parameter. The SAS data set specified by the &IDS parameter contains a list of key values stored in the SAS data step variable specified by the &SASV AR parameter. The macro selects rows from the &FROM DB2 table by comparing the &SASV AR values to the DB2 column name specified by the &DB2V AR parameter. The &ODS data set contains the SAS variables, specified by the &AS parameter, corresponding to the DB2 column names specified by the &SELECT parameter. The macro selects rows by constructing a WHERE IN statement from the key values in &IDS. The &N parameter specifies the maximum length of the IN statement list. The default value is 400. If the number of observations in &IDS exceeds this value, then the macro loops through &IDS processing at most &N values at a time. If &SASV AR is a character variable, the values must be enclosed in single quotes. The &TYPE parameter specifies the variable type for &SASV AR. The default value is CHAR. Any other value will treat &SASV AR as numeric. Description The flfst statement in the macro 7.local NOBS I STOPRC VI - V&N; assigns a local scope to the macro symbols &NOBS, &1, &STOPRC, and &VI through &V400, assuming the default value of 400 for &N. The local scoping of these symbols protects them from any potential collision with other symbols of the same name in different parts of the 59
application. Although the macro wid function properly without this scoping, it is good habit to explicitly scope symbols to avoid future troubles. The next statement in the macro Xlet TYPE = Xupcese( &TYPE ); guarantees that the &TYPE symbol will be in uppercase. This simplifies use of the &TYPE symbol later on, and allows the user to specify &TYPE in either upper or lower case. The next statement in the macro X.I et STOPRC.. e; initializes the &STOPRC symbol to O. This value will contain the return code from DB2 and is passed back to the operating system if an error occurs during execution. The macro has the ability to re-start from anywhere in the &IDS data set in case the application fails during execution. The &FO parameter specifies the first observation number to use in &IDS. If the &FO macro symbol is not I, then the macro assumes that you are restarting a previous run. This statement writes a terse restart message to the SAS log: "if &FO ne 1 "then "put MMMM** RESTARTING ********; Although this function is critical in a production environment, it is beyond the scope of this paper and will be discussed only tangentially. Now that the initialization niceties are complete, it is time to begin the real work of the macro. The first task is to determine the number of observations in the &IDS data set. This is done in the following data step: data _nu I L; if B then set &IDS nobs.. nobs; put nobs=; cal I symputc 'NOBS', left( putt nobs. best. ) ) ); stop; run; This data step takes advantage of the compile-time features of the SET statement to place the nnmber of observations in &IDS into the data step variable NOBS. The call symput function writes out the value of the data step variable NOBS to the macro symbol &NOBS using the BEST. SAS-supplied format. The SET statement is never executed because 'if 0' never evaluates to TRUE. Nevertheless, when SAS compiles the data step it makes certain architectural information available. The number of observations in a data set is one such piece of data and is obtained through the NOBS= option on the SET statement. The values of the macro symbols &FO and &NOBS are written to the SAS log with this macro statement: "put FO=&FO NOBS=&NOBS; As a practical matter, &IDS may not have any observations. In this case, the macro will retrieve one row from the &FROM table with missing key values for analysis. Although this is an important de-bugging feature of the production code, it is beyond the scope of this paper. The real work begins with the following macro statement: Xelse Xdo Xwhi lec &FO Ie &NOBS and &STOPRC eq Bend X&NLOOPS ne XB ); This statement begins a do-while loop which will continue until the &IDS data set is exhausted, or DB2 returns a non-zero return code. The macro parameter &TRACK is either blank, or it contains the name of a SAS data set which will record the last value of &FO used. The macro uses the %lengtho macro function to determine whether &TRACK is populated. If it is, the macro generates a data statement with &TRACK as the data step name. If &TRACK is not populated, the default condition, a data _null is generated. The data step writes macro symbols VI through V400 (assuming the default value) based on the value of the data set variable &SASV AR in &IDS. Here's how it works. First, the automatic data step variable ~_ is converted to a left-aligned character variable NN using this statement: nn = left( putt _n_. best. ) ); Bear in mind that ~_ is not the observation number in &IDS, but rather a counter of the number of times that SAS has come through the data step. The puto function writes out _N_ as a character variable while the lefto function left-aligns the result ofputo in the sevencharacter NN data step variable. Next the macro checks the value of the &TYPE parameter which has been already converted to upper case. If it has a value of CHAR, the macro creates the following call symput statement: cell symputc 'V' II nn,... I I &SASVAR I I... ); The first parameter is the character 'V' concatenated with the value of the data step variable NN. On the first pass 60
of the data step, the value is 'V 1'. On the second pass it is 'V2'. On the four hundredth pass it is 'V 400'. The second parameter is the single quote character concatenated with the value of the data set variable &SASV AR concatenated with another single quote. Thus, character variables are placed inside single quotes. This entire string - quotes and all- is written to the macro symbols VI through V400. If the value of the &TYPE parameter is anything other than CHAR, then the macro creates the following call symput statement: call sympute 'V' II nn. lefte put( &SASVAR. &VARFMT ) ) ); The first parameter is created as before. For the second parameter, the puto function writes out the data set variable &SASV AR using the format specified by the macro parameter &V ARFMT which has a default value of BEST. and this result is left-aligned using the lefto function. The data step will iterate unless the following statement evaluates to TRUE: if end or _n_ ge &N then do; The data step variable END is 1 if this is the last observation in the &IDS data set, and 0 otherwise. The END data step variable is created with the END= option on the SET statement. So the if condition will be true if SAS is at the end of the &IDS data set. The if condition will also be true if the automatic data step variable _n_ is equal to ( or greater than ) the value of the macro parameter &N ( which has a default value of 400). In any event, a true condition will cause the following two statements to execute: ca II symput( 'N' nn ); stop; The call symput statement writes out the value of the data step variable NN to the macro symbol N. Often, this will replace the value of &N with the present value of &N. The last time through the data step, though, it will replace the value of &N with the actual number of observations processed on the last loop through the &IDS data set This is important because &N will be used to control the writing of the elements in the SQL IN statement. The stop statement simply halts further execution of the data step. Assuming that &IDS has 400, or more, observations, the macro symbols &Vl through &V400 will contain the values of the data set variable &SASV AR in the first 400 observations of &IDS. It is time to pass these values along to DB2 in order to select the desired records. The macro contains a parameter called &INOBS which can hold the value of the SAS PROC SQL option INOBS=. The macro use the %lengtho macro function to determine if &INOBS has a value. If it does, the macro creates the following statement: inobs = &INOBS The omission of the trailing semicolon is intentional. As written, the semicolon which appears in the macro terminates the %if macro statement. In order to properly terminate the proc sql statement'another semicolon is required. This required semicolon is placed alone on a separate line to emphasize its importance and to distinguish it from the semicolon which terminates the %if macro statement. Next, the macro creates a connect to db2 statement using the DB2 SSID supplied as the macro parameter &SSID. If this is the first time through the %do %while loop, we need to create the &ODS data set with a create table statement. On subsequent iterations of the %do %wbile loop, we need to append to an existing &ODS data set using an insert to statement. This action is controlled by an %if-%tben-%else construction which evaluates the value of &FIRSTACT which takes on a default value of CREATE. Xif &FIRSTACT eq CREATE Xthen create table &ODS as; Xelse insert into &ODS; Note that the semicolons terminate the %if and %else statements and do not misplace additional semicolons in the proc sql create or insert clause. Notice, as well, that the macro unconditionally changes this value to INSERT with this macro assignment statement which is outside of the %if-%then-%else logic: Xlet FIRSTACT = INSERT; The next statement is the final piece of SAS SQL: select * from connection to db2e The remainder of the statements inside the parentheses are passed to DB2 for execution. As such, they must be valid DB2 SQL statements - not SAS SQL statements. The macro parameter &SELECT must evaluate to a valid DB2 select statement. This means that you must reference DB2 columns names, not SAS variable names. One potential trouble with the &SELECT parameter is that it usually contains commas because you usually select more than one column. You cannot code the &SELECT parameter on macro invocation as follows: Xmacro PARTDB2( SELECT=column_a. column_b. column_c. ) ; 61
because SAS will interpret the comma following columlla as a parameter separator and will attempt to match columllb to a positional parameter which does not exist. There are two strategies to handle this. First, you can quote the entire select clause using the %stro macro function as follows: "macro PARTDB2( SELECT="str( column_a, column-h, column_c ), ) ; Or, you can create another simple macro outside of the PARTDB2 macro and use it to fill in the value of the &SELECT macro parameter. For example: "macro TheList; column_a, column_b, column_c "mend TheList; "macro PARTDB2( SELECT="TheList, ); No attempt will be made to convince you that one method is better than the other. It all depends on your overall application and what method flatters your prejudices. The IN statement is generated by the following code. where &DB2VAR in ( &VI "do I = 2 "to 8., 8.&V&1 Xend; ) The IN statement begins by writing the macro symbol &V I. Then the macro loops through the remaining & V macro symbols starting with &V2 and ending with & V 400 assuming that the macro symbol &N has a value of 400. If the value of &N were, say, 52, then the loop would end with &V52. The only statement within the %doloop is, &&V&l The comma is written to separate values in the IN statement list. The compound macro symbol && V &1 first resolves to &V2, assuming that the value of the macro loop index &1 is 2. Since the expression still contains an ampersand, SAS re-scans the line and attempts to de-reference &V2 using the macro symbol table. On the second pass through the %do loop, &&V&I will evaluate first to &V3 and then to whatever value is stored in the macro symbol &V3. The loop continues until &1 reaches the value of &N. The macro also provides the ability to further constrain the where statement with the following macro logic: "if "Iength( &WHERE ) GT 8 "then and &WHERE ; "if "Iength( &WHEREMAC ) GT 8 "then and X&WHEREMAC ; The macro parameter & WHERE may contain any valid DB2 where fragment. If the value of &WHERE is not missing then the macro creates the following fragment to add to the IN constraint: and &WHERE Remember, the semicolon in the macro terminates the O/Oif statement and is not part of the DB2 where statement. In addition to supplying the additional where constraint as a literal, you may supply the constraint in the form of a macro by way of the &WHEREMAC parameter. If &WHEREMAC is not missing then the macro creates the following fragment: and &WHEREMAC This is the end of the DB2 SQL statements. The following statement terminates the DB2 SQL statements and uses the SAS SQL as command to alias the DB2 colunm names according to the value specified by the &AS macro parameter. ) as ae &as ); Since this is likely to be a comma-separated list of SAS variable names, the same caveats apply to the &AS parameter as apply to the &SELECT parameter. The DB2 pass-through facility automatically places the value of the DB2 return code and message string in the automatic SAS macro variables &SQLXRC and &SQLXMSG respectively. These values are written to the SAS log with the following statement: "put &SQLXRC &SQLXMSG; If the value of &SQLXRC is not zero, then the DB2 operation failed in some manner. If this is the case, the value of &SQLXRC is placed in the &STOPRC macro symbol with the following statement: "if &SQLXRC ne 8 "then "let STOPRC = &SQLXRC; The non-zero value of &STOPRC will cause the %do %whue loop to terminate when it attempts to iterate. The following statement increments the value of &FO by the valueof&n: "let FO = "eval( &FO + &N ); 62
This will cause the data step which creates the & V macro symbols to begin with observation &FO the next time through the %do %while loop. The macro symbol &NLOOPS may be set to control the absolute number of %do %while loops. While this is an important feature of the production code, it is beyond the scope of this paper and will not be discussed. Finally the %do %while loop is terminated with a simple %end and the whole process is repeated until the &IOS data set is exhausted, or one of the other conditions causes the %do %whue loop to terminate prematurely. For Further Information The authors of this narrative welcome comments by e-maii.youcanreachjackatshoe@world.std.com. or Judy at 102037.3235@compuserve.com The remainder of the macro performs some end-of-job processing which is also beyond the scope of this paper. Conclusion We have shown and explained in detail a macro that allows you to extract rows from one or more DB2 tables based on a set of key values stored in a SAS dataset. This technique is invaluable when the size of the DB2 table makes access descriptors impractical and the DB2 administrator prevents writing your list of desired key values into DB2. References Kent, Paul [1992] "Fun with the SQL Procedure--An Advanced Tutorial" pp 167-175 SUGI 17 Proceedings (The PARlDB2 macro code follows this page.) Gavin, Craig James [1993] "SAS/ACCESS Techniques for Large DB2 Databases" pp 99-105 SESUG Proceedings Loren, Judy and Dickson, Alan [1994] "Processing Large SAS and DB2 Files: Close Encounters of the Colossal Kind" pp 1497-1503 SUGI 19 Proceedings Scott, Stephen [1993] "Why We Replaced DB2 Software with SAS Software as a Relational DBMS for a 30-Gigabyte User Information System" pp 187-196 SUGI 18 Proceedings Trademarks SAS and SAS/ACCESS are registered trademarks of SAS Institute Inc., Cary, NC. DB2 is a registered trademark of IBM Corporation. 63
Advanced Tutoria1s Exhibit 1. The PARTDB2 Macro "macro PARTDB2( IDS=, ODS=, FROM=, SSID=DBQ1, 1* Input dataset *1 1* Output dataset *1 1* DB2 table name *1 1* DB2 SSID *1 DB2VAR=, 1* Key variable name in &FROM DB2 table *1 SASVAR=, 1* Key variable name in &IDS dataset *1 TYPE=CHAR, 1* Key variable type *1 VARFMT=BEST.,I* Format used to write numeric keys *1 SELECT=, AS=, 1* DB2 select statement *1 1* SAS al ias names *1 1* Conjunctive DB2 where clause fragments *1 WHERE=, 1* As a literal *1 WHEREMAC=, 1* As a SAS macro *1 TRACK=, 1* Tracking dataset name *1 FO=1, 1* First obs in &IDS *1 FIRSTACT=CREATE, 1* First action on &ODS *1 N=488, NLOOPS=, INOBS=) ; 1* Number of keys to use in each loop *1 1* Max number of loops *1 1* SAS PROC SQL inobs= value *1 /* Initial ize and scope macro variables *1 "'ocal NOBS I STOPRC VI - V&N; "'et TYPE = "upcase( &TYPE ); %Iet STOPRC = 13; /* Write restart message to SAS log *1 "if &FO ne 1 "then "put *.. **** RESTARTING *.. **.. ***; /M Find number of obs in &IDS and place *1 1* value in macro variable &NOBS *1 data _nu I 1_; if 13 then set &IDS nobs = nobs; put nobs=; cal I symput( 'NOBS'. I eft( put( nabs, best. ) ) ); stop; run; 1* Write &FO and &NOBS values to SAS log *1 "put FO=&FO NOBS=&NOBS; 1* If &IDS has no observations. then look *1 1* for records with missing key values *1 "if &NOBS eq 13 "then "do; proc sql "jf "Iength( &INOBS ) gt 8 "then i nabs = &INOBS; 64
connect to db2c ssid = &SSID ); create table &ODS as select * from connection to db2( select &SELECT from &FROM where %if &TYPE eq CHAR Xthen C &DB2VAR = ', ); %else C &DB2VAR =. ); %if %lengthc &WHERE ) GT 8 %then and &WHERE ; %if %Iength( &WHEREMAC ) GT e %then and %&WHEREMAC ; ) as ac &85 ); Xput &SQLXRC &SQLXMSG; quit; data &ODS; set &ODS; stop; "end; 1* Otherwise. bui Id &ODS by looping through *1 1* & FROM using &N keys at a time *1 Xelse "do %whi lec &FO Ie &NOBS and &STOPRC eq e and X&NLOOPS ne X8 ); 1* Write &FO value to &TRACK if requested *1 "if %Iength( &TRACK ) gt 8 %then %do; data &TRACK( keep = fo ); fo = &FO; if _n_ = 1 then output; "end; 1M Otherwise just start a _NULL_ data step *1 "e I se %do; data _nul 1_; %end; 1M Read in &IDS starting at &FO 1* Put va I ues of &SASVAR into *1 1* macro var i"ab I es &Vl to &V&N *1 set &IDS( keep = &SASVAR firstobs = &FO ) end = end; length nn $7.; nn = leftc put( _n best. ) ); %if &TYPE eq CHAR %then %do; call s\;imputc 'V' II nn. ",. I I &SASVAR II.," ); %end; "else "do; call symputc 'V' II nn. leftc putc &SASVAR. &VARFMT ) ) ); "end; MI 65
if end or _n_ ge &N then do; ca II symput( 'N', nn ); stop; end; run; proc sql Xif Xlength( &INOBS ) gt 0 Xthen Inobs = &INOBS; connect to db2( ssid = &SSID ); Xlf &FIRSTACT eq CREATE Xthen create table &ODS as; Xelse insert into &ODS; Xlet FIRSTACT = INSERT; select * from connection to db2( select &SELECT from &FROM where &DB2VAR in ( &Vl Xdo I = 2 Xto &N; &&V8cI Xend; ) Xif Xlength( &WHERE ) GT 0 Xthen and &WHERE ; Xif Xlength( &WHEREMAC ) GT 0 Xthen and r~wheremac ) as a( &as ); Xput 8cSQLXRC &SQLXMSG; Xlf 8cSQLXRC ne 0 Xthen Xlet STOPRC = 8cSQLXRC; quit; Xlet FO = Xeval( &FO + &N ); Xif Xlength( 8cNLOOPS ) gt 0 Xthen Xlet NLOOPS = Xeval( &NLOOPS - 1 ); Xend; /* Create tracking dataset If requested *1 Xif X&NLOOPS eq X8 and Xlength( &TRACK ) gt 0 Xthen Xdo; data &TRACK( keep = fo ); fo = &FO; output; stop;, Xend; 1* Abort program and pass SQL RC back *1 Xif &STOPRC ne 0 Xthen Xdo; data _nu I L; Xif &STOPRC gt 0 Xthen abort &STOPRC Xelse abort Xeval( -1 * &STOPRC );, Xend; Xmend PARTDB2; 66