Below i have created 2 triggers that apply a discount, one uses a function while the other does not. Is there any other way to make this efficient/better?
CREATE OR REPLACE TRIGGER APPLY_DISCOUNT
BEFORE INSERT OR UPDATE OF INV_NO,C_NO ON INVOICE
FOR EACH ROW
DECLARE
CURSOR C_APPTMNT
IS
SELECT C_NO,COUNT(C_NO)
FROM APPOINTMENT GROUP BY C_NO;
V_C_NO APPOINTMENT.C_NO%TYPE;
VISIT NUMBER(2);
BEGIN
VISIT:=CNT_VISIT(:NEW.C_NO);
IF VISIT BETWEEN 2 AND 4
AND :NEW.C_NO = V_C_NO THEN
:NEW.BILL := :NEW.BILL * 0.9;
ELSIF VISIT BETWEEN 5 AND 8
AND :NEW.C_NO = V_C_NO THEN
:NEW.BILL := :NEW.BILL * 0.8;
ELSIF VISIT >=9 AND :NEW.C_NO = V_C_NO THEN:NEW.BILL := :NEW.BILL * 0.7;
ELSE DBMS_OUTPUT.PUT_LINE('no discount added');
END IF;
CLOSE C_APPTMNT;
END;
/
////////////////////////////////////////////////////////////////
CREATE OR REPLACE FUNCTION ADD_DISCOUNT(
I_C_NO INVOICE.C_NO%TYPE, I_BILL INVOICE.BILL%TYPE)
RETURN NUMBER
IS
V_BILL invoice.bill%type;
CURSOR C_APPTMNT
IS
SELECT C_NO,COUNT(C_NO)
FROM APPOINTMENT GROUP BY C_NO;
V_C_NO INVOICE.C_NO%TYPE;
VISIT NUMBER;
BEGIN
OPEN C_APPTMNT;
FETCH C_APPTMNT INTO V_C_NO,VISIT;
IF VISIT >=3
AND I_C_NO = V_C_NO THEN
V_BILL := I_BILL * 0.9;
ELSIF VISIT >=6
AND I_C_NO = V_C_NO THEN
V_BILL := I_BILL * 0.8;
ELSIF VISIT >=9 AND I_C_NO = V_C_NO THEN V_BILL := I_BILL * 0.7;
ELSE V_BILL:= I_BILL;
END IF;
CLOSE C_APPTMNT;
RETURN V_BILL;
END;
/
CREATE OR REPLACE TRIGGER DIS_BILL
BEFORE INSERT OR UPDATE OF INV_NO,C_NO ON INVOICE
FOR EACH ROW
DECLARE
BEGIN
:NEW.BILL:=ADD_DISCOUNT(:NEW.C_NO,:NEW.BILL);
END;
/
The second one is wrong. If a value is >= 9 it is also >=6 and >=3. Therefore, those elses will never be reached.
In the first one, you write output, but only if no discount was added. It feels like you just put that line there because it won't compile without it, but you can also add a line containing null; to make an empty statement block compiling.
There are more tricks to make this trigger faster. For one, you don't have to query all records, since you know the group. And you can do the calculation in the query, although that won't make it much faster.
Your trigger could look like this:
CREATE OR REPLACE TRIGGER APPLY_DISCOUNT
BEFORE INSERT OR UPDATE OF INV_NO, C_NO ON INVOICE
FOR EACH ROW
BEGIN
SELECT
CASE
WHEN COUNT(C_NO) >= 9 THEN 0.7
WHEN COUNT(C_NO) >= 6 THEN 0.8
WHEN COUNT(C_NO) >= 3 THEN 0.9
ELSE 1
END * :NEW.BILL
INTO
:NEW.BILL
FROM
APPOINTMENT
WHERE
C_NO = :NEW.C_NO;
END;
/
I think SELECT INTO :NEW.BILL should work, but if not, you could select it into a variable and then assign it to :NEW.BILL.
Related
i was making an application on oracle apex. now, If the capacity (cap) is 0, this trigger should delete the newly inserted tuple because that passenger_id will not be valid. what did i do wrong here?
Here is the code
create or replace trigger Ticket_katbo
before insert on passenger
for each row
declare
newcap number;
cap number;
flightpk number;
begin
flightpk := :new.flight_fid;
select flight_capacity into cap from flight where fid = flightpk;
if (cap>0) THEN
newcap := cap-1;
update flight set flight_capacity = newcap where fid = flightpk ;
ELSIF (cap=0 or cap = NULL) THEN
Delete from passenger where passenger_id = :new.passenger_id;
END IF;
end;
You are doing this before the insert. So raise an error if the capacity is not there:
create or replace trigger Ticket_katbo
before insert on passenger
for each row
declare
v_capacity number;
begin
select flight_capacity into v_capacity
from flight
where fid = :new.flight_fid;
if (cap <= 0) then
raise_application_error( -20001,
'No capacity for another passenger');
end if;
update flight
set flight_capacity = flight_capacity - 1
where fid = :new.flight_fid;
end;
I'am trying to use more than one if condition in PL\SQL, and it gives me an error:
ORA-06550: line 16, column 5:
PLS-00103: Encountered the symbol "LOOP" when expecting one of the following:
if
here's my code:
declare
price_var number;
st_numb number;
co_pr number;
cursor student_count is select STUDENT_NUMBER,COURSE_PRICE from COURSES;
begin
open student_count;
loop
fetch student_count into st_numb,co_pr;
if st_numb<10 then
update COURSES set COURSE_PRICE=co_pr*1.05;
elseif st_numb>10 then
update COURSES set COURSE_PRICE=co_pr*1.07;
end if;
exit when student_count%notfound;
end loop;
end
can you tell me where is the error ?
thanks.
First, I think in PL/SQL it's "elsif" and not "elseif". Then (I dont know if this is important or not), maybe you need the parenthesis around the conditions, I don't know.
Source: https://www.tutorialspoint.com/plsql/plsql_if_then_elsif.htm
You would need elsif, not elseif and also ; after end.
Try this one corrected:
declare
price_var number;
st_numb number;
co_pr number;
cursor student_count is
select student_number
,course_price
from courses;
begin
open student_count;
loop
fetch student_count
into st_numb
,co_pr;
if st_numb < 10 then
update courses set course_price = co_pr * 1.05;
elsif st_numb > 10 then --elsif, not elseif
update courses set course_price = co_pr * 1.07;
end if;
exit when student_count%notfound;
end loop;
end; --also need ";" after end
Just to add to massko's answer -
The ordinary Cursor FOR loop is simpler, more efficient and more reliable than the explicit open-fetch-exit-close. Also, you have to specify which row to update within the loop, otherwise you will update every row in the table. (And although the compiler doesn't care how you lay out your code or whether you put random words in uppercase, it's good to get into the habit of coding neatly.) Therefore as a first refactor we get this:
begin
for r in (
select course_id -- Assuming courses have a unique ID
, student_number, course_price
from courses
)
loop
if r.student_number < 10 then
update courses set course_price = r.course_price * 1.05
where course_id = r.course_id;
elsif r.student_number > 10 then
update courses set course_price = r.course_price * 1.07
where course_id = r.course_id;
end if;
end loop;
end;
But then why repeat the update statement twice when all that changes is the multiplication factor, and why we are looping through rows we don't do anything with? We can therefore simplify it a stage further:
begin
for r in (
select course_id, student_number, course_price
from courses
where student_number <> 10
)
loop
update courses
set course_price = r.course_price *
case
when r.student_number < 10 then 1.05
when r.student_number > 10 then 1.07
end
where course_id = r.course_id;
end loop;
end;
Then again, why do we even need a laborious row-by-row approach when we can do it in one shot?
begin
update courses c
set course_price = r.course_price *
case
when c.student_number < 10 then 1.05
when c.student_number > 10 then 1.07
end
where c.student_number <> 10;
end;
btw I don't know your data model, but storing a count of the number of students within each course record doesn't seem a reliable approach.
I have below piece of code that is below is stored procedure to clean up the table based on certain conditions that is there is view and if it get refreshed , now please advise if we need to put the logic in a loop.. if the clean-up doesn’t happen, it should sleep for 15 mins and then re-attempt , please advise how to achieve this
create or replace procedure table_clean_up
is
v_refresh_date date;
v_table_count_m integer;
v_table_count_p integer;
begin
select count(*) into v_table_count_m
from all_mviews
where owner = 'M_TO'
and mview_name in ('DC_CASHFLOW_VIEW','DC_CASHFLOW_VIEW_ZERO')
and last_refresh_type = 'COMPLETE';
if v_table_count_m = 2 then
select cast(last_start_date as date) into v_refresh_date
from user_scheduler_jobs
where job_name = 'TABLE_CLEAN_UP_JOB';
select count(*) into v_table_count_p
from all_mviews m
where m.owner = 'P_SM_TO'
and m.mview_name in ('DC_CASHFLOW_VIEW', 'DC_CASHFLOW_VIEW_ZERO')
and m.last_refresh_type = 'COMPLETE'
and m.last_refresh_date > v_refresh_date;
if v_table_count_p = 2 then
delete dc_cashflow_delta;
end if;
end if;
end;
...
DBMS_LOCK.SLEEP(60*35);
...
Not sure if this is possible, but I'm trying to see if I can convert this procedure to become a view because we've been having trouble with drives not populating the table when the procedure is run.
I'm trying to understand someone else's code and because of the cursors, I'm not even sure we can change this procedure to a view.
----------------------------------------------------------------------
--This Procedure will interface drive information on a nightly basis--
----------------------------------------------------------------------
Procedure HEMA_DRIVE_AUTO IS
v_start_date DATE := trunc(sysdate) -30;
v_end_date DATE := trunc(sysdate);
v_delete_stats_dt DATE := trunc(sysdate)-120;
v_total_registration_count NUMBER;
v_total_performed_count NUMBER;
v_total_collected_count NUMBER;
v_total_deferred_count NUMBER;
v_total_qns_count NUMBER;
v_existing_drive NUMBER;
v_existing_performed NUMBER;
v_maph_drive NUMBER;
--This Cursor will collect the initial data
cursor c_drive_info is
select dr.drive_id, dr.Start_time, dr.vehicle_id
from drives dr
--where dr.drive_id in(1605606);
where trunc(dr.start_time) between v_start_date and v_end_date;
--This Cursor will be used to decode the Donation Types
cursor c_procedure_codes is
select * from hema_donation_type_map hdt
where hdt.mobiles = 1 order by procedure_code_id;
--This Cursor will define the intentions but exclude theraputics inthe mapping
cursor c_intention is
select rsa_motivation_id,hema_intent_id from hema_intent_map
where rsa_motivation_id <> 4 order by rsa_motivation_id;
BEGIN
-- delete records older then 4 months
delete from hema_nightly h where trunc(h.drive_date) < v_delete_stats_dt;
commit;
FOR cur_drive IN c_drive_info LOOP
delete from hema_nightly where drive_id = cur_drive.drive_id;
commit;
-- Loop by motivation/intention
FOR cur_intent in c_intention LOOP
-- Loop to get the procedure code data
FOR cur_proc_code IN c_procedure_codes LOOP
v_total_registration_count := 0;
v_total_performed_count := 0;
v_total_collected_count := 0;
v_total_deferred_count := 0;
v_total_qns_count := 0;
v_maph_drive := 0;
-- get the count for all other procedures
select count(1)
into v_total_registration_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id;
--get the deferral count
select count(unique(r.registration_id))
into v_total_deferred_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id
and r.step_completed < 12
and exists (select rsc.registration_id
from reg_steps_completed rsc
where rsc.registration_id = r.registration_id
and rsc.collection_step_id = 99);
-- QNS count
select count(unique(r.registration_id))
into v_total_qns_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.step_completed < 12
and not exists (select rsc.registration_id
from reg_steps_completed rsc
where rsc.registration_id = r.registration_id
and rsc.collection_step_id = 99)
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id;
-- performed count is the difference between total registrations and total deferrals.
v_total_performed_count := v_total_registration_count -
(v_total_deferred_count +
v_total_qns_count);
-- not calulatind yield so keep count the same
v_total_collected_count := v_total_performed_count;
-- does this drive exist
select count(drive_id)
into v_existing_drive
from hema_nightly
where drive_id = cur_drive.drive_id
and procedure_id = cur_proc_code.procedure_code_id
and intent = cur_intent.hema_intent_id;
-- Is this an aph vehicle?
select count(vehicle_id)
into v_maph_drive
from vehicles
where veh_drive_type_uid = 2
and vehicle_id = cur_drive.vehicle_id;
if v_existing_drive > 0 then
update hema_nightly
set performed = performed + v_total_performed_count,
collected = collected + v_total_collected_count,
registered = registered + v_total_registration_count,
deferrals = deferrals + v_total_deferred_count,
qns = qns + v_total_qns_count,
drive_date = cur_drive.start_time,
mod_date = sysdate,
intent = cur_intent.hema_intent_id,
aph = v_maph_drive
where drive_id = cur_drive.drive_id
and procedure_id = cur_proc_code.procedure_code_id
and intent = cur_intent.hema_intent_id;
commit;
elsif v_existing_drive = 0 and v_total_registration_count > 0 then
insert into hema_nightly
(drive_id,
procedure_id,
performed,
collected,
registered,
deferrals,
qns,
drive_date,
mod_date,
intent,
aph)
values
(cur_drive.drive_id,
cur_proc_code.procedure_code_id,
v_total_performed_count,
v_total_collected_count,
v_total_registration_count,
v_total_deferred_count,
v_total_qns_count,
trunc(cur_drive.start_time),
sysdate,
cur_intent.hema_intent_id,
v_maph_drive);
commit;
end if;
v_existing_drive := 0;
end loop;
end loop;
end loop;
end hema_drive_auto;
Views don't perform DML (insert, update, delete) and they don't manage transactions with COMMIT and ROLLBACK; they only select and retrieve data.
this procedure code is to apply a discount to the new insert. The problem I'm having is too many records are being fetched. How would i fix this? do i use a trigger or a function?
Ok i have created a trigger but am i getting a PLS-00103: Encountered the symbol "=" when expecting one of the following: := . ( # % ;
CREATE OR REPLACE PROCEDURE CHECK_DISCOUNT
AS
V_COUNT NUMBER;
V_C_NO APPOINTMENT.C_NO%TYPE;
V_BILL APPOINTMENT.BILL%TYPE;
BEGIN
SELECT C_NO,COUNT(C_NO)
INTO V_C_NO,V_COUNT
FROM APPOINTMENT
GROUP BY C_NO;
SELECT BILL
INTO V_BILL
FROM APPOINTMENT;
IF V_COUNT=3 THEN
V_BILL:=V_BILL * 0.9;
END IF;
UPDATE APPOINTMENT
SET BILL = V_BILL
WHERE C_NO=:new.C_NO;
COMMIT;
END;
/
Create or replace TRIGGER CHECK_DISCOUNT
BEFORE INSERT OR UPDATE OF C_NO ON APPOINT
FOR EACH ROW
DECLARE CURSOR C_APPTMENT IS
SELECT C_NO,COUNT(C_NO)
FROM APPOINTMENT GROUP BY C_NO;
VISIT NUMBER; V_C_NO APPOINTMENT.C_NO%TYPE;
V_BILL APPOINTMENT.BILL%TYPE;
V_TEN NUMBER(3):=0.9;
BEGIN
LOOP
OPEN C_APPTMENT; FETCH C_APPTMENT INTO V_C_NO, VISIT;
EXIT WHEN C_APPTMENT%NOTFOUND;
SELECT BILL INTO V_BILL FROM APPOINTMENT;
IF VISITS =3 THEN V_BILL=V_BILL * V_TEN
WHERE :NEW.C_NO=V_C_NO;
UPDATE APPOINTMENT SET BILL:= V_BILL
WHERE:NEW.C_NO=V_C_NO;
END LOOP;
CLOSE C_APPTMENT;
END;
When using select ... into you must assure you only select one row.
Usually you will add a where clause with primary- or unique key columns. That way you are sure to select only one row. You will also have to provide for instances where no rows are selected. Add a exception handler to trap no_data_found exceptions
declare
v_bill appointment.bill%type;
begin
begin
select a.bill
into v_bill
from appointment a
where a.id = :my_id; -- assuming ID is a primary key colomn
exception
when no_data_found then
null;
end;
if v_bill is not null
then
--.. do something with v_bill
end if;
end;