1

I want to prevent deletion of a phone number if there is only one in the table at the time.

So far I have this trigger:

CREATE OR REPLACE TRIGGER T_TfnoCliente_Cliente
BEFORE DELETE ON TFNO_CLIENTE
FOR EACH ROW
DECLARE
--PRAGMA AUTONOMOUS_TRANSACTION;
telefonosClienteAtencion NUMBER;
BEGIN
telefonosClienteAtencion := 0;
SELECT count(1) INTO telefonosClienteAtencion FROM TFNO_CLIENTE WHERE id = :old.id AND tipo = :old.tipo;
IF telefonosClienteAtencion < 2 THEN
  RAISE_APPLICATION_ERROR(-20101, 'Cannot delete a phone number if the user doesn't have more than one phone associated');
END IF;
END;

I am getting error ORA-04091: the table TFNO_CLIENT is mutating (...)

I understand the issue, and I have tried several alternatives:

  1. Pragma autonomous_transaction. This causes malfunctions and I would like to avoid it as much as possible.

  2. Instead of querying the table itself, querying a view that holds the count:

I then changed the query to

SELECT no_tfnos INTO telefonosClienteAtencion FROM CLIENTES_TFNOS WHERE cliente = :old.id;

CREATE OR REPLACE VIEW CLIENTES_TFNOS (cliente, no_tfnos) AS
SELECT id, count(*) 
from TFNO_CLIENTE 
group by id;

This didn't work either.

Is there any way to solve this easily? Thanks!

Barbaros Özhan
  • 59,113
  • 10
  • 31
  • 55
Marta Lobo
  • 175
  • 1
  • 16
  • 2
    The simplest method might be to store the "main" phone number in another table and use a foreign key relationship. Another alternative is to keep a count of the phone numbers -- in another table, updated using a trigger -- and then use a `check` constraint to be sure the value is never `0`. – Gordon Linoff Mar 18 '20 at 12:46
  • That code wouldn't even compile. Note the unescaped single quote. – sticky bit Mar 18 '20 at 12:55
  • well yes, I translated the error from spanish. That's not the exact error message @stickybit – Marta Lobo Mar 18 '20 at 12:59
  • 2
    It might be better not to use a trigger at all. Your business logic that there must always be one phone number can be put in a procedure or package. – kevinskio Mar 18 '20 at 13:03
  • @kevinsky could you please provide an example? – Marta Lobo Mar 18 '20 at 13:03
  • You could use a compound trigger, i.e. combined trigger for `BEFORE DELETE` and `BEFORE DELETE FOR EACH ROW` and `AFTER DELETE` - however using a procedure will be the better way. The trigger may fail in a multi-user environment. – Wernfried Domscheit Mar 18 '20 at 15:11

1 Answers1

2

This should be considered pseudo code

FUNCTION FN_HasMoreThanOnePhoneNumber(vclient_id IN NUMBER, vphone IN VARCHAR2) 
RETURN BOOLEAN IS
telefonosClienteAtencion NUMBER;
vResult BOOLEAN := FALSE;
BEGIN
telefonosClienteAtencion := 0;
SELECT count(*) 
INTO telefonosClienteAtencion 
FROM TFNO_CLIENTE WHERE id = vclient_id 
AND tipo = vphone;

IF telefonosClienteAtencion > 1 THEN
  vResult := TRUE;
END IF;

RETURN vResult;

END FN_HasMoreThanOnePhoneNumber;

Then in your code where you make the decision to insert or delete it could work like this

IF FN_HasMoreThanOnePhoneNumber(lclientId, lPhone) THEN
   process new data by deleting old phone number
ELSE
   RAISE_APPLICATION_ERROR(-20101, 'Cannot delete a phone number if the user doesn't have 
   more than one phone associated');
END IF;

A trigger hides your business logic. By having a function that does one thing and only one thing you can make what you want clear to the next programmer who looks at your work.

kevinskio
  • 4,431
  • 1
  • 22
  • 36
  • When would this function be executed? Shall I call it inside the trigger? My issue is that I need to test it in the database itself, for example when I execute a delete instruction – Marta Lobo Mar 18 '20 at 13:26
  • @MartaLobo this approach means you don't use a trigger. When you want to issue a delete you call the function. It tells you whether you can proceed. – kevinskio Mar 18 '20 at 13:35
  • okay thanks! so there's no way to prevent deletion when executing a delete instruction directly, right? – Marta Lobo Mar 18 '20 at 13:40
  • @MartaLobo I guess my answer is more from an application architecture point. What services or users would you have accessing the database? If you are worried that they could delete from this table then create roles for them that do not have that permission. In general, control access and enforce that all users and services access packages and procedures to change data in tables. So the schema owner where the data is located is locked and no one can log on as them. Other users have read. If they want to update/insert/delete they have to call a package or procedure. – kevinskio Mar 18 '20 at 13:45