Subject | Re: Stored Procedures |
---|---|
Author | flipmooooo |
Post date | 2005-02-08T13:29:35Z |
--- In firebird-support@yahoogroups.com, Helen Borrie <helebor@t...>
wrote:
Thanks alot for the detailed info regarding stored procedures. I hope
you don't mind, i just have a few more questions to ask to make
things clear and get me started :)
- 'EXECUTE PROCEDURE SET_STATUS_ID (:AID)' has to be enclaused in a
Begin/end block to act on exceptions occuring in the
execution 'block' and 'when' exception handeling must follow
immediatly after.
- When u have multiple 'when' clauses one after another they all act
on the same execution 'block' ('EXECUTE PROCEDURE SET_STATUS_ID
(:AID)')
- when u have created custome made exceptions (SKIP_ROW) how can i
handle the exception in a general way
WHEN EXCEPTION SKIP_ROW,EXCEPTION UNBALANCED_DOC DO
BEGIN
MyException = EXCEPTION.RDB$EXCEPTION_NAME;
MyExceptionText = EXCEPTION.RDB$MESSAGE;
END
Greetings,
Filip Moons
CREATE PROCEDURE SET_STATUS (
SESSIONID integer)
RETURNS (ERRORTEXT VARCHAR(40))
AS
DECLARE VARIABLE AID INTEGER;
DECLARE VARIABLE ACODE VARCHAR(8);
DECLARE VARIABLE ABEDRAG NUMERIC(15,2);
DECLARE VARIABLE ASTATUS SMALLINT;
DECLARE VARIABLE ERRORCOUNT INTEGER;
BEGIN
ERRORCOUNT = 0;
ERRORTEXT = 'No errors';
FOR
SELECT TSTID,TSTCODE,TSTBEDRAG,TSTSTATUS
FROM TEST
ORDER BY TSTID
INTO :AID,:ACODE,:ABEDRAG,:ASTATUS
DO
BEGIN
BEGIN
EXECUTE PROCEDURE SET_STATUS_ID (:AID);
END
WHEN GDSCODE DeadLock, GDSCODE lock_conflict DO
BEGIN
ERRORCOUNT = ERRORCOUNT + 1;
INSERT INTO TMPLOG
(TMPRESID,TMPREOID,TMPREUSERNAME,TMPREGDSERROR) VALUES
(:SESSIONID,:AID,USER,GDSCODE);
END
WHEN EXCEPTION SKIP_ROW DO
BEGIN
ERRORCOUNT = ERRORCOUNT + 1;
INSERT INTO TMPLOG
(TMPRESID,TMPREOID,TMPREUSERNAME,TMPREEXCEPTION) VALUES
(:SESSIONID,:AID,USER,'SKIP_ROW');
END
END
IF (ERRORCOUNT > 0) THEN
ERRORTEXT = CAST(ERRORCOUNT AS VARCHAR(9))||' updates skipped -
see log for details.';
END
wrote:
> At 05:54 PM 4/02/2005 +0000, you wrote:catch
>
> >I'm a new in using stored procedures in firebird. I'm trying to
> >'Exceptions' in a stored procedure in such way it will skip rowsfor
> >whichcompletes its
> >the exception occured but will continue to loop the remaining rows.
>
> OK, first understand, without exception handling, a SP either
> work entirely without exceptions and can then returns control toclient, to
> be committed or rolled back by the client in its entirety; or itfails on
> the first exception and returns to the client with an exceptionstructure,
> structure. Return values are returned on success, an error
> consisting of an error code and a message, are returned onfailure. That
> is, there is no way that putting exception stuff into a procedure'sreturn
> values will return those details to the client.place,
>
> Inside the procedure, you can use a WHEN handler at the appropriate
> to swallow an exception and/or write a log message.exception
>
> A WHEN ANY handler will do its stuff regardless of what the
> was. You can get much finer control of the exceptions trapped bywriting
> handlers that trap specific SQLCodes and gdscodes. You can alsocreate
> custom exceptions and throw them conditionally from the precedingcode
> block, having a corresponding WHEN block following that executionblock to
> trap it.tables. I have
>
> >The body of the procedure consists of 2 updates on 2 different
> >3 rows in table 1 and 3 rows in table 2. When fi row 2 from table2 is in a
> >state that it has been updated but not committed a deadlock willoccur when
> >the procedure is executed by some other user.2
>
> Correct.
>
> >After executing the procedure I get 3 updated rows in table 1 and
> >updated rows in table 2, so I have inconsistent data in my db.be
>
> Not correct. If you allow the exception to occur, the work cannot
> committed. You will keep your locks on those rows until you rollback the
> transaction but the database state isn't changed.be 'undone'
>
> >I thought that everything within the begin..end block would
> >when an exception occured.whatever way
>
> You can write exception handlers to make execution behave in
> you need it to. If you want your procedure to complete bothupdates ONLY
> if both updates can succeed then isolate the two operations insideone
> begin...end block and place the WHEN handler immediately following*that*
> block. The exception can be made to "skip over" the faulty pair ofthe
> operations by just writing a log record -- or doing nothing. After
> handler has executed, control moves to the line immediatelyfollowing the
> begin...end block defined for WHEN whatever DO.a loop,
>
> If the block where the exception occurred was a repeating block in
> then that iteration of the block is undone and execution resumes atthe
> next iteration. Of course, the loop itself should not have anycode
> following the exception handler, or you will come unstuck.updates, if
>
> >What i want to achieve is to loop each row in table 1, do the
> >all updates succeeded, continue looping. When some exceptionoccured
> >within the for select loop, ALL changes have to be 'undone' andcontinue
> >looping.and use
> >
> >CREATE PROCEDURE SET_STATUS (
> > RETURNERRORS SMALLINT)
> >RETURNS (
> > ERRORCODE VARCHAR(20),
> > ERRORVALUE INTEGER,
> > ERRORTEXT VARCHAR(80))
> >AS
> >DECLARE VARIABLE AID INTEGER;
> >DECLARE VARIABLE ACODE VARCHAR(8);
> >DECLARE VARIABLE ABEDRAG NUMERIC(15,2);
> >DECLARE VARIABLE ASTATUS SMALLINT;
>
> No. It's fine to have a FOR SELECT loop to iterate through a set
> those data for DML operations, but this is not a selectprocedure. This
> has to be called with EXECUTE PROCEDURE and, if it succeeds, youcan
> optionally return a value set to the client. But it will be asiingle-row,
> global set.references:
>
> >begin
> > FOR
> > SELECT TSTID,TSTCODE,TSTBEDRAG,TSTSTATUS
> > FROM TEST
> > ORDER BY TSTID
> > INTO AID,ACODE,ABEDRAG,ASTATUS
>
> This syntax is wrong. You need colons in front of the variable
>execution
> INTO :AID,:ACODE,:ABEDRAG,:ASTATUS
>
> > DO
> > BEGIN <--- This block must isolate the two operations
> > UPDATE TEST
> > SET
> > TSTSTATUS = 1
> > WHERE
> > TSTID = :AID;
> >
> > UPDATE TEST_CODE
> > SET
> > TCBEDRAG = TCBEDRAG + :ABEDRAG
> > WHERE
> > TCCODE = :ACODE;
> end <----------------------------- closes the execution block
> Now handle the exceptions:
> > WHEN ANY DO
> > BEGIN <------------- starts the exception handler
>
> > /* if (RETURNERRORS <> 0) then
> > BEGIN
>
> > ERRORCODE = 'SKIPPED';
> > ErrorValue = GDSCODE;
> > ErrorText = CAST(AID as VARCHAR(80));
>
> > /* SUSPEND; No!! not valid */
>
> /* Do nothing, or write a record to an errorlog */
>
> > END */
>
> > END <----- end of the exception handler
> > END <------------ end of the FOR loop. After an exception,
> > will resume at the next iteration of the loop and the updates inthe
> > enclosed block will have been undone. Locks remain on theaffected rows.
>a
> >end <---------- end of the procedure
>
>
> If you want a trace of the failed updates, write the details out to
> log. As an example of a log,you can
>
> create table errorlog (
> TransacID BigInt,
> Tstamp timestamp,
> Username varchar(32),
> RowID integer,
> Msg varchar(80),
> GDSError integer);
> commit;
>
> The following code does that logging:
>
> CREATE PROCEDURE SET_STATUS
> RETURNS (
> ERRORTEXT VARCHAR(40))
> AS
> DECLARE VARIABLE AID INTEGER;
> DECLARE VARIABLE ACODE VARCHAR(8);
> DECLARE VARIABLE ABEDRAG NUMERIC(15,2);
> DECLARE VARIABLE ASTATUS SMALLINT;
> DECLARE VARIABLE ERRORCODE CHAR(9);
> DECLARE VARIABLE ERRORCOUNT = 0;
> begin
> ERRORTEXT = 'No errors';
> FOR
> SELECT TSTID,TSTCODE,TSTBEDRAG,TSTSTATUS
> FROM TEST
> ORDER BY TSTID
> INTO :AID,:ACODE,:ABEDRAG,:ASTATUS
> DO
> BEGIN
> UPDATE TEST
> SET
> TSTSTATUS = 1
> WHERE
> TSTID = :AID;
>
> UPDATE TEST_CODE
> SET
> TCBEDRAG = TCBEDRAG + :ABEDRAG
> WHERE
> TCCODE = :ACODE;
> END
> WHEN ANY DO
> BEGIN
> ERRORCOUNT = ERRORCOUNT + 1;
> ERRORCODE = CAST(GDSCODE AS CHAR(9));
> INSERT INTO ERRORLOG VALUES(
> CURRENT_TRANSACTION,
> CAST('NOW' AS TIMESTAMP),
> USER,
> :AID,
> 'SKIPPED',
> :ERRORCODE);
> END
> END /* end of the FOR loop */
> IF (ERRORCOUNT > 0) THEN
> ERRORTEXT = CAST(ERRORCOUNT AS VARCHAR(9) ||
> ' updates skipped - see log for details.'
> end
>
> You can do smarter stuff than this - create custom exceptions so
> write better info to the log (or back to the user, in cases whereyou want
> an exception to cause execution to stop altogether); call aWRITE_LOG
> procedure that can be called by all of your procedures; even keepa table
> of custom messages for frequently occurring GDSCODEs to enable youto write
> something meaningful in the log other than the gdscodenumber....and so on.
>as "nested
> You can use nests of code and exception blocks to devise meaningful
> "savepoint logic" in your more complex SPs - sometimes known
> transactions". From Firebird 1.5 onward, you can also re-raise anexternal
> exception (see the 1.5 releasenotes for this.)
>
> If you want a log that will survive a failed execution, use an
> file, not a database table, for the log.Hi Helen,
>
> ./heLen
Thanks alot for the detailed info regarding stored procedures. I hope
you don't mind, i just have a few more questions to ask to make
things clear and get me started :)
- 'EXECUTE PROCEDURE SET_STATUS_ID (:AID)' has to be enclaused in a
Begin/end block to act on exceptions occuring in the
execution 'block' and 'when' exception handeling must follow
immediatly after.
- When u have multiple 'when' clauses one after another they all act
on the same execution 'block' ('EXECUTE PROCEDURE SET_STATUS_ID
(:AID)')
- when u have created custome made exceptions (SKIP_ROW) how can i
handle the exception in a general way
WHEN EXCEPTION SKIP_ROW,EXCEPTION UNBALANCED_DOC DO
BEGIN
MyException = EXCEPTION.RDB$EXCEPTION_NAME;
MyExceptionText = EXCEPTION.RDB$MESSAGE;
END
Greetings,
Filip Moons
CREATE PROCEDURE SET_STATUS (
SESSIONID integer)
RETURNS (ERRORTEXT VARCHAR(40))
AS
DECLARE VARIABLE AID INTEGER;
DECLARE VARIABLE ACODE VARCHAR(8);
DECLARE VARIABLE ABEDRAG NUMERIC(15,2);
DECLARE VARIABLE ASTATUS SMALLINT;
DECLARE VARIABLE ERRORCOUNT INTEGER;
BEGIN
ERRORCOUNT = 0;
ERRORTEXT = 'No errors';
FOR
SELECT TSTID,TSTCODE,TSTBEDRAG,TSTSTATUS
FROM TEST
ORDER BY TSTID
INTO :AID,:ACODE,:ABEDRAG,:ASTATUS
DO
BEGIN
BEGIN
EXECUTE PROCEDURE SET_STATUS_ID (:AID);
END
WHEN GDSCODE DeadLock, GDSCODE lock_conflict DO
BEGIN
ERRORCOUNT = ERRORCOUNT + 1;
INSERT INTO TMPLOG
(TMPRESID,TMPREOID,TMPREUSERNAME,TMPREGDSERROR) VALUES
(:SESSIONID,:AID,USER,GDSCODE);
END
WHEN EXCEPTION SKIP_ROW DO
BEGIN
ERRORCOUNT = ERRORCOUNT + 1;
INSERT INTO TMPLOG
(TMPRESID,TMPREOID,TMPREUSERNAME,TMPREEXCEPTION) VALUES
(:SESSIONID,:AID,USER,'SKIP_ROW');
END
END
IF (ERRORCOUNT > 0) THEN
ERRORTEXT = CAST(ERRORCOUNT AS VARCHAR(9))||' updates skipped -
see log for details.';
END