Warning: Trigger created with compilation error - oracle

I have two tables, seat_allocation and programme_code. I want to create a trigger for checking if the number of entries into the seat_allocation are equal to the max_seats(column in programm_code table) for that prog_code(column in programme_code table). This is my code:
SQL> create or replace trigger seats_full
2 before insert on seat_allocation
3 for each row
4 declare
5 cnt programme_code.max_seats%type;
6 max programme_code.max_seats%type;
7 begin
8 select count(*) into cnt from seat_allocation where prog_code=(select prog_code from programme_code where prog_code = :NEW.prog_code);
9 select max_seats into max from programme_code where prog_code=(select prog_code from programme_code where prog_code = :NEW.prog_code);
10 if max=cnt then
11 RAISE_APPLICATION_ERROR(-21000,'No vacant seats available');
12 end if;
13 end;
14 /
It gives Warning:Trigger created with compilation error. Can you please help me figure out what's wrong?

Variable name can'be MAX, it is reserved for the function with the same name. Change it to e.g. v_max_seats.
Apart from that, it seems that you're selecting from the seat_allocation table (line #8), while the trigger fires on insert on the same table. It'll cause the mutating table error so - you'll have to do something. Nowadays, it is a compound trigger that fixes that. If your database version doesn't support it, you'll use a package. There are examples on the Internet.
Also, why using a subquery? What's wrong with e.g.
select max_seats into v_max_seats from programme_code where prog_code = :new.prog_code

Related

Using EXECUTE IMMEDIATE based on entries of table

I (using Oracle 12c, PL/SQL) need to update an existing table TABLE1 based on information stored in a table MAP. In a simplified version, MAP looks like this:
COLUMN_NAME
MODIFY
COLUMN1
N
COLUMN2
Y
COLUMN3
N
...
...
COLUMNn
Y
COLUMN1 to COLUMNn are column names in TABLE1 (but there are more columns, not just these). Now I need to update a column in TABLE1 if MODIFY in table MAP contains a 'Y' for that columns' name. There are other row conditions, so what I would need would be UPDATE statements of the form
UPDATE TABLE1
SET COLUMNi = value_i
WHERE OTHER_COLUMN = 'xyz_i';
where COLUMNi runs through all the columns of TABLE1 which are marked with MODIFY = 'Y' in MAP. value_i and xyz_i also depend on information stored in MAP (not displayed in the example).
The table MAP is not static but changes, so I do not know in advance which columns to update. What I did so far is to generate the UPDATE-statements I need in a query from MAP, i.e.
SELECT <Text of UPDATE-STATEMENT using row information from MAP> AS SQL_STMT
FROM MAP
WHERE MODIFY = 'Y';
Now I would like to execute these statements (possibly hundreds of rows). Of course I could just copy the contents of the query into code and execute, but is there a way to do this automatically, e.g. using EXECUTE IMMEDIATE? It could be something like
BEGIN
EXECUTE IMMEDIATE SQL_STMT USING 'xyz_i';
END;
only that SQL_STMT should run through all the rows of the previous query (and 'xyz_i' varies with the row as well). Any hints how to achieve this or how one should approach the task in general?
EDIT: As response to the comments, a bit more background how this problem emerges. I receive an empty n x m Matrix (empty except row and column names, think of them as first row and first column) quarterly and need to populate the empty fields from another process.
The structure of the initial matrix changes, i.e. there may be new/deleted columns/rows and existing columns/rows may change their position in the matrix. What I need to do is to take the old version of the matrix, where I already have filled the empty spaces, and translate this into the new version. Then, the populating process merely looks if entries have changed and if so, alters them.
The situation from the question arises after I have translated the old version into the new one, before doing the delta. The new matrix, populated with the old information, is TABLE1. The delta process, over which I have no control, gives me column names and information to be entered into the cells of the matrix (this is table MAP). So I need to find the column in the matrix labeled by the delta process and then to change values in rows (which ones is specified via other information provided by the delta process)
Dynamic SQL it is; here's an example, see if it helps.
This is a table whose contents should be modified:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
This is the map table:
SQL> select * from map;
COLUMN CB_MODIFY VALUE WHERE_CLAUSE
------ ---------- ----- -------------
NAME Y Scott where id <= 3
SALARY N 1000 where 1 = 1
Procedure loops through all columns that are set to be modified, composes the dynamic update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select m.column_name, m.value, m.where_clause
5 from map m
6 where m.cb_modify = 'Y'
7 )
8 loop
9 l_str := 'update test set ' ||
10 cur_r.column_name || ' = ' || chr(39) || cur_r.value || chr(39) || ' ' ||
11 cur_r.where_clause;
12 execute immediate l_str;
13 end loop;
14 end;
15 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Scott 100
2 Scott 200
3 Scott 0
4 0
SQL>

Checking a trigger function

create or replace trigger addbook
before insert
on book
for each row
DECLARE
xcost float;
begin
select avg(cost) into xcost
from book;
if :new.cost > xcost then
INSERT INTO book (
book_no,
title,
cost
) VALUES (:new.book_no,
:new.title,
:new.cost
);
end if;
end addbook;
/
// .............. This is my trigger .It executed correctly but when I do the following:
SQL> INSERT INTO book (
2 book_no,
3 title,
4 cost
5 ) VALUES (104,
6 'C++ Brain work',
7 500
8 );
// ............I got error like this :
INSERT INTO book (
*
ERROR at line 1:
ORA-00036: maximum number of recursive SQL levels (50) exceeded
ORA-00036: maximum number of recursive SQL levels (50) exceeded
ORA-06512: at "EXP8.ADDBOOK", line 4
ORA-04088: error during execution of trigger 'EXP8.ADDBOOK'
ORA-06512: at "EXP8.ADDBOOK", line 8
// What I will do???????
Your trigger is triggering itself which causes a infinite loop and triggers the fail-safe protection showed. Either change your schema in order to record that changes in a second table or refactor your trigger to avoid entering in the infinite loop (additional column that according to its value would skip the second insertion)
I would prefer the first approach as it is much cleaner but I am speculating on your model.

How to return rows based on the database user and the table's contents?

I have a following table:
id name score
1 SYS 4
2 RHWTT 5
3 LEO 4
4 MOD3_ADMIN 5
5 VPD674 4
6 SCOTT 5
7 HR 4
8 OE 5
9 PM 4
10 IX 5
11 SH 4
12 BI 5
13 IXSNEAKY 4
14 DVF 5
I want to create a policy function in Oracle SQL that makes sure of the following things:
If a user(Leo) is executing a select statement on this table, it only gets 3 LEO 4.
sys_dba gets all the results no matter what.
I have given select permissions to Leo on this table created by Scott.
I am getting stuck at writing this complex PL/SQL function. I tried the following and it states compilation errors. Also, I think it does not do what I intend to do:
CREATE FUNCTION no_show_all (
p_schema IN NUMBER(5),
p_object IN VARCHAR2
)
RETURN
AS
BEGIN
RETURN 'select avg(score) from scott.rating';
END;
/
Based on your previous question and info you posted, here's how I understood the question: if you granted select on the whole table to any user, then it is able to fetch all rows from it. You have to further restrict values.
One option - as we're talking about the function - is to use case in where clause.
Here's an example.
Sample data:
SQL> create table rating as
2 select 1 id, 'sys' name, 4 score from dual union all
3 select 3, 'leo' , 3 from dual union all
4 select 6, 'scott' , 5 from dual union all
5 select 7, 'hr' , 2 from dual;
Table created.
Function:
it accepts username as a parameter (mind letter case! In my example, everything is lowercase. In your, perhaps you'll have to use upper function or something like that)
case says: if par_user is equal to sys, let it fetch all rows. Otherwise, fetch only rows whose name column's value is equal to par_user
return the result
So:
SQL> create or replace function f_rating (par_user in varchar2)
2 return number
3 is
4 retval number;
5 begin
6 select avg(score)
7 into retval
8 from rating
9 where name = case when par_user = 'sys' then name
10 else par_user
11 end;
12 return retval;
13 end;
14 /
Function created.
Let's try it:
SQL> select f_rating('sys') rating_sys,
2 f_rating('hr') rating_hr
3 from dual;
RATING_SYS RATING_HR
---------- ----------
3,5 2
SQL>
I suggest creating a view for each user, like so
create view THE_VIEW as select * from TABLE where NAME = user
Then grant access to the view only.
Now it doesn't matter what kind of query a user tries to perform on your table, she will only get one row back.
Of-course the DBA user can access all the table data.

Trigger to monitor is value acceptable or not

I have Oracle 11g and a table called CODES and there is column ID and CODESA as follow:
ID CODESA
1 9999
1 8889
2 77777
2 99999
3 1234
3 4321
4 565656
etc.
Then I need to update another table CODES2 and column CODESB based on ID in CODES table
I need a trigger to monitor this.
Let´s say I monitoring ID = 2 with this trigger and all different CODESA´s under that ID,
you can see that only these are possible to update in CODESB
2 77777
2 99999
How to make a trigger to launch if user is trying to enter some code in CODESB
which is for example from ID = 3 ?
Appreciate your help. Thanks,
Some_user
#APC is correct. We can use foreign key but lets say OP dont want those columns to be primary or unique, in that case trigger is the solution.
Create or replace trigger codes2_trg
Before insert or update On codes2
For each row
Declare
Cnt number;
Begin
Select count(1) into cnt
From codes where (id, codesa) = (:new.id, :new.codesb);
If cnt = 0 then
Raise_application_error('-20001', 'these balues are not allowed.');
End if;
End;
/
Cheers!!

literal string works but variables take forever

I have a query that works when I have fixed values. ie:
select
count(*)
from
address a
where
a.primary_name like upper('cambourne court') and
a.secondary_name like upper('flat 9');
However replace the upper('flat 9') with a variable which is second_name:=upper('flat 9') and the search now returns all 111 addresses in 'cambourne court'.
Why would this be?
EDIT: This is the complete address.sql file (with comments removed)
declare
address_details address%rowtype;
current_loc varchar2(32);
prime_name varchar2(255);
prime_number varchar2(255);
second_name varchar2(255);
street_name varchar2(255);
town_name varchar2(255);
success boolean;
the_count number;
begin
prime_name:=upper('&&primary_name');
prime_number:=upper('&&primary_number');
second_name:=upper('&&secondary_name');
street_name:=upper('&&street_name');
town_name:=upper('&&town_name');
success:=true;
-- error checking here (removed for brevity)
if success then
current_loc := 'finding address';
select
count(*)
into
the_count
from
dependency d,
address a,
street s
where
d.dep_obj_id1 = 2 and
d.dep_obj_id2 = 1 and
a.loc_id = d.dep_id1 and
s.loc_id = d.dep_id2 and
a.primary_name like prime_name and
a.secondary_name like second_name and
s.name like street_name and
s.town like town_name;
end if;
dbms_output.put_line('success: address found '||the_count);
exception
when too_many_rows then
dbms_output.put_line('failure: too many rows while '||current_loc);
when no_data_found then
dbms_output.put_line('failure: no rows found while '||current_loc);
when others then
dbms_output.put_line('failure: general error while '||current_loc);
end;
/
Update: I restarted SQL*Plus which seemed to have fixed the break.
Replacing prime_name and second_name with the actual strings means the code runs in less than a second. With variables means it takes more than 2 minutes.
Your symptoms correspond to having a PL/SQL variable with the same name as a column in the table.
[Edit]
feeling somewhat guilty with an upvote that wasn't the correct answer, so I tried to reproduce and don't get your results:
SQL> select * from address
2 ;
PRIMARY_NAME SECONDARY_NAME
------------------------------ ------------------------------
CAMBOURNE COURT FLAT 9
CAMBOURNE COURT FLAT 10
SQL> declare
2 second_name varchar2(30) := upper('flat 9');
3 x pls_integer;
4 cursor c is
5 select
6 count(*)
7 from address a
8 where
9 a.primary_name like upper('cambourne court') and
10 a.secondary_name like upper('flat 9')
11 ;
12 begin
13 select count(*) into x
14 from address a
15 where
16 a.primary_name like upper('cambourne court') and
17 a.secondary_name like upper('flat 9');
18 dbms_output.put_line('literal: '||x);
19 select count(*) into x
20 from address a
21 where
22 a.primary_name like upper('cambourne court') and
23 a.secondary_name like second_name;
24 dbms_output.put_line('variable: '||x);
25 end;
26 /
literal: 1
variable: 1
PL/SQL procedure successfully completed.
The 111 records suggests second_name doesn't contain the value you expect; how are you capturing &&secondary_name, and can you check the value it actually has before and after your omitted validation section? From the results it seems to contain '%' rather than 'flat 9', but I assume you've already checked that.
The speed issue suggests the optimiser is changing behaviour in a way that's changing the join order and/or the indexes being used. By default that could be joining every street row with every every address record that has a Cambourne Court and only then doing the dependency checks, but it will vary quite a bit based on what indexes it thinks it can use and any stats that are available. The difference is that with the literals, even though you're using like there are no wildcards so it may know it can use an index on the primary_name and/or secondary_name; in the variable version it can't know that when the query is parsed so has to assume worse-case, which would be '%'. Which it may actually be getting if it's returning 111 addresses.
Without doing an explain plan it's hard to guess exactly what's going on, but you could try adding some optimiser hints to at least try and get the join order right, and even to use an index - though that should possibly not stay in place if you can ever have values starting with %. That might tell you what's being done differently.
The explain plan may be suggestive.
After running it, find the sql_id from v$sql for that statemnet
select sql_text, sql_id from v$sql where lower(sql_text) like '%address%street%';
Then plug that into
select * from table(dbms_xplan.display_cursor('1mmy8g93um377'));
What you should see at the bottom is something like this, which would show whether there were any oddities in the plan (eg using a column in one of the tables, using a function...).
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("A"."LOC_ID"="D"."DEP_ID1" AND "S"."LOC_ID"="D"."DEP_ID2")
4 - filter(("A"."PRIMARY_NAME" LIKE :B4 AND "A"."SECONDARY_NAME" LIKE
:B3))
6 - filter(("S"."NAME" LIKE :B2 AND "S"."TOWN" LIKE :B1))
7 - filter(("D"."DEP_OBJ_ID1"=2 AND "D"."DEP_OBJ_ID2"=1))
Alex has pointed the probable cause. Tables are indexed and using "like" with a variable is a case of index deactivation. Optimizers treat "like" expressions with constants that have no wildcards or placeholders as "=", so indexes if present are considered.
Drop your index on those columns and you'll get same bad performance with constants or variables. Actually don't do it, just autotrace and compare plans.
Regards,

Resources