Database Forum / DB2 Topics / June 2006
Stored Procedure from newbie
|
|
Thread rating:  |
Frank Swarbrick - 29 Jun 2006 23:29 GMT Below the first stored procedure I have written. It works as intended. But, being my first one, I wanted to see if there are any obvious (or not so obvious) flaws in it. (Yes, there's a reason, perhaps not a good one, why I have the OUT fields before the INOUT fields.)
CREATE PROCEDURE DB2DEV.GET_FEE_AMOUNT ( OUT amount DECIMAL(9,2), OUT descr VARCHAR(100), INOUT typ CHARACTER(3), INOUT subtype CHARACTER(3), INOUT bank DECIMAL(5,0), INOUT brch DECIMAL(3,0), INOUT acct DECIMAL(7,0) ) READS SQL DATA ------------------------------------------------------------------------ -- SQL Stored Procedure -- amount Fee amount -- descr Fee description -- typ Fee type code -- subtype Fee subtype code -- bank Bank number -- brch Branch number -- acct Account number ------------------------------------------------------------------------ P1: BEGIN DECLARE cursor1 CURSOR WITH RETURN FOR SELECT FA.AMOUNT, FA.DESCR, FA.TYP, FA.SUBTYP, FA.BANK, FA.BRCH, FA.ACCT FROM DB2DEV.FEE_AMOUNTS AS FA WHERE (FA.TYP = GET_FEE_AMOUNT.typ AND FA.SUBTYP = GET_FEE_AMOUNT.subtype) AND ((GET_FEE_AMOUNT.brch <> 0 AND GET_FEE_AMOUNT.acct <> 0 AND FA.BRCH = GET_FEE_AMOUNT.brch AND FA.ACCT = GET_FEE_AMOUNT.acct) OR (GET_FEE_AMOUNT.brch <> 0 AND FA.BRCH = GET_FEE_AMOUNT.brch AND FA.ACCT = 0) OR (GET_FEE_AMOUNT.bank <> 0 AND FA.BANK = GET_FEE_AMOUNT.bank AND FA.BRCH = 0) OR (FA.BANK = 0 AND FA.BRCH = 0 AND FA.ACCT = 0)) ORDER BY FA.ACCT DESC, FA.BRCH DESC, FA.BANK DESC FETCH FIRST 1 ROW ONLY; OPEN cursor1; -- Set defaults in case no rows result SET typ = NULL; SET subtype = NULL; SET bank = NULL; SET brch = NULL; SET acct = NULL; FETCH cursor1 INTO amount, descr, typ, subtype, bank, brch, acct; CLOSE cursor1; END P1
Here is the DDL to create the table: ------------------------------------------------ -- DDL Statements for table "DB2DEV "."FEE_AMOUNTS" ------------------------------------------------
CREATE TABLE "DB2DEV "."FEE_AMOUNTS" ( "TYP" CHAR(3) NOT NULL , "SUBTYP" CHAR(3) NOT NULL , "BANK" DECIMAL(5,0) NOT NULL , "BRCH" DECIMAL(3,0) NOT NULL , "ACCT" DECIMAL(16,0) NOT NULL , "AMOUNT" DECIMAL(9,2) NOT NULL , "DESCR" VARCHAR(100) ) VALUE COMPRESSION IN "USERSPACE1" ;
-- DDL Statements for primary key on Table "DB2DEV "."FEE_AMOUNTS"
ALTER TABLE "DB2DEV "."FEE_AMOUNTS" ADD CONSTRAINT "CC1151514082302" PRIMARY KEY ("TYP", "SUBTYP", "BANK", "BRCH", "ACCT");
And here are some records in the table.
TYP SUBTYP BANK BRCH ACCT AMOUNT DESCR
--- ------ ------- ----- ------------------ ----------- ----------------- AAA 000 0. 0. 0. 3.00 AAA000 default
AAA 000 111. 0. 0. 2.95 AAA000 bank 00111
AAA 000 0. 234. 0. 2.90 AAA000 brch 234
AAA 000 0. 234. 1234567. 2.85 AAA000 2341234567
AAA 000 456. 0. 0. 2.80 AAA000 bank 00456
AAA 000 0. 345. 0. 2.75 AAA000 brch 345
AAA 000 0. 345. 9876543. 2.70 AAA000 3459876543
AAB 000 0. 0. 0. 2.00 AAB000 default
AAA 001 0. 0. 0. 10.00 AAA001 default
The point of the SP is to return one and only one row, the one which best matches the inputs, with non-zero values being prefered of zeroes. For example
db2 => call get_fee_amount ( ?, ?, 'AAA', '000', 00456, 345, 1234567)
Value of output parameters -------------------------- Parameter Name : AMOUNT Parameter Value : 2.75
Parameter Name : DESCR Parameter Value : AAA000 brch 345
Parameter Name : TYP Parameter Value : AAA
Parameter Name : SUBTYPE Parameter Value : 000
Parameter Name : BANK Parameter Value : 0.
Parameter Name : BRCH Parameter Value : 345.
Parameter Name : ACCT Parameter Value : 0.
Return Status = 0
Essentially, if there's a branch/account match we want to select that row; next we try to match on just branch (with account 0); then we try bank match with branch and account 0; then finally we default to bank, branch and account all set to zero (assuming, of course, we'll always get a type/subtype match; otherwise we're must be inputing the wrong type/subtype!)
One thing I'm not satisfied with is the setting of the INOUT parms to NULL prior to the FETCH. I would rather only execute this if the FETCH returns no result, but I wasn't able to get that to work correctly.
Anyway, thanks ahead of time for any thoughts/recommendations.
Frank
--- Frank Swarbrick Senior Developer/Analyst - Mainframe Applications FirstBank Data Corporation - Lakewood, CO USA
jefftyzzer - 30 Jun 2006 02:04 GMT Hi, Frank:
I'm assuming you've EXPLAINed this, so I'll not ask you about the query per se, but you could, if you wanted, stop the SP at the OPEN, and have the SP return a result set, as opposed to having to declare the variables and do all the cursor handling. You might already know this and have a very good reason for doing it the way you have, but I just thought I'd bring it up in case you wanted to knock a few lines off of your SP and shrink its signature a bit.
--Jeff
> Below the first stored procedure I have written. It works as intended. > But, being my first one, I wanted to see if there are any obvious (or not so [quoted text clipped - 144 lines] > Senior Developer/Analyst - Mainframe Applications > FirstBank Data Corporation - Lakewood, CO USA Frank Swarbrick - 30 Jun 2006 19:49 GMT Don't assume anything. :-) No, I have not EXPLAINed it. To be honest, I don't know how. Can you point me in the right direction? Is EXPLAIN related to the Access Plan?
(As I said, I'm a brand newbie, so any question I ask may very well be a 'dumb question'!)
I intentionally chose to return variables instead of a result set, because I wanted the client ap to be very simple, something like:
int i; CallableStatement callStmt = con.prepareCall("call get_fee_amount ( ?, ?, ?, ?, ?, ?, ? )"); // register the output parameters i = 0; callStmt.registerOutParameter(++i, Types.INTEGER); callStmt.registerOutParameter(++i, Types.VARCHAR); callStmt.setString(++i, feeType); callStmt.setString(++i, feeSubtype); callStmt.setInt(++i, bank); callStmt.setInt(++i, branch); callStmt.setInt(++i, account); // call the stored procedure callStmt.execute(); // retrieve the output parameters i = 0; feeAmount = callStmt.getBigDecimal(++i); feeDescr = callStmt.getString(++i); System.out.println(feeAmount+" "+feeDescr); System.out.println(callStmt.getString(++i)+" " +callStmt.getString(++i)+" " +callStmt.getInt(++i)+" " +callStmt.getInt(++i)+" " +callStmt.getInt(++i));
Seemed to me that there wasn't any point in having a result set since it would never have more than one row. Is this faulty logic? Anyway, with Sergey's help I have knocked it down a bit, totally eliminating the cursor (which I had tried before but had not done it correctly).
Thanks, Frank
--- Frank Swarbrick Senior Developer/Analyst - Mainframe Applications FirstBank Data Corporation - Lakewood, CO USA
>>> jefftyzzer<jefftyzzer@sbcglobal.net> 06/29/06 7:04 PM >>> Hi, Frank:
I'm assuming you've EXPLAINed this, so I'll not ask you about the query per se, but you could, if you wanted, stop the SP at the OPEN, and have the SP return a result set, as opposed to having to declare the variables and do all the cursor handling. You might already know this and have a very good reason for doing it the way you have, but I just thought I'd bring it up in case you wanted to knock a few lines off of your SP and shrink its signature a bit.
Bob Stearns - 30 Jun 2006 03:11 GMT > Below the first stored procedure I have written. It works as intended. > But, being my first one, I wanted to see if there are any obvious (or not so [quoted text clipped - 144 lines] > Senior Developer/Analyst - Mainframe Applications > FirstBank Data Corporation - Lakewood, CO USA I wanted to know if I understood your requirement correctly and if there was a straightforward sql way to do the task To that end, I created a sample table:
create table accounts( typ char(3) not null, subtyp char(3) not null, bank smallint not null, branch smallint not null, acctno smallint not null, balance decimal(8,2), primary key(typ, subtyp, bank, branch, acctno) )
And then created a query which seems to match your requirement, with the assumption that all the bank and branch 0 rows exist. The query language I use needs the ampersands as parameter markers (the typ and subtyp values should be parameters too; I only put 'ABC' / '000' rows in my test data):
select * from accounts where typ='ABC' and subtyp='000' and bank=case when exists(select * from accounts where typ='ABC' and subtyp='000' and bank=&bank) then &bank else 0 end and branch=case when exists(select * from accounts where typ='ABC' and subtyp='000' and bank=&bank and branch=&branch) then &branch else 0 end and acctno=case when exists(select * from accounts where typ='ABC' and subtyp='000' and bank=&bank and branch=&branch and acctno=&acctno) then &acctno else 0 end
The query seems to answer your requirement and the 'explain' seems not overlong, but I'm no expert in that area.
Another approach, still using straight sql would be to take the union of the four queries, order by bank desc, branch desc, acctno desc fetch first 1 row only. Exact syntax is left to the reader (in other words, I don't know it :-). This should be very fast since each query is going for a full match match on the primary key.
Frank Swarbrick - 30 Jun 2006 19:53 GMT Bob Stearns<rstearns1241@charter.net> 06/29/06 8:11 PM >>>
>I wanted to know if I understood your requirement correctly and if there >was a straightforward sql way to do the task To that end, I created a [quoted text clipped - 54 lines] >don't know it :-). This should be very fast since each query is going >for a full match match on the primary key. I'm thinking your understanding of what I'm looking for isn't quite right, but you code does give me some ideas. Is the goal of what you did to "short circuit" once the first row to meet the criteria is found? I would guess that this is probably better than what I was doing, which was getting all rows that matched but using only the first one. I will try utilizing your example.
Thanks! Frank
--- Frank Swarbrick Senior Developer/Analyst - Mainframe Applications FirstBank Data Corporation - Lakewood, CO USA
Serge Rielau - 30 Jun 2006 15:37 GMT > Below the first stored procedure I have written. It works as intended. > But, being my first one, I wanted to see if there are any obvious (or not so > obvious) flaws in it. (Yes, there's a reason, perhaps not a good one, why I > have the OUT fields before the INOUT fields.) The only conceptual flaw is that you declare the cursor as WITH RETURN, but you don't return a result set. You FETCH and CLOSE. So, either pass the result set back by leaving the cursor open (no fetch), or remove the WITH RETURN. In this case you could have used SELECT INTO here since you just got one row...
Cheers Serge
 Signature Serge Rielau DB2 Solutions Development IBM Toronto Lab
IOD Conference http://www.ibm.com/software/data/ondemandbusiness/conf2006/
Frank Swarbrick - 30 Jun 2006 19:04 GMT Serge Rielau<srielau@ca.ibm.com> 06/30/06 8:37 AM >>>
>> Below the first stored procedure I have written. It works as intended. >> But, being my first one, I wanted to see if there are any obvious (or not so [quoted text clipped - 7 lines] >In this case you could have used SELECT INTO here since you just got one >row... Cool. I've now changed it as follows:
CREATE PROCEDURE DB2DEV.GET_FEE_AMOUNT ( OUT amount DECIMAL(9,2), OUT descr VARCHAR(100), INOUT typ CHARACTER(3), INOUT subtype CHARACTER(3), INOUT bank DECIMAL(5,0), INOUT brch DECIMAL(3,0), INOUT acct DECIMAL(7,0) ) READS SQL DATA P1: BEGIN DECLARE CONTINUE HANDLER FOR NOT FOUND BEGIN SET typ = NULL; SET subtype = NULL; SET bank = NULL; SET brch = NULL; SET acct = NULL; -- VALUES (NULL, NULL, NULL, NULL, NULL) INTO typ, subtype, bank, brch, acct; END; SELECT FA.AMOUNT, FA.DESCR, FA.TYP, FA.SUBTYP, FA.BANK, FA.BRCH, FA.ACCT INTO amount, descr, typ, subtype, bank, brch, acct FROM DB2DEV.FEE_AMOUNTS AS FA WHERE (FA.TYP = typ AND FA.SUBTYP = subtype) AND ((brch <> 0 AND acct <> 0 AND FA.BRCH = brch AND FA.ACCT = acct) OR (brch <> 0 AND FA.BRCH = brch AND FA.ACCT = 0) OR (bank <> 0 AND FA.BANK = bank AND FA.BRCH = 0) OR (FA.BANK = 0 AND FA.BRCH = 0 AND FA.ACCT = 0)) ORDER BY FA.ACCT DESC, FA.BRCH DESC, FA.BANK DESC FETCH FIRST 1 ROW ONLY; END P1
One question. I read in the Application Development Guide: Programming Server Applications that using VALUES...INTO is more efficient, in general, then multiple SET statements. But it doesn't look like I can use the NULL keyword inside a VALUES statement. Is there an alternative?
Thanks a lot! I am glad I picked a somewhat less than simple example, as it causes me to learn more rather than just assume that "the one way" is "the only way".
Frank
--- Frank Swarbrick Senior Developer/Analyst - Mainframe Applications FirstBank Data Corporation - Lakewood, CO USA
Serge Rielau - 30 Jun 2006 19:31 GMT > One question. I read in the Application Development Guide: Programming > Server Applications that using VALUES...INTO is more efficient, in general, > then multiple SET statements. But it doesn't look like I can use the NULL > keyword inside a VALUES statement. Is there an alternative? Actually (see Josh's earlier post in another thread) DB2 V8.2 is pretty good about collapsing multiple SET statements into one VALUES INTO. So I guess that is some what outdated. In fact simple assignments, such as SET a = b; SET b = 3; SET c = c + 1; can be entirely processed within the PVM (like JVM, just for SQL PL) without doing any SQL. So they are actually better than VALUES INTO. A lot of the DB2 V8.2 performance improvements customers see are rooted in that fact.
Here is an interesting trivia on SELECT INTO. SET a = 0; SET a = (SELECT 1 FROM SYSIBM.SYSDUMMY1 WHERE 1 = 0; => a: NULL vs. SET a = 0; SELECT 1 INTO a FROM SYSIBM.SYSDUMMY1 WHERE 1 = 0; => a: 0
DB2 is fairly picky about where it allows untyped scalars (such as ? or NULL). You can always get through with: CAST(NULL AS <datatype>) or CAST(? AS <datatype>
Cheers Serge
 Signature Serge Rielau DB2 Solutions Development IBM Toronto Lab
IOD Conference http://www.ibm.com/software/data/ondemandbusiness/conf2006/
Frank Swarbrick - 30 Jun 2006 20:05 GMT >>>> Serge Rielau<srielau@ca.ibm.com> 06/30/06 12:31 PM >>> >> One question. I read in the Application Development Guide: Programming [quoted text clipped - 9 lines] >A lot of the DB2 V8.2 performance improvements customers see are rooted >in that fact. Sounds great. I will leave them as SET and be happy about it. :-)
>Here is an interesting trivia on SELECT INTO. >SET a = 0; [quoted text clipped - 8 lines] >NULL). You can always get through with: CAST(NULL AS <datatype>) or >CAST(? AS <datatype> I thought I'd save myself some typing, too, with VALUES, but... VALUES (CAST(NULL AS CHAR(3)), CAST(NULL AS CHAR(3)), CAST(NULL AS DECIMAL(5,0)), CAST(NULL AS DECIMAL(3,0)), CAST(NULL AS DECIMAL(7,0))) INTO typ, subtype, bank, brch, acct;
That doesn't save me anything! :-)
Thanks, Frank
--- Frank Swarbrick Senior Developer/Analyst - Mainframe Applications FirstBank Data Corporation - Lakewood, CO USA
Frank Swarbrick - 30 Jun 2006 20:38 GMT >>>> Frank Swarbrick<Frank.Swarbrick@efirstbank.com> 06/30/06 12:04 PM >>> >Cool. I've now changed it as follows: [quoted text clipped - 31 lines] > FETCH FIRST 1 ROW ONLY; >END P1 I just observed a problem with my code above. When a row matches it is changing the host variable at the time, so now the next comparison is done with the new value of the host variable. Oops! So now I've changed this to: P1: BEGIN DECLARE typ CHARACTER(3) DEFAULT NULL; DECLARE subtype CHARACTER(3) DEFAULT NULL; DECLARE bank DECIMAL(5,0) DEFAULT NULL; DECLARE brch DECIMAL(3,0) DEFAULT NULL; DECLARE acct DECIMAL(7,0) DEFAULT NULL;
SELECT FA.AMOUNT, FA.DESCR, FA.TYP, FA.SUBTYP, FA.BANK, FA.BRCH, FA.ACCT INTO amount, descr, typ, subtype, bank, brch, acct FROM DB2DEV.FEE_AMOUNTS AS FA WHERE (FA.TYP = GET_FEE_AMOUNT.typ AND FA.SUBTYP = GET_FEE_AMOUNT.subtype) AND ((GET_FEE_AMOUNT.brch <> 0 AND GET_FEE_AMOUNT.acct <> 0 AND FA.BRCH = GET_FEE_AMOUNT.brch AND FA.ACCT = GET_FEE_AMOUNT.acct) OR (GET_FEE_AMOUNT.brch <> 0 AND FA.BRCH = GET_FEE_AMOUNT.brch AND FA.ACCT = 0) OR (GET_FEE_AMOUNT.bank <> 0 AND FA.BANK = GET_FEE_AMOUNT.bank AND FA.BRCH = 0) OR (FA.BANK = 0 AND FA.BRCH = 0 AND FA.ACCT = 0)) ORDER BY FA.ACCT DESC, FA.BRCH DESC, FA.BANK DESC FETCH FIRST 1 ROW ONLY; VALUES(typ, subtype, bank, brch, acct) INTO GET_FEE_AMOUNT.typ, GET_FEE_AMOUNT.subtype, GET_FEE_AMOUNT.bank, GET_FEE_AMOUNT.brch, GET_FEE_AMOUNT.acct; END P1
Works much better!
Frank
--- Frank Swarbrick Senior Developer/Analyst - Mainframe Applications FirstBank Data Corporation - Lakewood, CO USA
|
|
|