Skip to Main Content

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Vic.

Asked: September 02, 2015 - 8:34 am UTC

Last updated: September 18, 2015 - 3:24 am UTC

Version: 11.2.0.3

Viewed 1000+ times

You Asked

Hi,

As part of normal operational activities, circumstances arise where manual updates / deletes to our production databases occur. These updates & deletes are made by our support team. We need to restrict the amount of rows deleted or updated at any one time. We'd like to put a restriction to delete 0 - 100 rows as default, to delete 101 - 500 rows requires manager authorisation. We are using 11.2.0.3 and will be upgrading to 12.1.0.2 within a couple of months.

I'm aware of using fine grained access control to restrict deletes & updates but to restrict users to only delete a certain number of rows? I'm not sure.

Would this be possible?

Regards

Vic

and Chris said...

You can use VPD to restrict the number of rows updated or deleted. The process for doing so is convoluted.

When you apply a VPD policy to a delete, a statement such as:

delete t


Becomes:

delete (select * from (select * from t) where <VPD policy clause>);


Therefore a policy applying a "rownum < :x" check becomes:

delete (select * 
        from   (select * from t) 
        where  rownum <= 100);

ORA-01732: data manipulation operation not legal on this view


Oh dear! To get around this, first you must find the rows of interest using a subquery. Then join those back to the main query, like so:

delete (select * 
        from (select * from t) 
        where rowid in (select rowid from t where rownum <= 100));

100 rows deleted.


The complete policy to apply this would be:

create table t (x not null) as
  select rownum x
  from   dual
  connect by level <= 500;

create or replace function restrict_deletes ( 
  schema_var in varchar2,
  table_var  in varchar2
) return varchar2
is
  where_clause varchar2 (100);
begin

  where_clause := 'rowid in (select rowid from t where rownum <= 100)';
  
  return where_clause;
  
end restrict_deletes;
/

begin
  dbms_rls.add_policy (
    object_schema    => 'chris',
    object_name      => 't',
    policy_name      => 'delete_policy',
    function_schema  => 'chris',
    policy_function  => 'restrict_deletes',
    statement_types  => 'update, delete'
   );
 end;
/

select count(*) from t;

  COUNT(*)
----------
       500

delete t;

100 rows deleted.

select count(*) from t;

  COUNT(*)
----------
       400

delete t
where  rownum <= 50;

50 rows deleted.

select count(*) from t;

  COUNT(*)
----------
       350


There's a problem with this approach however. The subquery filtering is applied before your where clause. Therefore this may mean no rows are removed!

In the case below we would expect the delete to remove 50 rows. The first 100 identified by

select rowid from t where rownum <= 100


have x values < 400 however. Therefore the delete does nothing!

select count(*) from t
where  x > 400;

  COUNT(*)
----------
       100

delete t
where  x > 400
and    rownum <= 50;

0 rows deleted.

select count(*) from t
where  x > 400;

  COUNT(*)
----------
       100


If the where clause for your deletes/updates is fixed and known, you can include it in the restrict_deletes function. If not you'll need to figure out a process to generate the appropriate where clause in the (select rowid from t...) subquery you create in the function.

Rating

  (4 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thank you

Vic, September 02, 2015 - 1:26 pm UTC

Many thanks for your assistance - you're already well on the way to becoming legends like TOM...
Chris Saxon
September 02, 2015 - 4:59 pm UTC

Thanks! Hopefully we can help you as much as Tom has.

KISS ?

Tubby, September 02, 2015 - 7:15 pm UTC

I dig the nerdy solution quite a bit, but why not do something like ...

Write some PLSQL which accepts inputs to perform the delete and enforces the number of rows limitation within that PLSQL? Grant access to a support schema to that routine and don't allow the support staff direct DELETE access to the tables.

Just a thought.

Cheers,
Chris Saxon
September 03, 2015 - 1:57 pm UTC

Yep, that's a preferable solution. The fewer people that have access to perform SQL deletes the better.

perhaps you could use something like this ...

Sokrates, September 02, 2015 - 8:50 pm UTC

...We'd like to put a restriction to delete 0 - 100 rows as default...

I don't quite understand what this means.
0-100 rows per call ? per transaction ? per evening ?

probably you could use something like this:
create table t(x primary key) as
  select rownum x
  from   dual
  connect by level <= 500;
  
create view v as select * from t;

create trigger tr_forbid_direct_del_on_t
before delete on t
begin
   if dbms_utility.format_call_stack not like '%TR\_D\_I\_V%' escape '\' then
      raise_application_error(
         -20999,
         'deletion on t only via v !'
         ); 
   end if;
end tr_forbid_direct_del_on_t;
/

create package pkg_v is
-- has to be called before deletion, at most 100 rows are allowed
procedure enable_delete;
procedure inc_delco;
function get_delco return int;
end pkg_v;
/

create package body pkg_v is

delco int;

procedure enable_delete is
begin
   delco := 0;
end enable_delete;

procedure inc_delco is
begin
   delco := delco + 1;
end inc_delco;

function get_delco return int is
begin
   return delco;
end get_delco;

end pkg_v;
/

create trigger tr_d_i_v 
instead of delete on v
for each row
begin
   if pkg_v.get_delco() < 100 then
      delete t
      where x = :old.x;
      pkg_v.inc_delco();
   end if;
end tr_d_b_v;
/


Then, a client can only delete v ( not t ) and has to "init" every deletion via a call to pkg_v.enable_delete, then, at most 100 rows are allowed to delete:
(note that sql%rowcount lies )

sokrates > select count(*) from t;

  COUNT(*)
----------
       500

sokrates > delete t;
delete t
*
ERROR at line 1:
ORA-20999: deletion on t only via v !
ORA-06512: at "SOKRATES.TR_FORBID_DIRECT_DEL_ON_T", line 3
ORA-04088: error during execution of trigger 'SOKRATES.TR_FORBID_DIRECT_DEL_ON_T'


sokrates > select count(*) from t;

  COUNT(*)
----------
       500

sokrates > delete v;

500 rows deleted.

sokrates > REM which is a lie !
sokrates > select count(*) from t;

  COUNT(*)
----------
       500

sokrates > call pkg_v.enable_delete()
sokrates > /

Call completed.

sokrates > delete v;

500 rows deleted.

sokrates > select count(*) from t;

  COUNT(*)
----------
       400

sokrates > delete v;

400 rows deleted.

sokrates > select count(*) from t;

  COUNT(*)
----------
       400

sokrates > call pkg_v.enable_delete()
sokrates > /

Call completed.

sokrates > REM now at most 100 rows are allowed to be deleted again
sokrates > delete v where mod(x, 10) = 0;

40 rows deleted.

sokrates > select count(*) from t;

  COUNT(*)
----------
       360

sokrates >

Chris Saxon
September 18, 2015 - 3:24 am UTC

THanks for adding your thoughts.

Thanks very much

Vic Cordiner, September 03, 2015 - 8:12 am UTC

Hi All,

Thanks very much for your help.

The main reason we have to do this is is that we have had support deleting many and incorrect rows in error. e.g. leaving a where clause off a delete or update. This causes much pain in getting the rows back doing a restore. (Flashback is being implemented)

To be honest an actual peer review system, with 4 eyes on on the code would be ideal before the deletes / updates are actually done.

I firmly believe this is a training issue here as well!!

Regards,

Vic
Chris Saxon
September 03, 2015 - 2:07 pm UTC

I agree that implementing a good training and review process is a better solution. Implementing the code for any of the solutions above needs careful testing. The authorization process also needs thought. Even with this in place it's not going to protect you from all mistakes.

It's quicker and easier to just create a checklist for all data fixes before they're run in production:

- Does the statement include a where clause?
- Has (at least) one other person checked the statement?
- etc.

Alternatively you could build some simple data management apps in APEX so support can see exactly what they're deleting/updating.

More to Explore

Security

All of the vital components for a secure database are covered in the Security guide.