How to use trigger between two tables in Oracle SQL? - oracle

Imagine this two tables
CREATE TABLE A
(
idA smallint primary key,
idP smallint ,
...
);
CREATE TABLE P
(
idP smallint primary key,
Type char , (ex:A, B, C)
...
);
I would like to behaviour like if in table A doesnt have the same type as in table P, is not for example a worker
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
BEGIN
IF((SELECT tp.Type
FROM P tp
WHERE tp.idP=:new.idP)!='W')
THEN RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;
I believe somethin is wrong

IN PL/SQL you need to select into a variable. But gmiley is correct, a foreign key or virtual column with an index is a better solution.
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
v_type CHAR(3);
BEGIN
SELECT tp.Type
INTO v_type
FROM P tp
WHERE tp.idP=:new.idP;
IF v_type != 'W'
THEN
RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;

You have to:
rename the column 'type' as this is a keyword and you'll get a lot of troubles when using it
add variable x to the declare section
use SELECT col INTO x construct
use x in the IF construct

Related

How to get the values from the Input parameter of (Object Type Table) in procedure in Oracle PL SQL?

Please anyone can assist me. I have a table type object.
CREATE OR REPLACE TYPE SI_PHONE_ACC_T AS TABLE OF SI_PHONE_ACC;
CREATE OR REPLACE TYPE SI_PHONE_ACC AS OBJECT
(
PHONE_ACC VARCHAR2(15);
);
Below is generally passed to the procedure as an input from UI end.
Example:
SI_PHONE_ACCT_T( SI_PHONE_ACC('123-345-6543'),SI_PHONE_ACC('999-999-9999'), SI_PHONE_ACC( 'ax878974545787wp')); -- first 2 are phone no., 3rd is account no.
Phonetable:
Emp_id--- phone_number
1 -- 123-345-6543
2 -- 999-999-9999
3--- 897-897-8781
Account table:
Emp_id--- account_number
10 -- A0000
20 -- B0000
30--- ax878974545787wp
CREATE OR REPLACE PACKAGE BODY order_mgr
IS
PROCEDURE ins_trees ( p_emp_details_in IN SI_PHONE_ACC_T )
BEGIN
-- Now, I need to retrieve emp_ids from Phone and Account tables based on the phone or account numbers passed in the input parameter. please let me know how to do this.
---once I get those emp_id's, i need to insert into employee table which contains only one column emp_id.
--Please let me know how to do this.
FOR i IN 1 .. p_emp_details_in.count
LOOP
INSERT into employee (emp_id)
values(??);
END IF;
END LOOP;
END;
PROCEDURE ins_trees ( p_emp_details_in IN SI_PHONE_ACC_T )
BEGIN
INSERT into employee (emp_id)
select Emp_id
from table(p_emp_details_in) t
, Phone_table t2
where t.PHONE_ACC = t2.phone_number
union
select Emp_id
from table(p_emp_details_in) t
, Account_table t2
where t.PHONE_ACC = t2.account_number
;
END;
/

Why am I getting this error: "ORA-00922: missing or invalid option" after running a PL/SQL procedure?

I created a table Patient with some attributes, like p_name, p_surname, p_number... I would like to create a procedure to transfer a patient from this table Patient to another table (Patient_backup), case his "p_number" attribute has received an input, deleting it from the first table and remaining only in the second. The second table has the same structure of the first one. I have coded the procedure like that.
CREATE TABLE patient (
p_number VARCHAR2(10) NOT NULL,
p_name VARCHAR2(15),
p_surname VARCHAR2(15),
p_street VARCHAR2(20),
p_city VARCHAR2(15)
);
CREATE TABLE patient_backup (
p_number VARCHAR2(10) NOT NULL,
p_name VARCHAR2(15),
p_surname VARCHAR2(15),
p_street VARCHAR2(20),
p_city VARCHAR2(15)
);
CREATE [OR REPLACE] PROCEDURE transfer (p_number VARCHAR2)
AS
CURSOR k1 IS SELECT p_number FROM patient;
BEGIN
OPEN k1;
LOOP
FETCH k1 INTO p_number;
IF p_number IS NULL THEN
dbms_output.put_line('empty');
ELSE
INSERT INTO patient_backup (SELECT * FROM patient);
Execute Immediate 'Drop Table patient;';
END IF;
END LOOP;
CLOSE k1;
END transfer;
But when I run it,I get the error "ORA-00922: missing or invalid option". Could you help me with that? I wonder if the code is correct. I have read a material about PL/SQL, but the concepts were not connected to each other, so I just tried to gather everything together, and I hope it is correct. Could you help me to correct this code and make it work?
It's hard to tell where exactly the error is, but my guess is: remove the ; from inside the string for execute immediate.
But I think you want do not want to DROP the table - that removes the table completely from the database including all rows and its definition. It won't be accessible after that.
I think what you really want is to DELETE a row from that table, not remove the table completely.
Also: the whole loop is completely unnecessary (and inefficient). You can do that with two simple SQL statements:
insert into patient_backup
select *
from patient
where p_number = 42; --<< to pick one patient
delete from patient
where p_number = 42;
Putting that into a procedure:
CREATE PROCEDURE transfer (p_number_to_delete VARCHAR2)
AS
BEGIN
insert into patient_backup
select *
from patient
where p_number = p_number_to_delete;
delete from patient
where p_number = p_number_to_delete;
END transfer;
It's highly recommended to not use the name of a column as the name of a parameter. That's why I named the parameter p_number_to_delete (but p_number is a bad name for a column that isn't a number to begin with - but that's a different discussion)
I think you need to DECLARE your cursor before you define it.
In Your procedure code have some error
1.P_NUMBER input parameter cannot be used into statment
2.don't use semicolon inside the EXECUTE IMMEDIATE string
3. in loop statement you should use exit otherwise it will run
continuously
Here the code
CREATE OR REPLACE PROCEDURE TRANSFER (P_NUMBER IN VARCHAR2) AS
CURSOR K1 IS
SELECT P_NUMBER FROM PATIENT;
P_NUM PLS_INTEGER;
BEGIN
OPEN K1;
LOOP
FETCH K1 INTO P_NUM;
IF P_NUM IS NULL THEN
DBMS_OUTPUT.PUT_LINE('EMPTY');
ELSE
INSERT INTO PATIENT_BACKUP (SELECT * FROM PATIENT);
DELETE FROM PATIENT;
END IF;
EXIT WHEN P_NUM IS NULL;
END LOOP;
CLOSE K1;
END TRANSFER;

Use fields from another table as max-number-rows-with-type constrain in oracle

I have two tables:
CREATE TABLE users (
user_id INT(7) NOT NULL,
restricted_type VARCHAR(64) NOT NULL
)
CREATE TABLE type_restrictions (
name VARCHAR(64) NOT NULL,
restriction INT NOT NULL
)
I want to check on insert, that there are no more than restriction users with restricted_type = type_restriction.name.
At this point I'm inserting data with this query:
INSERT INTO users (user_id, restricted_type) SELECT <id>, <type> FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM type_restrictions T
WHERE T.name = <type> AND T.restriction < (
SELECT COUNT(*)
FROM users U
WHERE U.user_id = <id> AND U.restricted_type = <type>)
)
But with two or more parallel queries it is possible to end up with more users with restricted_type than actual restriction for this type.
Is there any way to make such constraint work? (Also, I always insert only one row per query, if it helps)
You cannot use select ... in constraint. You cannot select from table which you are inserting into in normal trigger. What you can do? Materialized view (probably, I am not sure) or compound trigger. Here is my (working) try:
create or replace trigger trg_users_restrict
for insert on users compound trigger
type tt is table of number index by varchar2(5);
vt tt;
i varchar2(5);
v_max int;
before statement is
begin
for r in (select restricted_type, count(1) cnt from users group by restricted_type)
loop
vt(r.restricted_type) := r.cnt;
end loop;
end before statement;
after each row is
begin
begin
vt(:new.restricted_type) := vt(:new.restricted_type) + 1;
exception when no_data_found then
vt(:new.restricted_type) := 1;
end;
end after each row;
after statement is
begin
i := vt.first;
while i is not null loop
select nvl(max(restriction), 0) into v_max
from type_restrictions where name = i;
if vt(i) > v_max then
raise_application_error( -20001,
'maximum number exceeded for restriction type ' || i );
end if;
i := vt.next(i);
end loop;
end after statement;
end trg_users_restrict;
In before statement I grouped data from users table into collection. In after each row I increased proper values in collection for newly inserted row(s). In after statement I check if data in collection exceeds allowed ranges in table type_restrictions.
When two sessions insert concurent data then this which commits last causes exception.

How to use %ROWTYPE when inserting into Oracle table with identity column?

I have an Oracle 12c database with a table containing an identity column:
CREATE TABLE foo (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
bar NUMBER
)
Now I want to insert into the table using PL/SQL. Since in practice the table has many columns, I use %ROWTYPE:
DECLARE
x foo%ROWTYPE;
BEGIN
x.bar := 3;
INSERT INTO foo VALUES x;
END;
However, it give me this error:
ORA-32795: cannot insert into a generated always identity column
ORA-06512: at line 5
Since it is very good for code readability and maintainability, I do not want to stop using %ROWTYPE. Since I under no circumstances want to allow anything but the automatically generated ID's I do not want to lift the GENERATED ALWAYS restriction.
This article suggests that the only way to be able to use %ROWTYPE is to switch to GENERATED BY DEFAULT ON NULL. Is there no other way to fix this?
The only thing I can think of, since you're on 12c is to make the identity column INVISIBLE, like the code below.
The problem is that it makes getting a %ROWTYPE with the id a little more difficult, but it's doable.
Of course, it may also confuse other people using your table to not see a primary key!
I don't think I'd do this, but it is an answer to your question, for what that's worth.
DROP TABLE t;
CREATE TABLE t ( id number invisible generated always as identity,
val varchar2(30));
insert into t (val) values ('A');
DECLARE
record_without_id t%rowtype;
CURSOR c_with_id IS SELECT t.id, t.* FROM t;
record_with_id c_with_id%rowtype;
BEGIN
record_without_id.val := 'C';
INSERT INTO t VALUES record_without_id;
-- If you want ID, you must select it explicitly
SELECT id, t.* INTO record_with_id FROM t WHERE rownum = 1;
DBMS_OUTPUT.PUT_LINE(record_with_id.id || ', ' || record_with_id.val);
END;
/
SELECT id, val FROM t;
You can create a view and insert there:
CREATE OR REPLACE VIEW V_FOO AS
SELECT BAR -- all columns apart from virtual columns
FROM foo;
DECLARE
x V_FOO%ROWTYPE;
BEGIN
x.bar := 3;
INSERT INTO V_FOO VALUES x;
END;
I think that a mix of former answers - using view with invisible identity column - is a optimal way to accomplish this task:
create table foo (id number generated always as identity primary key, memo varchar2 (32))
;
create or replace view fooview (id invisible, memo) as select * from foo
;
<<my>> declare
r fooview%rowtype;
id number;
begin
r.memo := 'first row';
insert into fooview values r
returning id into my.id
;
dbms_output.put_line ('inserted '||sql%rowcount||' row(s) id='||id);
end;
/
inserted 1 row(s) id=1

How to call a Procedure which uses the same table in after trigger

I want to use the same table data after deleting the data which fails in following method.
The issue I faced is the latest change is not getting committed before the after trigger is completed.
create table test_tbl(id_ number, type_ varchar2(100) , count_ number);
create table test_count_tbl(type varchar2(100), count_ number) ;
begin
insert into test_tbl(id_ , type_ , count_ ) values (1,'type1', 10 );
insert into test_tbl(id_ , type_ , count_ ) values (2,'type1', 20 );
insert into test_tbl(id_ , type_ , count_ ) values (3,'type2', 10 );
insert into test_tbl(id_ , type_ , count_ ) values (4,'type2', 40 );
insert into test_tbl(id_ , type_ , count_ ) values (5,'type3', 10 );
insert into test_tbl(id_ , type_ , count_ ) values (6,'type3', 60 );
commit;
end;
create or replace procedure test_count_update_p( p_type_ in varchar2)
is
begin
MERGE INTO test_count_tbl D
USING (select type_, sum(count_) count_sum_
from test_tbl
where type_ = p_type_
group by type_ ) S ON (D.type = S.count_sum_)
WHEN MATCHED THEN UPDATE SET D.count_ = S.count_sum_
-- DELETE WHERE (S.salary > 8000)
WHEN NOT MATCHED THEN INSERT (D.type, D.count_)
VALUES (S.type_, S.count_sum_);
commit;
end ;
CREATE OR REPLACE TRIGGER test_tbl_trigger
AFTER INSERT OR DELETE OR UPDATE ON test_tbl
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
L_TYPE VARCHAR2(100);
BEGIN
if DELETING THEN
L_TYPE := :OLD.TYPE_;
end if;
IF UPDATING OR INSERTING THEN
L_TYPE := :NEW.TYPE_;
end if;
test_count_update_p(L_TYPE);
COMMIT;
END;
Do the following to see the exact issue..
begin
insert into test_tbl(id_ , type_ , count_ ) values (7,'type4', 60 );
commit;
end;
select * from test_tbl ;
record is inserted to the table.
select * from test_count_tbl ;
record is not counted in the this table yet.
begin
delete test_tbl where id_ = 7;
commit ;
end;
select * from test_tbl ;
deleted the record.
select * from test_count_tbl ;
Counted the record which is not available in the table test_tbl;
You can't.
A normal row-level trigger cannot query the table the trigger is defined on because that would raise a mutating table exception. I'm assuming that's why you have declared your trigger to use an autonomous transaction (an autonomous transaction for anything other than persistent logging is almost certainly an error). If you do that, however, your trigger cannot see the uncommitted changes made by the triggering transaction. That's the problem you're encountering now.
An alternative would be to use a compound trigger. You'd declare a collection of test_table.type_%type, you would add the values that are changing to this collection in the row-level portion of your trigger, and then you would iterate over the elements in the collection in the after-statement portion of your trigger. A statement-level trigger is allowed to query the table on which the trigger is defined so you can call your procedure from the after-statement portion of your compound trigger.
Your best action is to drop the TEST_COUNT_TBL table altogether. Just create a view by that name:
create view TEST_COUNT_TBL as
select type_ Type, sum( count_ ) Count
from test_tbl
group by type_;
Then you will always have accurate, up-to-date information at your beck and call but never have to worry about doing strange and wonderful things with triggers.
Used Compound Trigger and removed PRAGMA AUTONOMOUS_TRANSACTION; as well as removed commit statements. Still committed data could access and do the calculations in an external function and write to a separate table.

Resources