Skip to Main Content
  • Questions
  • Update Statement and Exception Handling

Breadcrumb

XMAS_TREE

The AskTOM team is taking a break over the holiday season, so we're not taking questions or responding to comments, but we might still sneak in a few Office Hours sessions! Thanks for being a member of the AskTOM community.

Question and Answer

Chris Saxon

Thanks for the question, Mo.

Asked: October 17, 2002 - 10:11 pm UTC

Last updated: June 11, 2020 - 3:07 pm UTC

Version: 8.1.7

Viewed 50K+ times! This question is

You Asked

Tom:

1. I have several PL/SQL procedures that use several update statments. After each update, I use the
IF (sql%rowcount > 0) THEN
update 2nd table
END IF;

and so on. I am doing a lot of IFs and I am wondering whether I cao commit all updates at once or rollback. If updates results in 0 rows updated would it raise any excpetion? Any recommendations here.

2. If I have two tables that has many-to-many relationship and thus
are implemented using three table design with many to one to the intermediate table.

Since the intermediate table has foreign keys to both tables, I can not seem to updated the parent when a user wants to change the column value in the parent. Would you suggest here using delete the child record and then updating the parent and then re-inserting the child with the new value, or using update_cascade? Any recommendations.

3. THere is a database that I select/update from that uses several columns for primary keys. SOmetime 8 columns and each is about 10 to 20 characters. ANyway, I notice that my procedure is slow when I try to do something with the database. Do yo uthink it might be these long primary keys and it would be better to use one sequence instead.

Thank you,


Thank you,


and Tom said...

You can commit all updates at one or rollback all updates at once, yes.

There are NO exceptions when 0 rows are updated-- that is NOT an error. If is the right way to catch that.

2) You don't have a primary key then -- a primary key never never never never never changes.

Consider using a surrogate key -- ID -- populated via a sequence. It'll never change -- no more problem.

3) I seriously doubt it is solely due to an 8 column key -- you need to diagnose the issue, never guess. Use the tools provided

</code> http://asktom.oracle.com/~tkyte/tkprof.html <code>

being the place to start.....


I like surrogate keys personally. An 8 column key just doesn't sit right with me.

Rating

  (32 ratings)

We're not taking comments currently, so please try again later if you want to add a comment.

Comments

update

mo, November 28, 2003 - 4:58 pm UTC

Tom:

Is there a better way to do this. I want to update 3 tables and commit only if 3 updates ran fine otherwise rollbacK. Her eis how I do it but is creating a meshy code:

update table 1 set col1.....

If (sql%rowcount > 0) Then
update table 2 set col1 .....
else
raise update-failed;
end if;

IF (sql%rowcount > 0) Then
update table 3 set col11.....
else
raise update-filed;
end if;

commit;

exceptions
WHEN update-failed then
rollback;

end;

Tom Kyte
November 28, 2003 - 5:34 pm UTC

well, it is only according to you that an update of zero rows is a "failure".

updating zero rows to me is total success, just means there wasn't anything to update!

Seems strange that you would update three rows and care that you updated three rows having never read them out??? if you read them out (with select for update), you would have no such worries and would not suffer from lost updates (as you stand a chance of now -- you are basically "shooting at deer in the dark and hoping you hit some" apparently).


begin
update t1
t1_updated := sql%rowcount > 0;
update t2
t2_updated := sql%rowcount > 0;
update t3
t3_updated := sql%rowcount > 0;
if ( not t1_updated or not t2_updated or not t3_updated )
then
raise failure;
end if;
end;

is all you need. as long as the invoker of this does not catch the error and "hide it", it'll rollback all by itself (if you have my book expert one on one oracle -- i go over transactions and how they work in some detail)

update

mo, December 03, 2003 - 4:49 pm UTC

Tom:

Thanks for the neat way of doing the transactional update. But you say do "select for update" and I understood that this will not work for web applications and it is only for client server apps.

do you do also "select for update" before actual "update" when you are updating via mod_plsql? Do you do that to lock the record until you are done with all of your updates for other tables?

THanks,

Tom Kyte
December 03, 2003 - 6:57 pm UTC

we use optimistic concurrency controls.

search this site for those words

or if you have my book expert one on one oracle -- i go over that in some detail in there in the chapter on transactions

transactional insert/update

mo, June 28, 2004 - 12:41 pm UTC

Tom:

From your comments above, what way do you use to commit/rollback a transaction.

Let us say I have three tables (CUSTOMER, ORDER, ORDER_ITEMS).

I have a web page where user enters information.

I want to set it up so that the three inserts are committed or if one insert fails to rollback.

However let us say User filled out the customer info and order info and skipped the items.

Now because I check in my code not to insert if values are null, I do not want to rollback too since it is not a fail on an insert too.

How do you usually implement transactional inserts/updates using web pages.

Tom Kyte
June 28, 2004 - 1:13 pm UTC

this does not make sense:

..
Now because I check in my code not to insert if values are null, I do not want
to rollback too since it is not a fail on an insert too.
.....


I create API's, these API's take a set of inputs, do whatever they need to perform a consistent change of the database. The caller of the API then decides "commit or rollback" (never commit in the API, it'll prevent reuse of your API in a larger transaction). A page is a transaction to me, each and ever submit is a single transaction.

insert

mo, June 28, 2004 - 3:00 pm UTC

Tom:

1. Can you give me a small example of an API to do that like you say?

2. I think I am mixing apples with oranges. Is not true that whenever an INSERT statement fails (foreign key issue or data type etc), an internal exception will be raised and I can code "WHEN OTHERS" to rollback the transaction?

3. If Item 2 is true then I do not really need to check the SQL%rowcount for every insert step? is this true too.

Thank you,

Tom Kyte
June 28, 2004 - 3:13 pm UTC

1) huh? just functions like:


function save_question(
p_email in varchar2,
p_fname in varchar2,
p_lname in varchar2,
p_location in varchar2,
p_job_title in varchar2,
p_org in varchar2,
p_category in varchar2,
p_version in varchar2,
p_years in varchar2,
p_subject in varchar2,
p_notify in varchar2,
p_question in varchar2 )
return number;


procedure admin_update_question(
p_id in varchar2,
p_subject in varchar2,
p_answer in varchar2,
p_status in varchar2 );

and so on. a simple stored procedure that takes as input everything it needs to perform a multi-statement transaction.... a typical "API"


2) it is my 100% commited belief that transaction control belongs at the top level, at the caller. they and only they should control commit/rollback. I do not believe at the low level you would ever consider catching a when others and rolling back. You would let the exception propagate up to the top layer. If NO ONE catches it -- the statement (all work performed by that statement, which is your plsql call in fact) is automagically rolled back. If someone at the top does catch it and "hides" it, they would have to rollback themselves if that is the right thing to do.

but again, i don't program that way myself. If you have expert one on one Oracle, i wrote about this.

3) that makes no sense to me. sql%rowcount is the count of rows inserted. if there were an error, you'd never get to that point, an exception would be raised.

if you have an insert ... values - sql%rowcount will be 1. if you have an insert as select, sql%rowcount will be 0..anything -- 0 is not an error, 1 is not an error, 1000 is not an error. sql%rowcount isn't really for error checking.

update

mo, June 28, 2004 - 8:42 pm UTC

Tom:

From your example, I guess I am doing everything as an API. I do not understand why you thought otherwise. I do not understand why you return a number in the procedure.

I see your point that SQL%ROWCOUNT is not really for error checking.

I will explain how I do things in a simple example and can you please tell me what problems do you see with it and how to correct it.


My conclusion from your previous answer is that I only need to delete the check for SQL%rowcount.


PROCEDURE NEW_REQUEST
(i_applicant_id VARCHAR2 DEFAULT NULL,
i_user_id VARCHAR2 DEFAULT NULL )

IS

my_variables

BEGIN

MY_HTML statements to create a blank data entry form.

htp.print('<form action="save_request" method="post" name="request_form" >');
htp.print(' <table width="100%" border="0">');
htp.formHidden('i_applicant_id',i_applicant_id);
htp.formHidden('i_user_id',i_user_id);

END;




PROCEDURE SAVE_REQUEST (
button IN VARCHAR2 DEFAULT NULL,
i_applicant_id IN VARCHAR2 DEFAULT NULL,
.....
.....
....
....
i_user_id IN VARCHAR2 DEFAULT NULL )

IS

my_variables


BEGIN

SELECT request_seq.nextval INTO v_request_id FROM dual;


INSERT into REQUEST (Request_ID,
applicant_id
user_id,
creation_date )

VALUES
(v_request_id,
i_applicant_id,
i_user_id,
sysdate );

IF (SQL%rowcount = 0) THEN
raise record_not_saved;
END IF;

--SAVE the material requested.

FOR i IN 1..i_material_id.count
LOOP


IF (i_material_id(i) is not null) THEN

INSERT into requested_item (request_id,
item_number,
material_id,
quantity_requested,
user_id,
creation_date)
VALUES
(v_request_id,
v_item_number,
i_material_id(i),
i_quantity_requested(i),
i_user_id,
sysdate );

v_item_number := v_item_number+1;
END IF;



END LOOP;

IF (SQL%rowcount = 0) THEN
raise record_not_saved;

END IF;

COMMIT;

--Print the save confirmation.

HTML confirmation page


EXCEPTION


WHEN DUP_VAL_ON_INDEX THEN
ROLLBACK;
htp.p('<FONT COLOR="BROWN">');
htp.p('<BR><PRE>');
htp.big('Record with this primary key already exists in table.' );

htp.p('<BR></FONT></PRE>');

WHEN RECORD_NOT_SAVED THEN
ROLLBACK;
htp.p('<FONT COLOR="BROWN">');
htp.p('<BR><PRE>');
htp.big('Record was not saved. ');
htp.p('<BR></FONT></PRE>');

END; --Save_Request




Tom Kyte
June 29, 2004 - 7:40 am UTC

i didn't think otherwise. I had no thoughts on your approach. You asked simply:

<quote>
How do you usually implement transactional inserts/updates using web pages
</quote>

and I responded "API"




PROCEDURE SAVE_REQUEST (
button IN VARCHAR2 DEFAULT NULL,
i_applicant_id IN VARCHAR2 DEFAULT NULL,
.....
i_user_id IN VARCHAR2 DEFAULT NULL )
IS
my_variables
BEGIN
SELECT request_seq.nextval INTO v_request_id FROM dual;

INSERT into REQUEST (Request_ID, applicant_id user_id, creation_date )
VALUES (v_request_id, i_applicant_id, i_user_id, sysdate );
Never select a sequence from dual only to turn around and send it right back! simply:

insert into t ... values( s.nextval)

and if you need the sequence value, use the RETURNING clause of the insert


IF (SQL%rowcount = 0) THEN
raise record_not_saved;
END IF;

as stated, that condition will *never* happen. if the insert failed, that code would never execute

FOR i IN 1..i_material_id.count
LOOP
IF (i_material_id(i) is not null)
THEN
INSERT into requested_item (request_id, item_number, material_id,
quantity_requested, user_id, creation_date)
VALUES (v_request_id, v_item_number, i_material_id(i),
i_quantity_requested(i), i_user_id, sysdate );
v_item_number := v_item_number+1;
END IF;
END LOOP;

as stated, this code is not worthwhile...
IF (SQL%rowcount = 0) THEN
raise record_not_saved;
END IF;

I firmly believe transactional control belongs elsewhere. what if someone needs to call "save_record" and
"something_else" as a transaction -- you have made it impossible. the caller should do that

COMMIT;

you don't have a clean API if you are doing gui stuff in it. I would not have that there
--Print the save confirmation.

HTML confirmation page

I would let the caller handle all exceptions. they can rollback, they can do whatever. Again, gui in the api, what
if tomorrow someone wants to call this as a web service, not as a mod_plsql thingy.


EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN


update

mo, July 02, 2004 - 12:13 pm UTC

Tom:

I implemented your suggestions. It is a good idea to separate the function(insert/update) from presentation. However I do not understand some of these:

1. Why you do not like to select a sequence from dual.

2. After I commit I print the Confirmatin page using:

PRINT_form(v_test_no,i_user_id);

Problem is that when you refresh you will run the insert again which I do not understand.

When I do it this way, It will not run the insert function.

htp.p( '<HTML><HEAD>');
htp.p(' <META HTTP-EQUIV="Refresh"
CONTENT="0;URL=package.print_form?i_test_no='||v_test_no||
'&i_user_id='||i_user_id||'">');
htp.p('</HEAD><body></body></html>');

How do you explain this and how do you suggest to do it?

3. What is Web Service? I hear about it but do really understand what it is?

Thank you,

Tom Kyte
July 02, 2004 - 12:28 pm UTC

1) because it is much less efficient to do two sql's rather than one.

ops$tkyte@ORA9IR2> create table t ( x int );
 
Table created.
 
ops$tkyte@ORA9IR2> create sequence s;
 
Sequence created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_id  number;
  3  begin
  4          select s.nextval into l_id from dual;
  5          insert into t values ( l_id );
  6          dbms_output.put_line( l_id );
  7  end;
  8  /
1
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_id  number;
  3  begin
  4          insert into t values ( s.nextval ) returning x into l_id;
  5          dbms_output.put_line( l_id );
  6  end;
  7  /
2
 
PL/SQL procedure successfully completed.


the second case there is really nice, less code, tastes great -- uses less resources and definitely helps with 90/10 block splits (if you pause in between "get sequence" and "insert sequence", large chance someone else inserts a sequence out of order -- meaning we don't see it as a monotomically increasing value and we'd do a 60/40 split - -meaning the index would grow faster then it needed to).


2) the calling, controlling procedure should:

   a) call your transactional API (which contains NO GUI logic)
   b) commit (or rollback) as it sees fit
   c) do gui stuff.

what does "refresh" has to do with anything?  not following you there.  why 'refresh'.  

3) google it.

"web service"

on google will return you the answer...... 

mo, July 02, 2004 - 4:02 pm UTC

Tom:

<meaning we don't see it as a monotomically increasing
value and we'd do a 60/40 split>

1. Does this mean less gaps in sequence value or no?

2. I mean when you display the HTML to the user to confirm the "INSERT". If he hits refresh on the browser it will do another insert into the table. while the second case where you call the URL does not do it. Some users hit "refresh" without realizing it runs the API twice.

Thanks,

Tom Kyte
July 02, 2004 - 4:44 pm UTC

1) no, means less whitespace in the index structure.

2) i already told you how to solve problems like that -- i don't care how you write you code

a) modular as I suggest with separation of pieces
b) as a stream of stuff

you have the same exact issues, i fail to see how one technique solves it where the other cannot.

mo, July 02, 2004 - 4:54 pm UTC

Tom:

I did it your way but I think you miss my point:

1) the calling, controlling procedure should:
This is the Data Entry Form (data_entry).
a) call your transactional API (which contains NO GUI logic)
I submit data_entry to "Save_Data_Entry". This is where my INSERT/commit is.
b) commit (or rollback) as it sees fit
"Save_Data_Entry".
c) do gui stuff.
This is "Print_Data_Entry". This is getting called from "SAve_Data_Entry" after I commit.

YOu have to call the gui from somewhere (Save_Data_Entry) and this is where it makes a difference between a regular call and a URL call when user hits a refresh.

Is not this transactional like you proposed?


Tom Kyte
July 02, 2004 - 6:58 pm UTC

b) doesn't fit in my scheme of things. wrong place

c) save_data_entry should not be calling print_data_entry, the controlling process should be doing what you say your api is doing.




select and update vs update

sara, April 05, 2007 - 1:17 pm UTC

Which way is better?
You issue a select statement to find if the record exists
and then do an update if the record exists
OR
directly update and check with sql%rowcount
if sql%rowcount <= 0 then
raise exception...
end if;
there is no FK relationships or checks during update

Tom Kyte
April 05, 2007 - 1:51 pm UTC

depends - in general, it sounds like you are doing a "blind update" - updating data without having ever looked at it.


If you read the data out at some point, looked at it, made a decision in your code to modify it - you should just update it - but in a fashion that ensures that a lost update does not occur, eg:


update t
set x = :x, y = :y
where primary_key = :pk
and decode( x, :old_x, 1 ) = 1
and decode( y, :old_y, 1 ) = 1


if you are just doing a blind update (you never read the data in the first place), I would question your logic - and in the rare case that such an event should happen - you would just update it.


So, just update it - but do so in a manner that does not suffer from the lost update problem (make sure the record did not change between the time you read it out, made a decision to modify it, and wrote it back).


Don't query it to see if it exists to update - by the time you go to update it, it might not exist anymore.

update

A reader, April 13, 2007 - 1:28 pm UTC

excellent

update

sam, August 19, 2007 - 11:46 am UTC

Tom:

What is the best way to do this.

I have a client program (http client or web page) that sends employee info to the database for update. Let us say it send address information (empid, street_address,city,state,zip).

My stored procedure should perform the update and send a confirmation to the client that "Updates Saved" or "Updates Failed".

My thinking is as follows:

1. check if the record exist or not by doing
select count(*)into l_Cnt from emp where empid=p_empid;

2. If record does not exist raise NO_DATA_FOUND, and alert program this person does not exist.
3. If person exists, then do
update emp set ...... where empid=p_empid;
4. Now here, can I say if
sql%rowcount = 0 then row was not updated and alert user about failure
sql%rowcount = 1 then row was updated and alert user about success

Basically I want to code something that tells me whether update happened as expected or not.

Are these the steps you would do. Can you show a small sample on how you would do it and what exceptions you will be checking for for this kind of update.

Thank you,
Tom Kyte
August 22, 2007 - 10:26 am UTC

Actually, you need to do much better than that.

You need to perform lost update detection.

We must presume (in order to update), you read the data out at some point...

basically, you will:
Update table
   Set column1 = :new_column1, column2 = :new_column2, ....
 Where primary_key = :primary_key
   And column1 = :old_column1 
   And column2 = :old_column2


and check sql%rowcount (in plsql - or use whatever your API provides to tell you how many rows were updated) to see if you actually succeeded.


to read about this update in context:


<quote src=Expert Oracle Database Architecture>

Lost Updates
A lost update is a classic database problem. Actually, it is a problem in all multiuser computer environments. Simply put, a lost update occurs when the following events occur, in the order presented here:
1. A transaction in Session1 retrieves (queries) a row of data into local memory and displays it to an end user, User1.
2. Another transaction in Session2 retrieves that same row, but displays the data to a different end user, User2.
3. User1, using the application, modifies that row and has the application update the database and commit. Session1¿s transaction is now complete.
4. User2 modifies that row also, and has the application update the database and commit. Session2¿s transaction is now complete.
This process is referred to as a ¿lost update¿ because all of the changes made in step 3 will be lost. Consider, for example, an employee update screen that allows a user to change an address, work number, and so on. The application itself is very simple: a small search screen to generate a list of employees and then the ability to drill down into the details of each employee. This should be a piece of cake. So, we write the application with no locking on our part, just simple SELECT and UPDATE commands.
Then an end user (User1) navigates to the details screen, changes an address on the screen, clicks Save, and receives confirmation that the update was successful. Fine, except that when User1 checks the record the next day to send out a tax form, the old address is still listed. How could that have happened? Unfortunately, it can happen all too easily. In this case, another end user (User2) queried the same record just after User1 did¿after User1 read the data, but before User1 modified it. Then after User2 queried the data, User1 performed her update, received confirmation, and even requeried to see the change for herself. However, User2 then updated the work telephone number field and clicked Save, blissfully unaware of the fact that he just overwrote User1¿s changes to the address field with the old data! The reason this can happen in this case is that the application developer wrote the program such that when one particular field is updated, all fields for that record are ¿refreshed¿ (simply because it¿s easier to update all the columns instead of figuring out exactly which columns changed and only updating those).
Notice that for this to happen, User1 and User2 didn¿t even need to be working on the record at the exact same time. They simply needed to be working on the record at about the same time.
I¿ve seen this database issue crop up time and again when GUI programmers with little or no database training are given the task of writing a database application. They get a working knowledge of SELECT, INSERT, UPDATE, and DELETE and then set about writing the application. When the resulting application behaves in the manner just described, it completely destroys a user¿s confidence in it, especially since it seems so random, so sporadic, and it is totally irreproducible in a controlled environment (leading the developer to believe it must be user error).
Many tools, such as Oracle Forms and HTML DB, transparently protect you from this behavior by ensuring the record is unchanged from the time you query it and locked before you make any changes to it, but many others (such as a handwritten Visual Basic or Java program) do not. What the tools that protect you do behind the scenes, or what the developers must do themselves, is use one of two types of locking strategies: pessimistic or optimistic.

.................

Optimistic Locking
The second method, referred to as optimistic locking, defers all locking up to the point right before the update is performed. In other words, we will modify the information on the screen without a lock being acquired. We are optimistic that the data will not be changed by some other user; hence we wait until the very last moment to find out if we are right.
This locking method works in all environments, but it does increase the probability that a user performing an update will ¿lose.¿ That is, when that user go to update her row, she finds that the data has been modified, and she has to start over,
One popular implementation of optimistic locking is to keep the old and new values in the application, and upon updating the data use an update like this:
Update table
   Set column1 = :new_column1, column2 = :new_column2, ....
 Where primary_key = :primary_key
   And column1 = :old_column1 
   And column2 = :old_column2

...
Here, we are optimistic that the data doesn¿t get changed. In this case, if our update updates one row, we got lucky; the data didn¿t change between the time we read it and the time we got around to submitting the update. If we update zero rows, we lose; someone else changed the data and now we must figure out what we want to do to continue in the application. Should we make the end user rekey the transaction after querying the new values for the row (potentially causing the user frustration, as there is a chance the row will have changed yet again)? Should we try to merge the values of the two updates by performing update conflict-resolution based on business rules (lots of code)?
The preceding UPDATE will, in fact, avoid a lost update, but it does stand a chance of being blocked¿hanging while it waits for an UPDATE of that row by another session to complete. If all of your applications use optimistic locking, then using a straight UPDATE is generally OK since rows are locked for a very short duration as updates are applied and committed. However, if some of your applications use pessimistic locking, which will hold locks on rows for relatively long periods of time, then you will want to consider using a SELECT FOR UPDATE NOWAIT instead, to verify the row was not changed and lock it immediately prior to the UPDATE to avoid getting blocked by another session.
There are many methods of implementing optimistic concurrency control. We¿ve discussed one whereby the application will store all of the before images of the row in the application itself. In the following sections, we¿ll explore three others, namely
* Using a special column that is maintained by a database trigger or application code to tell us the ¿version¿ of the record
* Using a checksum or hash that was computed using the original data
* Using the new Oracle 10g feature ORA_ROWSCN
</quote>

update

sam, August 24, 2007 - 10:45 pm UTC

Tom:

1. If the table has 30 columns would you still do optimistic locking? The update where caluse would be pain correct?

for web apps would you ever use "select record for update"

2. Do you think selecting the data after update is better confirmation than reading sql%rowncount for example:

update emp
set street_address = p_street_address,
city = p_city,
state = p_state,
zip = p_zip
where empId = v_EmpID
returning street_address, city, state, zip
into v_street, v_city, v_state, v_zip ;

if v_street = p_street_addres and v_city = p_city and v_state = p_state and v_zip then
commit;

return 'Update Success !';
else
rollback;
return 'Update Failed';
end if;

Tom Kyte
August 27, 2007 - 4:08 pm UTC

1) we write software to do things correctly. If your attitude is "wow, that'll be hard for ME (the human programmer) so I won't do it" - please don't write any code I have to use.

My bank, I hope they do not take that approach "wow, protecting the integrity of data is so hard for the coder, we should just skip it - the customers will probably never notice"


(can you tell that statement by you just FLOORED ME, among other emotions)

2) umm, look at your logic. So many things wrong with it.

You just updated address, city, state and zip to some values and you GET THOSE VALUES RETURNED TO YOU (what you input, you get as output).

then you compare the inputs to the outputs and see if they match. Hmmm, but if the outputs are simply your inputs.....

Well, they would always match UNLESS one of the inputs was null then it would be unknown if they match

but in any case the TRUE/FALSE or UNKNOWN-ness of you "if" statement would do nothing to protect data integrity.


you will either:

a) select * into l_rec from t where pk = :pk and decode( c1, :old_c1, 1 )=1 and decode( c2,:old_c2,1) = 1 .... FOR UPDATE NOWAIT;

if (record returned)
then update with new values
else FAIL, record is locked (ora-54) OR you didn't geta record (someone else modified it, start OVER)

b) update t set c1 = :new_c1, c2 = :new_c2, .... where pk = :pk and decode(c1,:old_c1,1) = 1 and decode(c2,:old_c2,1) = 1 and .....;




and I don't care how much "pain" this causes, you are paid to do things correctly, no matter how tedious you believe it to be personally!!!!

update

Sam, August 28, 2007 - 1:57 pm UTC

Tom:

I think you misunderstood me on the two issues above. Your point about data integrity is valid. But what I meant is that if a table has 30 columns do you still use the same method for optimistic locking by using 30 coulmns in your update statement.

Is not easier to use a flag, date timestamp or checksum in that case?

2. My second question was not regarding optimstic locks.

It was to ensure that my update was success or fail. I was thinking of using SQL%ROWCOUNT but that will tell you that one row was updated. It does not tell you that the values were updated based on the inputs.

This is what I meant by that program.
Tom Kyte
September 04, 2007 - 3:11 pm UTC

1) Well, I did not misunderstand, you were very clear - you just did not type in what you meant.

You very very very clearly wrote:


...
If the table has 30 columns would you still do optimistic locking?
....

the question was "to do or not to do optimistic locking". The question was not "would you use the old/new values or a flag or a checksum...."

Sure, I would tend to use the flag column personally myself - if designing the application from scratch.

But what if you couldn't - and ora_rowscn isn't available (wrong release or the scn is tracked at the block level and you cannot rebuild the table) and whatever - then the column approach is just fine.


2) I do not follow you here at all.

in your example, if you update a row, you return "success" - the same as simply checking sql%rowcount = 1 would

if you do not update a row, the v_ values will be null and you will return failure, just like sql%rowcount = 0 would.

so, your way is a very obtuse, obscure way - that is made more clear and safer by using sql%rowcount


update

Sam, September 04, 2007 - 10:43 pm UTC

Tom:

I do not really understand how the "sql%rowcount" is clearer and safer than second method listed below, Can you elaborate and write a sample of code of how it should be.

method 1

update emp
set city = p_city where empid=1234;
if (sql%rowcount = 0) then
rollback;
return 'Fail';
elsif (sql%rowcount = 1) then
return 'Success';
end if;

We are not sure that values were updated correctly until we read them back and verify they are the same as parameter value.

So this does it

method 2

update emp set city = p_city where empid=1234
returning city into v_city

If (v_city = p_city) then commit;
return "success";
else
rollback;
return "update failed';
end if;


Tom Kyte
September 05, 2007 - 1:59 pm UTC

I have no clue what you mean by this:

...
We are not sure that values were updated correctly
....


IF sql%rowcount = 0, then you updated NOTHING
IF sql%rowcount = 1, then you updated a single row.


they were updated to whatever you asked them to be updated to, if rowcount = 1.


Now, try your example where

a) city WAS NULL in the table, and p_city = 'X'
b) city WAS NOT NULL in the table, and p_city is NULL
c) city WAS NULL in the table and p_city IS NULL



I don't get what you are trying to do here at all.


If you say "city=p_city", AND rowcount = 1, then you KNOW that in one row - city was set to the value in P_CITY.



Oh, and I don't like you using rollback and commit in stored procedures - ONLY THE CLIENT knows when to commit and when to rollback.

update

Sam, September 12, 2007 - 8:03 pm UTC

1. What i am simply trying to do is tell a user that the information he submitted to the database has been saved.
I was thinking that SQL%rowcount is not very reliable because it only tells you that one row or two rows were updated.
It does not tell you what the actual data that was updated until you select it from table and verify it is the same that the user submitted?

Am i wrong here?


2. OK I tried the example based on your cases. This is what I get

a. city was null in the table and p_city ='X', I got "success" and table was updated.
b. city was not null and p_city is null, then I got failure. It did not update
c. city was null and p_city is null, then I got failure. It did not update

I guess the reason for that is that

If (NULL = NULL )

does not return true. correct


3.< Oh, and I don't like you using rollback and commit in stored procedures - ONLY THE CLIENT knows when to commit and when to rollback. >

What do you mean by client knows that. The client is a user with a web page. He hits "SAVE" and then this stored procedure runs and saves the data. How can I remove the commit from stored procedure. The whole web application is a stored procedures.



Tom Kyte
September 15, 2007 - 7:13 pm UTC

1) umm, it tells you your update actually updated the row they were interested in updating. think about that please.

If you issue an update and say "set x = 5", x will be 5 (sans triggers of course). that is sort of the way it works.


2) right, your logic was not correct...

3) only your most top level procedure should do this, not the individual procedures that do the modifications.

update

sam, September 16, 2007 - 12:05 am UTC

Tom:

Can you provide a very small web example of what you say here. I just cant grasp it.

<only your most top level procedure should do this, not the individual procedures that do the modifications.>

All I have is two procedures. "A" creates the data entry form for the user and "B" saves the submitted information to the database.

Tom Kyte
September 16, 2007 - 11:31 am UTC

B is a transaction, it should contain no user interface, just database stuff, so B must be called by something - in fact, B might be a handful of routines ( because we code in a very modular fashion, with very small subroutines)

So, you have a procedure A that draws a screen...

Procedure A will submit to a routine "Handle_A_input"

Handle_A_input will invoke B and B' and B'' and so on - whatever it takes to process the transaction and Handle_A_Input will *likely* call A or bits of A to redraw the screen and then Handle_A_Input may commit.

update

sam, September 16, 2007 - 5:21 pm UTC

What is the difference between what I have and what you suggest. let us say we want to insert a new employee into EMP tables.

1. PROC A --> print the html tags for user input. define action="PROC B".
2. PROC B --> do whatever validation on data submited and then

insert into table emp values (.......)
commit;
print confirmation to user that his data was saved.

Now here i agree that the "print confirmation: should be PROC C because if user hit "Refresh" it will run the insert again.

Now looking at what you say it should be done.

1. PROC A ---> user display
2. PROC HANLDE_A_INPUT --> what do you do here
3. PROC B --- what do you do here


Tom Kyte
September 18, 2007 - 12:47 pm UTC

we have the concept of modular code, that of separating bits of logic.

Yours will not be the last application to want to use this data, put the transactional logic SEPARATE so that when your user interface becomes "the old junky interface" - you can reuse what you have.

makes maintaining the code easier
makes enhancing the code easier
makes understanding the code easier
makes everything better.

use small modular, reusable routines.

update

Sam, September 18, 2007 - 2:05 pm UTC

I understand the concept of modular code. I just do not understand how what I have here below is NOT modular.

1. PROC A --> print the html tags for user input. define action="PROC B".
2. PROC B --> transaction code
insert into table emp values (.......)
commit;
3. PROC C --> print HTML confirmation to user that his data was saved.


Is there any problem if using "Commit" in PROC B here because based on what you said before "only the client knows what to commit". The whole application is based on stored procedures. I do not understand where you put the commit/rollback in client.
Tom Kyte
September 18, 2007 - 4:55 pm UTC

all i saw in your example was a proc a and proc b.


suppose tomorrow the code needs to be:

proc A
proc B
proc D --> more transaction code, you need to CREATE an emp AND assign them a room
proc C --> now we are done, now we can commit.


your existing proc B cannot presume to be the end all, be all routine. Things change over time - by committing yourself, you presume to know what the client transaction will forever be.

I'm saying "you do not know that". Not long term anyway.

sam, October 27, 2007 - 2:40 pm UTC

Tom:

1. is it possible to have one update statement that can handle updating one or several columns depending on the input parameters.

For example, i have

p_col1_status
p_col2_status
p_col3_status
p_col4_status

If all parameters are not null then I want to update them all.

update table set col1_status = p_col1_status,
col2_status = p_col2_status,
col3_status = p_col3_status

however, if only one parameter has a value i only one update that parameter and leave the others at their current database value.

if p_col1_stauts is not null and all others ar null
update table set col1_status = p_col1_status

is it possible to do all these permutations in one update statement using a decode or some trick.

2. If you an http client sending 4 different messag formats to provide the status values for input parameters, would you break your oracle code to subroutines or you would have one update program procedures that handles all these messages as above.

thanks,
Tom Kyte
October 29, 2007 - 12:41 pm UTC

this is not really relevant to the original question, but trivial to answer

update t 
   set col1 = decode( p_col1, null, col1, p_col1 ),
       col2 = decode( p_col2, null, col2, p_col2 ),
       ...
 where (p_col1 is not null or p_col2 is not null 
        or p_col3 is not null or p_col4 is not null)
   and (whatever other conditions you have)




no idea what you mean by "4 different message formats", the http client is either

a) sending a single message
b) sending 4 messages, one by one

and that'll dictate how you process...

update

sam, October 29, 2007 - 11:27 pm UTC

TOm:

I assume that "col1" in the update statement is a variable for the original column value meaning that
I have to select

select col1 into col1 from table where.....


correct?


2. What i meant is that I have several http messages or web pages and each updates one parameter.

would it be better to have one processing routine for all these pages/messages or would you create 4 subroutines/procedure and each have its own update statmeent to handle it.

One handling procedure sounds easier but I have some concerns about exception handling. I think 4 procedures provides more flexibility.

what do you think?
Tom Kyte
October 30, 2007 - 1:15 pm UTC

1) ummm, no. not at all

I took YOUR example and used it.

update t 
   set col1 = decode( p_col1, null, col1, p_col1 ),
       col2 = decode( p_col2, null, col2, p_col2 ),
       ...
 where (p_col1 is not null or p_col2 is not null 
        or p_col3 is not null or p_col4 is not null)
   and (whatever other conditions you have)



table T is a table with columns named COL1, COL2, .....

p_col1 is a plsql program variable, your input, just like in your example.

2) it is mostly up to you, I prefer specific code over wildly generic code, a single routine would be "generic", whether it is extremely generic (too generic to do it correctly) or not, I cannot tell sitting here.

update

A reader, February 05, 2008 - 7:47 pm UTC

Tom:

I have a procedure that receives an input X and sends/prints an XML output Y.
I update my_log table with the message sent out.

1. DO I need to add the same code (update statement) in every exception and main program.

2. Is there a way to capture the output message variable from those htp.p statements instead of printing it first and then assigning it to a variable to update the my_log table.?



BEGIN

OWA_UTIL.mime_header('text/xml',TRUE);
htp.p('<?xml version="1.0" encoding="ISO-8859-1"?>');

For x in (select * from table)
LOOP

......
....


END LOOP

htp.p('<XML tags>')

v_msg_sent := '<all the XML tags>'

update my_log
set msg_sent = v_msg_sent
WHERE msg_id = v_msg_id;

commit;



WHEN NO_DATA_FOUND

htp.p('<ERR_CODE>'||'200'||'</ERR_CODE>'
htp.p('<ERR_MSG>'||'No data was found'||'</ERR_MSG>';

v_msg_sent := '<ERR_CODE>'||'200'||'</ERR_CODE>'||'<ERR_MSG>'||'No data was found'||'</ERR_MSG>';

update my_log
set msg_sent = v_msg_sent
WHERE msg_id = v_msg_id;

commit


WHEN INVALID_PARAMETER

htp.p('<ERR_CODE>'||'100'||'</ERR_CODE>'
htp.p('<ERR_MSG>'||'Your input is missing a parameter'||'</ERR_MSG>';

update my_log
set msg_sent = v_msg_sent
WHERE msg_id = v_msg_id;

commit;

END;
Tom Kyte
February 05, 2008 - 9:44 pm UTC

... DO I need to add the same code (update statement) in every exception and
main program. ...

no idea what you mean by that - but do allow me to comment on your code.


I do not like it one bit. Classic reason WHY I wish plsql could neither commit nor rollback.

So, say you have a less trivial example like:

begin
update this
delete that
insert here
exception
when something
then
update error_table;
commit;
end;


what does your exception block commit? It commits maybe the update this, maybe the delete (but never the insert in this example). the only thing you can say here is "if the update error table works, that will be committed - other bits MIGHT get committed, might not"



This screams for a "procedure" (whenever you see yourself typing the same thing over and over...)

similar to this (I don't like this logic, that a failure has to rollback, I believe an exception should just be propagated all of the way up - but whatever):


create or replace procedure 
update_the_log( p_msg_sent in varchar2, p_msg_id in number, p_err_code in number default null, p_err_msg in varchar2 default null)
is
    pragma autonomous_transaction; 
begin
   if (p_err_code is not null) then htp.p('<ERR_CODE>'|| to_char(p_err_code) ||'</ERR_CODE>' ); end if;
   if (p_err_msg is not null) then htp.p('<ERR_MSG>'|| p_err_msg ||'</ERR_MSG>' );
   update my_log set msg_sent = p_msg_sent where msg_id = p_msg_id;
   commit;
end update_the_log;
/


BEGIN

OWA_UTIL.mime_header('text/xml',TRUE);
htp.p('<?xml version="1.0" encoding="ISO-8859-1"?>');

begin
   For x in (select * from table)
   LOOP
      ......
      ....
   END LOOP;

   htp.p('<XML tags>')
   v_msg_sent := '<all the XML tags>'

   update_the_log( v_msg_sent, v_msg_id );
exception
when no_data_found
then
   rollback;
   update_the_log( v_msg_sent, v_msg_id, 200, 'No data was found' );
when invalid_parameter
then 
   rollback;
   update_the_log( v_msg_sent, v_msg_id, 100, 'Your input is missing a parameter' );
end;

update

A reader, February 06, 2008 - 11:37 am UTC

Tom:

This is an excellent example. I will implement this in all code now.

However, I have two logs: one is Error Log and one is the Procedure output log which captures what was sent to the client (all those XML tags for his query).

The issue is if client sent a query to query all EMPLOYEE table, and DB sent him back 20 employess there can be many htp.p statements for each line.

Then I want to save all that XML output into my log. It would be cumbersome to assign each line to a variable again. Is there an efficient way of doing this: sort of printing the output and then updating the table with it.

For x in (Select * from table)
LOOP
htp.p(<XML line 1>);
htp.p(<XML line 2>);
......
END LOOP;

my_var := '<XML Line 1>||'<XML Line 2>||...;

thanks for your great help.



Tom Kyte
February 06, 2008 - 1:34 pm UTC

I can only follow your example... You updated my_log all three times.

You just need more than one procedure for two tables, might even be "more clear" that way, as you won't need the is null check.

and only use the autonomous transaction for the ERROR LOGGING ONE.


one obvious programming approach to what you are doing involves - yet another procedure.


is
   l_my_var long;
   ...

   procedure print( p_string in varchar2 )
   is 
   begin
        htp.p( p_string );
        l_my_var := l_my_var || p_string;
   end;
begin
   ....
   for x in (select * from table)
   loop
       print( xml line 1 );
       print( xml line 2 );
   end loop;
   ....
   l_my_var is the full string here....


Excellent!

Srinivas Narashimalu, February 06, 2008 - 2:01 pm UTC


That was an excellent Tom!

-Sri

update

A reader, February 06, 2008 - 3:25 pm UTC

TOm:

just some clarifications:

1. I assume you are saying create another procedure named print and use that instead of htp.p(string). You are not creating a sub-procedure within procedure.

correct?

2. In update_the_log procedure what does this statement do

pragma autonomous_transaction

why do you need it.

Does it mean that the commit inside that procedure will only apply for those statements in it and not the calling parent procedure?
Tom Kyte
February 07, 2008 - 7:33 am UTC

1) how could you read my code and come to that assumption? I actually demonstrated using a procedure in a procedure there....


I am creating a procedure in the procedure.

You can do it however you like.


2) when you update the error log, you do NOT want to commit the larger transaction. You do not want to roll it back - you want this error logging routine to be able to be called by anyone safely.

the autonomous transaction makes this "safe", the insert into the error log is committed - but NOTHING ELSE IS - you do not break anyones transactional integrity, but you are sure that the record is logged.

update

A reader, February 08, 2008 - 3:34 pm UTC

Tom:

I am trying to do what you suggested but running into an issue.

On each exception I have created a procedure that basically prints out the XML message
sent to the client because it gets repeated numerous times.

I am trying to take that message printed from the sub procedure and pass it into the update_error_log. How do you do that? or shall i update the error log in the sub procedure.


BEGIN

v_msg_no defaul null;

.....



EXCEPTION

WHEN INVALID_PARAMETER THEN
rollback;
invalid_parameter; -- this prints an XML message (<XML><err_code>100</err_code><err_msg>you parameter is not valid</err_msg.</XML>)

update_error_log(v_msg_no,v_msg_sent);


WHEN INVALID_USER THEN
rollback;
invalid_user;


update_error_log(v_msg_no,v_msg_sent);



2. I tried creating a separate procedure for PRINT and use that because I do not want to repeat it in same procedure but it seems i cant see the value of the variable l_my_var and pass it from PRINT to my main procedure.
Does it have to be in every procedure?


3. Do i have to use RAISE; in the above user defined expcetions. I only want to send the client a xml message with a user defined error number that his token expired or his userid was not valid, etc. When I used RAISE the XML did not come out at all. Do you also see any issue using a defiend error number like above.


Tom Kyte
February 08, 2008 - 3:42 pm UTC

1) no idea what you mean. Unless you mean "should I turn my invalid_parameter / invalid_user procedures into FUNCTIONS that return a string that is the message"

(use your programming skills here, this is just an exercise in programming at this point, nothing about oracle really - just coding)

2) do you understand how scoping works in plsql? Yes, if you make this standalone, and my_var is a local variable - the standalone procedure cannot "see" it, you would have to pass it as an in out parameter.

3) what is the client, what calls this procedure, what is the TOP LEVEL THING calling this.

update

A reader, February 08, 2008 - 4:07 pm UTC

1) I am trying to see the options available and see which one is the best:
a. Let the "invalid_parameter" subroutine do the update to the error log instead of the main program
b. Keep the "invalid_parameter" procedure but add "OUT" parameter to it to capture the text output into a variable, then pass it back to the main program to update the msg log. is this doable?
c. Create a function like you say that prints the message and then returns the string and then pass it to update_the_log

what do you recommend?

2. DO you mean adding OUT to print procedure and then assigning that to the local variable.
How do you assign the OUT parameter to the local variable.

3. My client was "BROWSER". But the production client is a perl script. would raise be of any use here if perl recevies a customized XML message and handled it based on the pre-agreed error code.

Basically perl script will do this

IF (err_code = 100)
login again;
else if (err_code = 101)
do this;
end if;

Tom Kyte
February 11, 2008 - 11:39 am UTC

1) modular programming, what result gives you a fine set of reusable subroutines that results in code that fits on a screen from top of procedure to bottom and pleases you aesthetically

2) I mean, if you don't have something in scope, you'll need to pass it in obviously. You need to both read and write this variable, so it'll be an IN OUT parameter.

You will have no local variables at all to speak of, you'll be PASSING some variable as a formal, named parameter and using it.

3) the error code should be the ora-xxxx error code and the client decides what to do based on that.

update

A reader, February 13, 2008 - 11:52 pm UTC

Tom:

is there an ORA-xxxx code to tell a client the trnsactin was committed sort of like a status code that everything is OK (no errors).
Tom Kyte
February 14, 2008 - 9:19 am UTC

sure, it is ora-00000

no error - commit is just a sql statement, you execute it, you get either

a) the absence of an error, ora-00000
b) an error

error

A reader, February 18, 2008 - 1:26 pm UTC

Tom:

if you want to report an error to client do you use

Code = ORA-xxxxx
message = this is for .....

or you skip the ORA- and just send xxxxx.

2. i also noticed the normal completion is reported as
ORA-0000 and not 5 zeros. Which one is correct. 4 zeros or 5 zeros.
Tom Kyte
February 18, 2008 - 2:08 pm UTC

depends, in many cases the application should

a) log error
b) convert error into something that uses the client can understand

for you see, the client could be anything - a human being, another procedure, the end point of a web service, whatever


just consider it ZERO. the sqlcode is ZERO, the sqlerrm is a string.



print code

A reader, March 04, 2008 - 5:44 pm UTC

Tom:

Let me confirm something.

I have 20 proceduers in packages, each prcodure has a different cursor and prints different records in xml format.

DO i need to stick this print procedure in every procedure and use print instead of htp.p so i can update my log with the output.

I think the answer is "yes" but I wanted to confirm. I do not think there is a better way of doing it.



is
l_my_var long;
...

procedure print( p_string in varchar2 )
is
begin
htp.p( p_string );
l_my_var := l_my_var || p_string;
end;
begin
....
for x in (select * from table)
loop
print( xml line 1 );
print( xml line 2 );
end loop;
....
l_my_var is the full string here....

Tom Kyte
March 04, 2008 - 7:51 pm UTC

to make this not be a local procedure is pretty straightforward programming.

pass a PARAMETER, instead of accessing a local global.


procedure print( p_my_var in out long, p_string in varchar2 )
is
begin
  htp.p( p_string );
  p_my_var := p_my_var || p_string;
end;





do that as a top level procedure in a package and then
loop
   print( l_my_var, xml_line_1 );
   ....



Not really "oracle specific", but just sort of straight forward programming there..


locking

A reader, November 24, 2009 - 11:37 am UTC


at what point does implicit rollback occur?

Steve, March 08, 2012 - 1:25 pm UTC

In pl/sql when dml has occurred and a for whatever reason a "raise user_defined_exception" is called, does the raise cause a rollback or does the "exception when user_defined_exception"?
This is the type of scenario in the code I need to work with:

procedure xyz
is
begin
 procedure abc;

exception
when user_defined_exception
then
raise_application_error;

end xyz;


procedure abc
is
record1 table1%rowtype;
begin

select *
into record1
from table1
for update;

if record1.col1 != value
then
-- call to table update code
 update_table1(record1);

if sql%rowcount > 0
then
some_other_code;
else
-- would a rollback be appropriate here to release update lock?
raise user_defined_exception;
end if;
commit;  --update_table1, some_other_code, and release lock

end abc;


Thanks.
Tom Kyte
March 09, 2012 - 9:22 am UTC

the raise itself never causes a rollback.


I would suggest to never use commit or rollback in plsql. let the client - which is after all the only thing that truly knows when a transaction is complete - make that call.


there is an implicit rollback to savepoint issued from the client IF the plsql routine returns an exception t the client. a block of code such as:

begin p; end;


that is submitted by a client is processed like this:

begin
   savepoint X;
   p;
exception
   when others
   then
       rollback to X;
       RAISE;
end;


it makes the execution of P atomic (it either entirely succeeds or entirely fails - but only if the plsql programmer doesn't MUCK IT ALL UP by committing themselves or rolling back)

Let the client decide when and if the transaction is complete. That way - they can take two or three of what you thought were transactions and execute them as a single one.

Why are 8 column primary keys not good

M. Moula, June 11, 2020 - 1:37 pm UTC

Why do you consider 8 column primary keys not good?

Consider a sales setup as below (each place has a sales manager located there)

APAC -> South Asia -> India -> Western Region -> Maharashtra -> Mumbai -> Suburbs -> Western Suburbs -> Kandivali -> Thakur Village

Using a synthetic number (from a sequence perhaps) as a primary key brings complexity instead of simplicity IMHO.

BTW, the example above is not (wholly) concocted, only slightly embellished from real life.
Chris Saxon
June 11, 2020 - 3:07 pm UTC

An 8 column primary key is possible. But is likely a sign that the table has a low level of normalization.

Your example looks like a hierarchy to me. In which case you have a single column PK and another column referencing the parent PK.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library