Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
Home
Discussion Groups
Database Servers
DB2InformixIngresMS SQLOraclePervasive.SQLPostgreSQLProgressSybase
Desktop Databases
FileMakerFoxProMS AccessParadox
General
General DB TopicsDatabase Theory
Related Topics
Java Development.NET DevelopmentVB DevelopmentMore Topics ...

Database Forum / DB2 Topics / June 2006

Tip: Looking for answers? Try searching our database.

Stored Procedure from newbie

Thread view: 
Enable EMail Alerts  Start New Thread
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
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.