Contact US

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!
  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It's Free!

*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Students Click Here

Oracle: All versions FAQ

Oracle Supplied Packages

How can I tell if a row has been changed since I last looked at it? by MikeJones
Posted: 28 Mar 01

I've seen this question banded about a few times, it appears to be more of a problem with Web applications which don't have a persistent connection to the database and therefore can't lock a record.  One solution is to create a version-stamp or timestamp column on the table(s) and then on update update this column.  I'm not very keen on this for a few reasons.

1.  you have to increase the size of every row on the table and possibly many tables.  This incease in size might not be trivial

2.  any existing code would have to be changed to incorperate maintaining the extra column.  This may impact the performance of this code, and end users will see a slowing of their system with no functionality improvement.

I know that a trigger could also be used to maintain this, but I feel this is just as sloppy.

So I decided to write the following, I hope it is of use to someone.  I will calculate an integer value in the range of 0..2147483647 for each row.  This can be considered a checksum if you will.

You call the function as

select what,
from table_name

On update you can use the hash value you got to make sure the row hasn't changed...

update my_table
set column = 'some value'
where some_condition = 'some condition'
and '1234567890' = hashme('my_table',rowid)

where 1234567890 is the hash value you got back from the select.

For those of you which are either:

A) lazy and can't be bothered to look through the code
B) New to Oracle and don't understand some of it.

It has the following limitations.

* Any Long Raw columns are ignored in the value.
* The table can not have any LOB columns
* The total length of a row can not exceed 32K (excluding

These limitations are caused by either Oracle limitations or my lack of enthusiasm to solve them.

So here is the code.

create or replace function hashme (i_table_name in varchar2,
                                   i_row_id in rowid)
return number
/* Variables and Types */
  type char_tab     is table of char(255)      index by binary_integer;
  type varchar2_tab is table of varchar2(4000) index by binary_integer;
  type date_tab     is table of date           index by binary_integer;
  type number_tab   is table of number         index by binary_integer;
  type raw_tab      is table of raw(32000)     index by binary_integer;
  l_columns          dbms_sql.desc_tab;
  l_tablename        varchar2(30) := i_table_name;
  l_cursor           integer;
  l_ignore           integer;
  no_of_cols         integer;
  l_char             char_tab;
  l_date             date_tab;
  l_varchar2         varchar2_tab;
  l_number           number_tab;
  l_raw              raw_tab;
  l_vc_inc           integer := 1;
  l_c_inc            integer := 1;
  l_d_inc            integer := 1;
  l_n_inc            integer := 1;
  l_r_inc            integer := 1;
  l_offset           integer;
  l_read_length      integer;
  l_long_chunk       varchar(32000);
  l_row              varchar2(32000);
  l_sql              varchar2(1000);
  rowid_not_found    exception;
  someother_datatype exception;
  l_step             varchar2(100);
/* Procedures */
procedure p_set_step (p_step in varchar2) is
--dbms_output.put_line (p_step);
l_step := p_step;
  l_sql := 'select * from '|| l_tablename || ' where rowid = ''' || i_row_id || '''';
  l_cursor := dbms_sql.open_cursor;
  dbms_sql.parse(l_cursor, l_sql, omx_oam_cmn_01.gc_dbmssql_flag);
  dbms_sql.describe_columns(l_cursor, no_of_cols, l_columns);
  for r_column in 1 .. no_of_cols loop
  p_set_step('Column '||r_column);
    if l_columns(r_column).col_type = 1 then
      l_varchar2(l_vc_inc) := null;
      dbms_sql.define_column(l_cursor, r_column, l_varchar2(l_vc_inc),l_columns(r_column).col_max_len);
      l_vc_inc := l_vc_inc +1;
    elsif l_columns(r_column).col_type = 2 then
      l_number(l_n_inc) := null;
      dbms_sql.define_column(l_cursor, r_column, l_number(l_n_inc));
      l_n_inc := l_n_inc +1;
    elsif l_columns(r_column).col_type = 12 then
      l_date(l_d_inc) := null;
      dbms_sql.define_column(l_cursor, r_column, l_date(l_d_inc));
      l_d_inc := l_d_inc +1;
    elsif l_columns(r_column).col_type = 96 then
      l_char(l_c_inc) := null;
      dbms_sql.define_column_char(l_cursor,r_column,l_char(l_c_inc), l_columns(r_column).col_max_len);
      l_c_inc := l_c_inc +1;
    elsif l_columns(r_column).col_type = 8 then
    elsif l_columns(r_column).col_type = 24 then
      -- Cant cope with long raw.
    elsif l_columns(r_column).col_type = 23 then
      l_raw(l_r_inc) := null;
      dbms_sql.define_column_raw(l_cursor, r_column, l_raw(l_r_inc), l_columns(r_column).col_max_len);
      raise someother_datatype;
    end if;
  end loop;
  l_vc_inc := 1;
  l_c_inc  := 1;
  l_d_inc  := 1;
  l_n_inc  := 1;
  l_ignore := dbms_sql.execute(l_cursor);
  if dbms_sql.fetch_rows(l_cursor) = 0 then
    raise rowid_not_found;
  end if;
  p_set_step('no of cols is '||no_of_cols);
  for r_columns in 1 .. no_of_cols loop
    p_set_step('on_col '||r_columns);
    if l_columns(r_columns).col_type = 1 then
      dbms_sql.column_value(l_cursor, r_columns, l_varchar2(l_vc_inc));
      l_row := l_row || nvl(l_varchar2(l_vc_inc),'null');
      l_vc_inc := l_vc_inc +1;
    elsif l_columns(r_columns).col_type = 2 then
      dbms_sql.column_value(l_cursor, r_columns, l_number(l_n_inc));
      l_row := l_row || to_char(l_number(l_n_inc));
      l_n_inc := l_n_inc +1;
    elsif l_columns(r_columns).col_type = 12 then
      dbms_sql.column_value(l_cursor, r_columns, l_date(l_d_inc));
      l_row := l_row || to_char(l_date(l_d_inc),'ddmmyyyyhh24miss');
      l_d_inc := l_d_inc +1;
    elsif l_columns(r_columns).col_type = 96 then
      dbms_sql.column_value_char(l_cursor, r_columns, l_char(l_c_inc));
      l_row := l_row || l_char(l_c_inc);
      l_c_inc := l_c_inc +1;
    elsif l_columns(r_columns).col_type = 8 then
      l_offset := 0;
      while 1=1 loop
        dbms_sql.column_value_long(l_cursor, r_columns, 32000, l_offset, l_long_chunk, l_read_length);
        l_row := l_row || dbms_utility.get_hash_value(l_long_chunk, 0, 2147483647);
        exit when l_read_length < 32000;
        l_offset := l_offset + 32000;
      end loop;
    elsif l_columns(r_columns).col_type = 23 then
      dbms_sql.column_value_raw(l_cursor, r_columns, l_raw(l_r_inc));
      l_row := l_row||utl_raw.cast_to_varchar2(l_raw(l_r_inc));
      l_r_inc := l_r_inc + 1;
      raise someother_datatype;
    end if;
  end loop;
  return dbms_utility.get_hash_value(l_row, 0, 2147483647);
  when rowid_not_found then
    raise_application_error(-20101,'Rowid '|| i_row_id|| ' not found in table '|| i_table_name);
  when someother_datatype then
    raise_application_error(-20102,'Can not cope with one of the datatypes in the table '|| i_table_name);
  when others then
    raise_application_error(-20100,'Step '||l_step||' '||substr(sqlerrm,1,200));
set arraysize 1
sho errors

Anyone Interested in futhering the code should know following.

* Long raws are excluded from the calculation as DBMS_SQL
  does not support them in terms of Define column and
  column value.
* LOB's are not supported but could be, they would need to
  be read in a loop and each 32K chunk hashed up and added
  to the main string, much the same way as longs are
  currently handled.
* If a huge table is read it's possible that it will blow
  the 32K limit of the row to hash, this could be solved by
  turning the row in to a hash value as it approaches 32K
  and then continuing, I'm not overly keen on this and
  think it's unlikely to happen (32K row size without
  long / Lob data!)

I've done little testing with this, but have found it to work well with the test data I have given it.  On a 22K rows it produced no duplicate keys, I'll try it on bigger data soon.  The performance of the function should be good, It needs to do only 1 ROWID fetch so interms of table access should be very effecint, however if many rows are selected and therefore passed through the function it may be an idea to pin both DBMS_SQL and DBMS_UTILITY to the SGA so as to minimise the reloading overhead.

Back to Oracle: All versions FAQ Index
Back to Oracle: All versions Forum

My Archive

Close Box

Join Tek-Tips® Today!

Join your peers on the Internet's largest technical computer professional community.
It's easy to join and it's free.

Here's Why Members Love Tek-Tips Forums:

Register now while it's still free!

Already a member? Close this window and log in.

Join Us             Close