We are trying to make a trigger to calculate the TOTAL_COST of a Car Rental based on the number of days it has been rented. The total cost for the rental is calculated based on the number of days and the cost of the vehicle. An additional tax of 12% is added to the total. If the rental is longer than 10 days a discount of 15% is subtracted from the total cost.
This Is Our Trigger:
create or replace trigger L5_Q8
After Update on E2_RESERVATIONS
for each row
Declare
TOTALDAYS NUMBER(4);
TotalCostBeforeTax Number(8);
BEGIN
TOTALDAYS := (trunc(:NEW.END_DATE) - TRUNC(:NEW.START_DATE)) + 1;
IF(TOTALDAYS > 10) THEN
SELECT V.COST_PER_DAY * TOTALDAYS * 0.85
INTO TotalCostBeforeTax
from E2_Reservations R
join E2_Vehicle V on R.V_ID = V.V_ID;
END IF;
IF(TOTALDAYS <= 10) THEN
SELECT V.COST_PER_DAY * TOTALDAYS
INTO TotalCostBeforeTax
from E2_Reservations R
join E2_Vehicle V on R.V_ID = V.V_ID;
END IF;
TotalCostBeforeTax := TotalCostBeforeTax * 1.12;
UPDATE E2_RESERVATIONS SET TOTAL_COST = 10.1 WHERE ROWID IN (SELECT MAX(ROWID) FROM E2_RESERVATIONS);
END;
These are Our Tables:
E2_RESERVATIONS:
E2_VEHICLE:
You're trying to calculate the total cost for the current record, right? So do the calculation before the change gets applied, not in an after update trigger. Also, there's way too much SQL in that trigger.
Try this:
create or replace trigger l5_q8
before update on e2_reservations
for each row
declare
totaldays number(4);
totalcostbeforetax number(8,2);
begin
-- length of reservation
totaldays := (trunc(:new.end_date) - trunc(:new.start_date)) + 1;
-- base cost of hire
select v.cost_per_day * totaldays
into totalcostbeforetax
from e2_vehicle v
where v.v_id = :new.v_id;
-- apply discount for long reservation
if(totaldays > 10) then
totalcostbeforetax := totalcostbeforetax * 0.85;
end if;
-- apply tax to total cost
:new.total_cost := totalcostbeforetax * 1.12;
end;
Note that is a BEFORE UPDATE trigger, not AFTER UPDATE.
Also, the variable totalcostbeforetax needs to handle decimals, because you're multiplying by 0.85, and anyway should match the declaration of the column total_cost.
"Wouldn't you need to join the vehicle table with reservations?"
A trigger has access to all the columns of the trigger it is built on. So you're already joined to the RESERVATIONS table, by dint of where v.v_id = :new.v_id, which is the vehicle ID of the current reservations record.
Related
I wrote These Two queries For Sample Table to insert into "z_exp14_resualt" From "z_exp14_main ". The First Query Works Correctly (Null) But Second That For rows have due_date on the main table (Not Null), not works correctly! . I Think Problem is The loop. It started before the due date and even goes beyond that . For NOT Nulls Insert must start from today(year and monts from sysdate and day from opening_date) and continue until due_date
Calculate For Null DUE_DATES
declare
i number := 1;
BEGIN
for i in 1..12 loop
insert into z_exp14_resualt
select dep_id,ADD_MONTHS(ADD_MONTHS(opening_date,trunc( months_between (sysdate,opening_date))),i),rate*balance
from z_exp14_main
WHERE due_date IS null;
end loop;
END;
/
And For Not Null Due_dates
DECLARE
diff number;
x number :=1;
BEGIN
for i in (select * FROM z_exp14_main WHERE due_date IS NOT null) loop
diff :=trunc( months_between (sysdate,i.opening_date));
WHILE (ADD_MONTHS(ADD_MONTHS(i.opening_date,diff),x)<i.due_date) LOOP
insert into z_exp14_resualt
select dep_id,ADD_MONTHS(ADD_MONTHS(i.opening_date,diff),x),rate*balance
from z_exp14_main WHERE due_date is not null ;
x :=x+1;
end loop;
end loop;
end;
/
sample Date On Main (z_exp14_main)
--
DEP_ID
DUE_DATE
BALANCE
RATE
OPENING_DATE
--
20056634
null
283428
10
15-SEP-16
--
20056637
null
180222
10
07-NOV-14
--
20056639
null
58741
10
28-AUG-14
--
40000020
27-NOV-21
5000000
22
31-MAR-14
--
40000023
23-APR-21
63000000
22
25-AUG-18
The Problem Is In the "While" condition. I correct it And This Is Final Answer:
DECLARE
diff number;
x number ;
BEGIN
for i in (select * FROM z_exp14_main WHERE due_date IS NOT null) loop
diff :=trunc( months_between (sysdate,i.opening_date));
x:=0;
WHILE (add_months(sysdate,x)<i.due_date) LOOP
insert into z_exp14_resualt values (i.dep_id,ADD_MONTHS(ADD_MONTHS(i.opening_date,diff),x),(i.rate*i.balance) );
x :=x+1;
end loop;
end loop;
end;
/
I am trying to compute cash balances bunch of tables and insert them into another table. I have a loop which computes the balance for each date of the year and inserts it into the balance table.
The problem is that this sql takes almost half an hour:
declare
d_i number :=1
begin
loop
insert into my_cash_table
select
portfolio ,
book_name, position_type, currency, cash_balance_cad
from
cash_balance cb
-- plus bunch of code here
...
where bal_date in &sdate + d_i
group by book_name, position_type, currency;
d_i := d_i + 1;
if d_i > 366 then
exit;
end if;
end loop;
end;
Is there anyway for me to optimize it so that I can grab all the data in a loop and then just bulk insert? Also can I run simply select statement in a loop, i.e. without doing an insert?
Thank you
Maybe you need optimization of your query.
In order to exclude context switching between sql and plsql you may go two ways.
Use subquery or with statement to generate numbers from 1 to 366 and multiply inserting rows by joining to it:
begin
insert into my_cash_table
with days_num_q as
(select level num
from dual
connect by level <= 366)
select portfolio,
book_name,
position_type,
currency,
cash_balance_cad
from cash_balance cb
join cnt_days
on bal_date = &sdate + days_num_q.num
/* -- plus bunch of code here
.....
*/;
end;
Create nested collection of numbers from 1 to 366 and use forall statement:
declare
type days_num_tbl_type is table of number;
days_num_tbl days_num_tbl_type;
d_i number;
begin
d_i := 1:
days_num_tbl := days_num_tbl_type();
days_num_tbl.extend(366);
while d_i <= 366 loop
days_num_tbl(d_i) := d_i;
d_i := d_i + 1;
end loop;
forall i in days_num_tbl.FIRST..days_num_tbl.LAST
insert into my_cash_table
select portfolio,
book_name,
position_type,
currency,
cash_balance_cad
from cash_balance cb
/* -- plus bunch of code here
.....
*/
where bal_date = &sdate + days_num_tbl(i);
end;
For this problem I need to increase employees.salary by 20% starting with the lowest salaries(asc order) until $100,000 is exhausted. I'm having difficulty finding a solution on how to save the updated amount left until $100,000 has been used. This is what I have so far. Thank you
declare
cursor mancur is
select salary from employees order by salary asc;
tempcur mancur%ROWTYPE;
profits number := 100000;
tempsalary employees.salary%type;
tempinc number(8,2);
begin
open mancur;
loop
fetch mancur into tempcur;
tempinc := tempcur.salary * 1.20;
tempsalary := profits - tempcur.salary;
dbms_output.put_line(tempcur.salary || ' increased by 20% ' || tempinc || ', Bonus amount left ' || tempsalary);
exit when mancur%notfound; --when 100,000 has been used
--update employees set salary = salary * 1.20 where employee_id = tempcur.employee_id;
end loop;
close mancur;
end;
/
begin
open mancur;
loop
fetch mancur into tempcur;
tempinc := tempcur.salary * 1.20;
profits := profits - (tempinc-tempcur.salary); -- You have to keep subtracting the increment amount to find if bonus is exhausted or not
if profits <=0 Then --When all your funds are exhausted
Exit
End if
dbms_output.put_line(tempcur.salary || ' increased by 20% ' || tempinc || ', Bonus amount left ' || profits);
exit when mancur%notfound; --when 100,000 has been used
--update employees set salary = salary * 1.20 where employee_id =
tempcur.employee_id;
end loop;
close mancur;
end;
/
declare
profits number := 100000;
tempinc number(8,2);
begin
for hike_loop in (select employee_id,salary from employees order by salary asc) loop
if profits <= 0 then
break;
end if;
tempinc := hike_loop.salary * 0.20;
if (tempinc <= profits) then
update employees set salary = salary * 1.20 where employee_id = hike_loop.employee_id;
profits := profits - tempinc;
else
break;
end if;
end loop;
end;
/
Just for fun: here is how this should be done in plain SQL. There is no need for functions or procedures for this task, unless it's homework in a PL/SQL class. And even then, the same MERGE statement should be run from the PL/SQL code, so that the processing is done at the set level, not row by row.
This also solves for the "small remaining amount" differently from the solutions posted so far. If there isn't enough to increase the "last" salary by 20%, it is increased by however much is left up to $100,000. And if two or more employees with the same salary are "the first to be left out", then the "remaining amount" is divided equally between these employees.
merge into employees e
using ( select employee_id,
sum (salary) over (order by salary) as sum_salary,
count(salary) over (partition by salary) as cnt
from employees
) x
on ( e.employee_id = x.employee_id )
when matched then update
set salary = 1.2 * salary + case when sum_salary <= 100000 / 0.2 then 0
else (100000 - 0.2 * sum_salary) / cnt end
where sum_salary - cnt * salary <= 100000 / 0.2
;
I'm developing a desktop application that works like Risiko!'s game, based on some database procedures.
I need to assign to each player a tot of rows which are not equal to each other assigned to other players.
I've tried for something like this, but doesn't work:
`create or replace PROCEDURE "giveterritory"
(idpartita NUMBER,nr_partecipanti number )
is
begin
DECLARE
CURSOR cur_file IS
SELECT *
FROM Player
WHERE idgame = Player.idgame ;
var_cur cur_file%ROWTYPE;
cont number ;
nr_territories_gioc_prec number ;
G1_Terr int ;
G2_Terr int ;
G3_Terr int ;
G1_ID number;
G2_ID number;
G3_ID number;
begin
cont:=0 ;
for var_cur in cur_file
loop
cont := cont + 1 ;
update territory
set Player_owner = var_cur.id_Player
where ROWNUM = MOD( MOD ( number_RANDOM(),42 ) ,ROWNUM );
G1_Terr := var_cur.number_territories_tot ; -- mi salvo i territories del primo Player
G1_ID := var_cur.id_Player ;
end if ;
if (cont=2 ) then
update territory
set Player_owner = var_cur.id_Player
where ROWNUM = MOD( MOD ( number_RANDOM(),42 -G1_Terr ) , ROWNUM ) and id_territory NOT IN ( select Player_owner from territory where ;
G2_Terr := var_cur.number_territories_tot ;
end if ;
if (cont=3 ) then
update territory
set Player_owner = var_cur.id_Player
where ROWNUM <= 42 - G1_Terr - G2_Terr;
--where ROWNUM <= 42 - var_cur.number_territories_tot * 2 ;
G3_Terr := var_cur.number_territories_tot ;
end if ;
end loop;
end;
end;`
number_random code:
create or replace FUNCTION "NUMBER_RANDOM"
return NUMBER
is
numbrndm int;
begin
select dbms_random.value(1,99999999) into numbrndm
from dual;
return numbrndm;
end;
If I understand correctly, you're trying to assign each player a random set of territories?
If so, sort the territories in random order and assign them to players round robin. Here's a MERGE statement that should do that. I apologize for any minor syntax errors: I am not in front of an Oracle database right now.
MERGE INTO territory t
USING (
WITH players AS ( SELECT rownum player#, id_player
FROM player
WHERE idgame=:idgame ),
players_count AS ( SELECT count(*) players_count FROM players )
-- Get the id_player for each assignment
SELECT assignedt.id_territory, p.id_player
FROM
(
-- Assign the randomly-ordered territories to players in round-robin format
SELECT randt.id_territory,
mod(rownum, pc.players_count)+1 player#
FROM (
-- Query the territories in a random order
SELECT t.id_territory
FROM territory t
ORDER BY dbms_random.random ) randt, players_count pc
-- Optional:
-- ... add a where clause to limit rownum <= some number to make
-- ... sure each player receives an even number of territories.
-- ... You'd need the count of territories to do that.
) assignedt inner join players p on p.player# = assignedt.player#
) u
ON ( u.id_territory = t.id_territory )
WHEN MATCHED THEN UPDATE SET t.id_player = u.id_player;
I'd put a random number column in your SQL selects for each row. Then you can order your record set by the random number directly in SQL.
Do this for both players and territory lists.
This example should work for any number of territories and players. If the territories don't divide up equally, it will be at random which players get one extra territory.
We are going to loop through the territories list once, in random order. We are going to step through the player list until we reach the end, then reload the player list again. This happens as many times as needed.
Might need to fix this for syntax or logic. I don't have an Oracle instance to test it.
CREATE OR REPLACE PROCEDURE giveterritory (pID_GAME PLS_INTEGER) IS
CURSOR c_player IS
SELECT id_player, dbms_random.value() rnd
FROM players
WHERE id_game = pID_GAME
ORDER BY 2 desc;
--
CURSOR c_territory IS
SELECT id_territory, dbms_random.value() rnd
FROM territories
WHERE id_game = pID_GAME
FOR UPDATE of id_player
ORDER BY 2 desc;
--
l_player_row c_player%ROWTYPE;
l_territory_row c_territory%ROWTYPE;
BEGIN
OPEN c_player;
FETCH c_player into l_player_row;
--
FOR r in c_territory
LOOP
UPDATE territories
SET id_player = l_player_row.id_player
WHERE CURRENT OF c_territory;
--
FETCH c_player INTO l_player_row;
IF c_player%NOTFOUND THEN
CLOSE c_player;
OPEN c_player;
FETCH c_player INTO l_player_row;
END IF;
END LOOP;
END;
CREATE or REPLACE PROCEDURE UPDATE_SUBTOTAL is
v_order_no orderline.order_no%type;
v_subtotal number(15);
CURSOR product_orderline_cur is
SELECT ol.order_no, sum(p.unit_price * ol.qty) as subtotal
from product p, orderline ol
where p.product_no = ol.product_no
group by ol.order_no;
BEGIN
OPEN product_orderline_cur;
LOOP
FETCH product_orderline_cur into v_order_no, v_subtotal;
EXIT when product_orderline_cur%notfound;
-- store subtotal in orders table
UPDATE orders
SET subtotal = v_subtotal
WHERE order_no = v_order_no;
END LOOP;
--an order may be created but no orderlines added yet,insert a 0
UPDATE orders
SET subtotal = 0
WHERE subtotal is null;
CLOSE product_orderline_cur;
END;
/
show errors;
CREATE OR REPLACE TRIGGER orders_before_update
BEFORE UPDATE ON orders
FOR EACH ROW
DECLARE v_sum INT;
BEGIN
SELECT SUM(p.unit_price * ol.qty)
INTO v_sum
FROM product p INNER JOIN orderline ol
ON p.product_no = ol.product_no
WHERE ol.order_no = :new.order_no;
:new.subtotal := COALESCE(v_sum, 0);
END;