plsql how to fire trigger only when a function X is activated? - oracle

I have two functions. The first is to withdraw money (the traditional way, I mean when someone withdraw from agency). The function will not allow the withdrawal when client balance is less than amount asked.
Then I create another function to withdraw money from DAP using visa card.
The task is to write a trigger to throw an exception in the case of an expiry of the date of a card.
That means this trigger will fire only when the second function is activated (the function that make withdrawal using card) because the client can have an expired card but he can still use the first function, i.e. the withdrawal from agency.
These are my tables:
CREATE TABLE COMPTE(
NUMEROCOMPTE VARCHAR2(10),
SOLDE NUMBER(38,3),
SEUILDEBIT NUMBER(38,3),
DATESOLDE DATE,
ETAT VARCHAR2(10),
CONSTRAINT pk_NUMEROCOMPTE PRIMARY KEY (NUMEROCOMPTE)
);
CREATE TABLE CARTE(
NUMEROCARTE VARCHAR2(16),
TYPECARTE VARCHAR2(10),
PLAFOND NUMBER(38,3),
DATEEXPIRATION DATE,
NUMEROCOMPTE VARCHAR2(10),
CONSTRAINT pk_NUMEROCARTE PRIMARY KEY (NUMEROCARTE),
CONSTRAINT fk_NUMEROCOMPTE FOREIGN KEY (NUMEROCOMPTE) REFERENCES COMPTE (NUMEROCOMPTE)
);

If you're trying to update the compte table (i.e. withdraw some money), you have to know how is someone doing it - by using a card, or at the agency. I presume that's what the etat column says (see line #4). If not, how do you know it, then? Include that condition into the trigger.
Therefore: if you're using a card, check its expiration date and raise an error if it is lower than right now:
SQL> create or replace trigger trg_bu_compte
2 before update on compte
3 for each row
4 when (new.etat = 'CARD') --> I have no idea whether that's it, but - you have
5 declare --> to have "something" what says wheteher you're
6 l_dateexpiration date; --> using a card or not
7 begin
8 select ca.dateexpiration
9 into l_dateexpiration
10 from carte ca
11 where ca.numerocompte = :new.numerocompte;
12
13 if l_dateexpiration < trunc(sysdate) then
14 raise_application_error(-20000, 'Card has expired');
15 end if;
16 end trg_bu_compte;
17 /
Trigger created.
SQL>

Related

got error in SQL in my oracle when i call my trigger to update total in my STUDENT table

I have to call a trigger which updates fields of total and percentage after query is inserted
heres the code
create table bhrugus1 (
student_name varchar2(100),
dbms number,
dsa number,
mco number,
total number,
percentage number,
roll_no number primary key
);
CREATE OR REPLACE TRIGGER trial1
AFTER INSERT on bhrugus1
REFERENCING new AS new
FOR EACH ROW
BEGIN
:new.total := :new.dbms + :new.dsa + :new.mco;
:new.percentage := (:new.dbms + :new.dsa + :new.mco) / 3 ;
END;
insert into bhrugus1 values ('bhrugu',90,90,90,1,1,2);
SELECT * from bhrugus1;
the table and while running trigger i get error
the new error
Please modify your trigger:
CREATE OR REPLACE TRIGGER trial1
before INSERT on bhrugus1
REFERENCING new AS new
FOR EACH ROW
BEGIN
:new.total := :new.dbms + :new.dsa + :new.mco;
:new.percentage := (:new.dbms + :new.dsa + :new.mco) / 3 ;
END;
You shouldn't run an update statement in your trigger, use the :new... instead of update.
The other important thing is:
before insert
You are missing a component, what happens when someone UPDATEs any or all of dbms,dsa,mco. There is no update trigger to recalculate total or percentage. You could of course just include 'OR UPDATE' on the trigger. A better way however would be defining them a virtual columns.
create table bhrugus1 (
student_name varchar2(100),
dbms number,
dsa number,
mco number,
total number generated always as (dbms+dsa+mco) virtual,
percentage number generated always as ((dbms+dsa+mco)/3) virtual,
roll_no number primary key
);
With that both total and percentage (bad name it is the average - but that is another issue) will always calculated properly. And drop the trigger it is no longer needed.

Error unique constraint and during execution of trigger oracle

I have form in which user add information of new born baby with his/her head family name. When add information into table then getting following errors
ORA-00001: unique constraint (PK) violated
ORA-06512: at trigger_name, line 21
ORA-04088: error during execution of trigger
Trigger:
CREATE OR REPLACE TRIGGER "DB_NAME"."TRG_NBB"
AFTER INSERT ON baby
FOR EACH ROW
WHEN (new.status = 'A') DECLARE
v_1 tab_1.col_1%type;
v_2 tab_2.col_2%type;
v_3 tab_2.col_3%type;
v_4 tab_2.col_4%type;
v_5 tab_2.col_5%type;
v_6 date;
newmofid number;
BEGIN
select max(nvl(col_2,0))+1 into newmofid from tab_2;
SELECT distinct col_1,col_2,to_char(col,'DD-MM-YYYY') INTO v_1,v_2,v_6
from table
where tcid = :new.tcid;
SELECT col_4,col_5,col_3 into v_4,v_5,v_3
from tab_2
where col_1 = v_1
and col_2 = v_2;
INSERT INTO tab_2 (all_columns)
VALUES(variable_names);
DBMS_OUTPUT.PUT_LINE('New Born Baby successfully added to member table');
END trg_nbb;
/
ALTER TRIGGER "DB_NAME"."TRG_NBB" ENABLE;
When I execute this sql query It's take 4 to 5 seconds and increment in values very quickly
select max(nvl(col_2,0))+1 into newmofid from tab_2;
Result:
6030819791
Again execute takes 3 to 4 seconds
Result:
6030819798
How to solve this problem?
Thanks
I suspect it is MAX + 1 that causes problems:
select max(nvl(col_2,0))+1 into newmofid from tab_2;
Such a principle is in most cases wrong and will fails, especially in a multi-user environment where two (or more) users at the same time fetch the same MAX + 1 value, do some processing, and - at the time of insert - one of them succeeds (because it is the first), but the rest of them fail because such a value already exists in the table.
I suggest you switch to a sequence, e.g.
create sequence seq_baby;
and then, in your form, do
select seq_baby.nextval into newmofid from dual;

plsql - can anyone help me out how do we reset a sequence in oracle 11 g database at two particular times in one single day?

Let me explain you the scenario of hospital management tool.
In every hospital, we have n no. of doctors, n no. of admins, n no. of security, etc departments respectively. Each and every hospital we have an out-patient consultations in the morning time approximately around 8:00 am to 10:00 am, from 10:00 am to evening 5:00 pm doctors will undertake operations and treatments for In-patients in "Intensive care unit" (ICU). So now after 5:00 pm again the doctors will have an out-patients consultation in the hospital from 18:00 pm to 20:00 pm.
Now, let me explain the same in technical terminology to you.
When the out-patients come ask a token number of so and so doctor. The admin will then select particular department in the UI and select particular doctor as per patient's problem. For this, i'm maintaining a each doctor's tables in the database which doctor name itself..
example :
1)Neurology Department
i) Dr. Sarath Kumar
ii) Dr. anil kumar
2)Cardiology Department
i) Dr. Madhu
ii) Dr. Anji Reddy
3)Orthopedics Department
i) Dr. Murali
ii) Dr. Sirisha
etc...
Creation of a doctor table :
create table sarath_Kumar(token_no not null primary key,
patient_name char(50) not null ,
patient_age number(3) not null ,
patient_phonenumber number(12) not null unique ,
patient_email varchar2(50) not null unique,
patient_gender char(1) not null,
patient_location varchar2(50) not null,
patient_dateofappointment date not null,
CONSTRAINT sk_token_no CHECK (token_no<=20);
Note:
if we think generally admin doesn't know which token number is going on for each and every doctor.
As we have the same table structure for each and every doctor by their name. But now the thing is the first column in each doctor table has to generate automatically from 1, to do this i created a sequence and a procedure to execute the sequence before an insertion happens by the admin.
let's take morning session of out-patients consultation from 8:00 am to 10:00 am. Each doctor will only have a 20 patients for consultation.
Sequence Creation :
create sequence appointment_sequence
start with 1
increment by 1
minvalue 1
maxvalue 20
cache 5
;
Procedure Creation :
create or replace trigger appointment_sequence before insert on sarath_kumar for each row
begin
:new.token_no := appointment_sequence.NEXTVAL;
end;
/
what i need from you is :
After reaching 20 patients for any doctor during consultation i.e., the token number reached it's maximum level between 8:00 am to 10:00 am. If any person asks for a appointment for that particular doctor. The admin shouldn't able to provide any kind of appointment for that doctor and insist the patient to come in evening time consultation which is from 18:00 pm to 20:00pm .
I need a procedure or function in which the doctor table should get truncated and the sequence should get reset back to minvalue at 10:00 am and in the evening after 20:00 pm respectively.
First of all, You should have the patient_appoint table instead of a separate table with the doctor's name and just pass the doctor's ID in that table.
create table patient_appoint(token_no not null primary key,
doctor_id number not null,
patient_name char(50) not null ,
patient_age number(3) not null ,
patient_phonenumber number(12) not null unique ,
patient_email varchar2(50) not null unique,
patient_gender char(1) not null,
patient_location varchar2(50) not null,
patient_dateofappointment date not null,
CONSTRAINT sk_token_no CHECK (token_no<=20);
For resetting the sequence to 1, Use CYCLE property of sequence. Use below code to generate the sequence -
create sequence appointment_sequence
start with 1
increment by 1
minvalue 1
maxvalue 20
cycle
cache 5
;
For restricting to only 20 patients per day, you may use below trigger -
CREATE OR REPLACE TRIGGER TR_PATIENT_APPOINT
AFTER INSERT ON PATIENT_APPOINT
DECLARE
v_count NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM PATIENT_APPOINT
WHERE TRUNC(patient_dateofappointment) = TRUNC(SYSDATE);
IF (v_count > 20) THEN
raise_application_error(-20000, 'Maximum 20 appointments allowed per day.');
END IF;
END TR_PATIENT_APPOINT;
As others have pointed out or at lest hinted at this will be a maintenance nightmare, with each doctor having their own table and their own sequence. Consider what happens when a patient cancels. You don't get that sequence value back, so that doctor can only see 19 patients. And that is an easy situation to handle. There is an easier way: don't use sequences.
If you break it down each patent is essentially given a 6min time slot (120min/20slots). With his you generate a skeleton schedule for each doctor that does not have patient information initially. Admins then fill in patient information when needed, and can actually view the available time for each doctor. The following shows how to generate such a schedule. (Note it assumes you have normalized you doctor table (1 table containing all doctors) and created a patient table (1 table containing all patients).
--- prior Setup
create table doctors(doc_id integer, name varchar2(50), ..., constraint doc_pk primary key (doc_id));
create table patients(pat_id integer, name varchar2(50), ..., constraint pat_pk primary key (pat_id));
--- Daily Out patient Schedule.
create table out_patient_schedule (
ops_id integer
, doc_id integer not null
, pat_id integer
, apt_schedule date
, constraint ops_pk primary key (ops_id)
, constraint ops2doc_fk foreign key (doc_id) references doctors(doc_id)
, constraint ops2pat_fk foreign key (pat_id) references patients(pat_id)
);
--- Generate skeleton schedule
create or replace procedure gen_outpatient_skeleton_schedule
as
begin
insert into out_patient_schedule( doc_id, apt_schedule)
with apt_times as
( select trunc(sysdate, 'day') + 8/24 + (120/20)*(level-1)/(60*24) apt_time from dual connect by level <= 20
union all
select trunc(sysdate, 'day') + 18/24 + (120/20)*(level-1)/(60*24) from dual connect by level <= 20
)
select doc_id, apt_time from doctors, apt_times;
end gen_outpatient_skeleton_schedule;
Now create an Oracle Job, or an entry for what ever job schedule you have, that executes the above procedure between midnight and 8:00.
There is a race condition you need to handle, but doing so would be much easier that trying it with sequences.
Good Luck either way.

Oracle - Trigger that checks a constraint every month

I've been experimenting trigger function in oracle with various constraint, recently someone recommends me to using materialized view instead of trigger on the following condition which i think is quite a wise choice to do so. But for learning purpose, i would like to know how does trigger function works.
create a trigger to check on a specify constraint base on monthly basis.
table rent
|ID|Member|book|
----------------------
1 | John |fairytale|2-jun-12|
2 | Peter |friction|4-jun-12|
3 | John |comic|12-jun-12|
4 | Peter |magazine|20-jun-12|
5 | Peter |magazine|20-jul-12|
6 | Peter |magazine|20-jul-12|
constraint : member are only allow to borrow 2 books monthly.
Code contributed by #HiltoN which i don't quite understand:
create or replace trigger tr_rent
before insert on rent
for each row
declare
v_count number;
begin
select count(id)
into v_count
from rent
where member = :new.member;
if v_count > 2 then
raise_application_error (-20001, 'Limit reached');
end if;
end;
In general, that trigger does not work.
In general, a row-level trigger on table X cannot query table X. So, in your case, a row-level trigger on RENT is generally not allowed to query the RENT table-- doing so would throw a mutating trigger exception. If you want to guarantee that your application will only ever insert 1 row at a time using an INSERT ... VALUES statement, you won't hit a mutating trigger error but that is generally not an appropriate restriction. It is also not appropriate in a multi-user environment-- if there are two transactions running at about the same time both checking out a book to the same user, this trigger will potentially allow both transactions to succeed.
The proper place to add this sort of check is almost certainly in the stored procedure that creates the RENT record. That stored procedure should check how many rentals the member has over the current month and error out if that is more than the limit. Something like
CREATE OR REPLACE PROCEDURE rent_book( p_member IN rent.member%type,
p_book IN rent.book%type )
AS
l_max_rentals_per_month constant number := 2;
type rental_nt is table of rent.rend_id%type;
l_rentals_this_month rental_nt;
BEGIN
SELECT rent_id
BULK COLLECT INTO l_rentals_this_month
FROM rent
WHERE member = p_member
AND trunc(rental_date,'MM') = trunc(sysdate, 'MM')
FOR UPDATE;
IF( l_rentals_this_month.count > l_max_rentals_per_month )
THEN
RAISE_APPLICATION_ERROR( -20001, 'Rental limit exceeded' );
ELSE
INSERT INTO rent( rent_id, member, book, rental_date )
VALUES( rent_id_seq.nextval, p_member, p_book, sysdate );
END IF;
END;
If you really wanted to enforce something like this using triggers, the solution would get much more complicated. If you don't care about efficiency, you could create a statement-level trigger
create or replace trigger tr_rent
after insert on rent
declare
v_count number;
begin
select count(id)
into v_count
from (select member, count(*)
from rent
where trunc(rental_date,'MM') = trunc(sysdate,'MM')
group by member
having count(*) > 2);
if v_count >= 1 then
raise_application_error (-20001, 'At least one person has exceeded their rental limit');
end if;
end;
This works but it requires (at least) that you do the validation for every member every time. That is pretty inefficient when you have a large number of members. You could reduce the workload by substantially increasing complexity. If you
Create a package which declares a package global variable that is a collection of rent.member%type.
Create a before statement trigger that initializes this collection.
Create a row-level trigger that adds the :new.member to this collection
Create an after statement trigger that is similar to the one above but that has an additional condition that the member is in the collection you're maintaining.
This "three-trigger solution" adds a substantial amount of complexity to the system particularly where the appropriate solution is not to use a trigger in the first place.
I agree with Justin, your trigger wouldn't work for a number of reasons. A materialized view or stored procedure solution would get you there. I suggest the very best solution to this problem would be a simple unique index:
create unique index rent_user_limit on rent (member, trunc(rental_date, 'month'));

How to generate alphanumeric id in Oracle

In my vb application I want an autogenerated id of alphanumeric characters, like prd100. How can I increment it using Oracle as backend?
Any particular reason it needs to be alphanumeric? If it can just be a number, you can use an Oracle sequence.
But if you want just a random string, you could use the dbms_random function.
select dbms_random.string('U', 20) str from dual;
So you could probably combine these 2 ideas (in the code below, the sequence is called oid_seq):
SELECT dbms_random.string('U', 20) || '_' || to_char(oid_seq.nextval) FROM dual
There are two parts to your question. The first is how to create an alphanumeric key. The second is how to get the generated value.
So the first step is to determine the source of the alpha and the numeric components. In the following example I use the USER function and an Oracle sequence, but you will have your own rules. I put the code to assemble the key in a trigger which is called whenever a row is inserted.
SQL> create table t1 (pk_col varchar2(10) not null, create_date date)
2 /
Table created.
SQL> create or replace trigger t1_bir before insert on t1 for each row
2 declare
3 n pls_integer;
4 begin
5 select my_seq.nextval
6 into n
7 from dual;
8 :new.pk_col := user||trim(to_char(n));
9 end;
10 /
Trigger created.
SQL>
The second step requires using the RETURNING INTO clause to retrieve the generated key. I am using SQL*PLus for this example. I confess to having no idea how to wire this syntax into VB. Sorry.
SQL> var new_pk varchar2(10)
SQL> insert into t1 (create_date)
2 values (sysdate)
3 returning pk_col into :new_pk
4 /
1 row created.
SQL> print new_pk
NEW_PK
--------------------------------
APC61
SQL>
Finally, a word of warning.
Alphanumeric keys are a suspicious construct. They reek of "smart keys" which are, in fact, dumb. A smart key is a value which contains multiple parts. At somepoint you will find yourself wanting to retrieving all rows where the key starts with 'PRD', which means using SUBSTR() or LIKE. Even worse someday the definition of the smart key will change and you will have to cascade a complicated update to your table and its referencing foreign keys. A better ides is to use a surrogate key (number) and have the alphanumeric "key" defined as separate columns with a UNIQUE composite constraint to enforce the business rule.
SQL> create table t1 (id number not null
2 , alpha_bit varchar2(3) not null
3 , numeric_bit number not null
4 , create_date date
5 , constraint t1_pk primary key (id)
6 , constraint t1_uk unique (alpha_bit, numeric_bit)
7 )
8 /
Table created.
SQL>

Resources