SQL - Trigger is invalid and failed re-validation - sql

When a user creates an application for the table 'application' I want a copy placed into the table 'application_history'. I've tried creating a trigger and get the error that is in the title.
create or replace TRIGGER create_history AFTER INSERT ON application
FOR EACH ROW
DECLARE
BEGIN
insert into application_history values (:new.arn, :new.srn, :new.job_id, :new.application_status);
END;
Any help will be much appreciated.

As Justin suspected, you seem to be having more than 4 columns in application_history. Either provide values for all columns or specify columns lie 'insert into table1 (col1, col2) values (1,2)'
It worked for me after adding extra column 'hist_id'(just to give you an idea) ...
create table application (arn number, srn number, job_id number, application_status char(1) );
create table application_history (hist_id number, arn number, srn number, job_id number, application_status char(1) );
create or replace TRIGGER create_application_history AFTER INSERT ON application
FOR EACH ROW
DECLARE
BEGIN
insert into application_history values (1, :new.arn, :new.srn, :new.job_id, :new.application_status);
END;
/

I think you need a / at the end. So:
create or replace TRIGGER create_history AFTER INSERT ON application
FOR EACH ROW
DECLARE
BEGIN
insert into application_history values (:new.arn, :new.srn, :new.job_id, :new.application_status);
END;
/

Related

problem creating PL/SQL procedure and sequence

Here I have a question that I need to write a PL/SQL. The structure of the database is also linked. The question requires to use a sequence inside a procedure. I'm new to this and don't know if this works properly and my exec command doesn't seem working please help me out. Also is this how to look up the max shareholder_id that the sequence should start with, or can I a select inside create sequence?
Write a PL/SQL procedure called INSERT_DIRECT_HOLDER which will be used to insert new direct holders. Create a sequence to automatically generate shareholder_ids. Use this sequence in your procedure.
-Input parameters: first_name, last_name
DROP SEQUENCE shareholder_id_seq;
SELECT
MAX(shareholder_id)
FROM shareholder;
CREATE SEQUENCE shareholder_id_seq
INCREMENT BY 1
START WITH 25;
CREATE OR REPLACE PROCEDURE insert_direct_holder(
p_first_name in direct_holder.first_name%type,
p_last_name in direct_holder.last_name%type)
IS
v_shareholder_id NUMBER(6);
BEGIN
INSERT INTO DIRECT_HOLDER(direct_holder_id,first_name,last_name) values(shareholder_id_seq.nextval, p_first_name, p_last_name);
INSERT INTO shareholder (shareholder_id, type) VALUES (shareholder_id_seq.nextval,'Direct_Holder');
COMMIT;
END;
/
/* test command*/
exec insert_direct_holder( p_first_name, p_last_name );
You need to insert the record with same ID in both the tables.
also, you need to insert record into your parent table (shareholder) first and then child table(direct_holder).
CREATE OR REPLACE PROCEDURE insert_direct_holder(
p_first_name in direct_holder.first_name%type,
p_last_name in direct_holder.last_name%type)
IS
v_shareholder_id NUMBER(6);
BEGIN
v_shareholder_id := shareholder_id_seq.nextval;
INSERT INTO shareholder (shareholder_id, type) VALUES (v_shareholder_id,'Direct_Holder');
INSERT INTO DIRECT_HOLDER(direct_holder_id,first_name,last_name) values(v_shareholder_id, p_first_name, p_last_name);
COMMIT;
END;
/
/* test command*/
exec insert_direct_holder( p_first_name, p_last_name );
Sequence is created once to implement auto incrementing feature for any numeric column.
For the current use case it has to be created just once and left forever hopefully.Sequence can be modified in future if required.
If shareholder table has records in it already, then create the sequence with start value as SELECT MAX(shareholder_id) + 1 FROM shareholder; to avoid primary key constraint violation.
A slight modification is required for the stored procedure to use the same SHAREHOLDER.SHAREHOLDER_ID as the column has foreign key relationship with DIRECT_HOLDER.
Use INSERT ALL to insert to both the tables for the same sequence.nextval.
CREATE OR REPLACE PROCEDURE insert_direct_holder(
p_first_name in direct_holder.first_name%type,
p_last_name in direct_holder.last_name%type)
IS
BEGIN
INSERT ALL
INTO SHAREHOLDER
(shareholder_id, type) values(shareholder_id_seq.nextval,'Direct_Holder')
INTO DIRECT_HOLDER
(direct_holder_id,first_name,last_name) values
(shareholder_id_seq.nextval,p_first_name,p_last_name)
SELECT 'DUMMY' FROM dual;
COMMIT;
END;
/
dbfiddle demo with working code : https://dbfiddle.uk/?rdbms=oracle_18&fiddle=5d80488fb69d78d4b5087f06a5becf96

Creating Oracle SQL Trigger Error

This is what I need to accomplish: Create a TRIGGER named tgr_customer_insert that will fire AFTER a row is inserted into the customers table.
The trigger can be created after you create the cardholder table, so it can be in the same ps16a.sql file just created. This trigger will insert a row into the cardholder table when a row is inserted into the temp_table_customers table. Here are the columns to insert:
card_number (this is inserted using the seq_cardholder sequence number)
customer_id (this is a bind variable from the temp_table_customer table using the :new.column_name syntax)
credit_limit (this is a bind variable from the temp_table_customer table using the :new.column_name syntax)
This is my code:
`CREATE OR REPLACE TRIGGER tgr_customer_insert
AFTER INSERT
ON customers
FOR EACH ROW
BEGIN
-- Insert record into customers table
INSERT INTO cardholder
( card_number,
customer_id,
credit_limit
)
VALUES
( new.seq_cardholder,
:new.customer_id,
:new.credit_limit
);
END;
`
Error is: ORA-24344: success with compilation error
Line 3 Position 4.
Hair is being torn out. Thank you in advance for you time with this matter.
I think you are missing a ':' in INSERT VALUES for first value binding.
CREATE OR REPLACE TRIGGER tgr_customer_insert
AFTER INSERT
ON customers
FOR EACH ROW
BEGIN
-- Insert record into customers table
INSERT INTO cardholder
( card_number,
customer_id,
credit_limit
)
VALUES
( :new.seq_cardholder,
:new.customer_id,
:new.credit_limit
);
END;
If, "seq_cardholder" is a sequence then you have to use as below:
CREATE OR REPLACE TRIGGER tgr_customer_insert
AFTER INSERT
ON customers
FOR EACH ROW
BEGIN
-- Insert record into customers table
INSERT INTO cardholder
( card_number,
customer_id,
credit_limit
)
VALUES
( seq_cardholder.nextval,
:new.customer_id,
:new.credit_limit
);
END;

Values of the inserted row in a Trigger Oracle

I want a trigger that updates the value of a column, but I just want to update a small set of rows that depends of the values of the inserted row.
My trigger is:
CREATE OR REPLACE TRIGGER example
AFTER INSERT ON table1
FOR EACH ROW
BEGIN
UPDATE table1 t
SET column2 = 3
WHERE t.column1 = :new.column1;
END;
/
But as I using FOR EACH ROW I have a problem when I try it, I get the mutating table runtime error.
Other option is not to set the FOR EACH ROW, but if I do this, I dont know the inserted "column1" for comparing (or I dont know how to known it).
What can I do for UPDATING a set of rows that depends of the last inserted row?
I am using Oracle 9.
You should avoid the DML statements on the same table as defined in a trigger. Use before DML to change values of the current table.
create or replace trigger example
before insert on table1
for each row
begin
:new.column2 := 3;
end;
/
You can modify the same table with pragma autonomous_transaction:
create or replace trigger example
after insert on table1 for each row
declare
procedure setValues(key number) is
pragma autonomous_transaction;
begin
update table1 t
set column2 = 3
where t.column1 = key
;
end setValues;
begin
setValues(:new.column1);
end;
/
But I suggest you follow #GordonLinoff answere to your question - it's a bad idea to modify the same table in the trigger body.
See also here
If you need to update multiple rows in table1 when you are updating one row, then you would seem to have a problem with the data model.
This need suggests that you need a separate table with one row per column1. You can then fetch the value in that table using join. The trigger will then be updating another table, so there will be no mutation problem.
`create table A
(
a INTEGER,
b CHAR(10)
);
create table B
(
b CHAR (10),
d INTEGER
);
create trigger trig1
AFTER INSERT ON A
REFERENCING NEW AS newROW
FOR EACH ROW
when(newROW.a<=10)
BEGIN
INSERT into B values(:newROW.b,:newROW.a);
END trig1;
insert into A values(11,'Gananjay');
insert into A values(5,'Hritik');
select * from A;
select * from B;`

How can I modify this trigger to include column-name, old-value and new-value?

Suppose, a trigger that keeps track of AREA-table and records the changes in AREA_LOGGING_TABLE.
CREATE TABLE AREA
( AREA_NUMBER NUMBER,
AREA_NAME VARCHAR(20)
)
CREATE TABLE AREA_LOGGING_TABLE
( WHO_MODIFIED VARCHAR(20),
WHEN_MODIFIED DATE,
OLD_VALUE BLOB,
NEW_VALUE BLOB,
COLUMN_NAME VARCHAR(30)
)
I want to record username, date-time, column-name, old-data, and, new-data.
How can I do that?
CREATE OR REPLACE TRIGGER AREA_MODIFY_LOGGER_COLUMN_LVL
AFTER INSERT or UPDATE or DELETE
ON AREA
REFERENCING OLD AS old_data NEW AS new_data
FOR EACH ROW
DECLARE
v_username varchar2(10);
BEGIN
-- Find username of person performing the DELETE on the table
SELECT user INTO v_username
FROM dual;
-- Insert record into audit table
INSERT INTO AREA_LOGGING_TABLE(who_modified, when_modified, old_value, new_value)
VALUES ( v_username, sysdate, :old_data.area_number, :new_data.area_number);
END;
This is not working.
Besides, I don't know how to include column-name here.
The utl_raw.cast_to_raw function can be used to convert your values to BLOB.
Regarding the column_name, I think you can hard code it in the insert statement as you already doing it after :NEW and :OLD.
The nvl function was used to handle nulls in :NEW \ :OLD.
CREATE OR REPLACE TRIGGER AREA_MODIFY_LOGGER_COLUMN_LVL
AFTER INSERT or UPDATE or DELETE
ON AREA
REFERENCING OLD AS Old NEW AS New
FOR EACH ROW
DECLARE
v_username varchar2(10);
BEGIN
-- Find username of person performing the DELETE on the table
SELECT user INTO v_username
FROM dual;
if nvl(:old.area_number, -1) <> nvl(:new.area_number, -1) then
-- Insert record into audit table
INSERT INTO AREA_LOGGING_TABLE(who_modified, when_modified, old_value, new_value, column_name)
VALUES ( v_username, sysdate, utl_raw.cast_to_raw(:Old.area_number), utl_raw.cast_to_raw(:New.area_number), 'AREA_NUMBER');
end if;
if nvl(:old.area_name , '-1') <> nvl(:new.area_name, '-1') then
-- Insert record into audit table
INSERT INTO AREA_LOGGING_TABLE(who_modified, when_modified, old_value, new_value, column_name)
VALUES ( v_username, sysdate, utl_raw.cast_to_raw(:Old.AREA_NAME), utl_raw.cast_to_raw(:New.AREA_NAME), 'AREA_NAME');
end if;
END;
Just to giving you brief for trigger to enhance your concept for trigger
You have below inbuilt table created by SQL when trigger fired.
- deleted (i.e. select #empid=d.Emp_ID from deleted d)
- inserted (i.e. select #empid=i.Emp_ID from inserted i) (can be used in Insert/update operation)

How to find the source table rows used by a big complex query

We have a huge Oracle SQL query in our project which is using many views and tables for source data.
Is there any way to find the list of rows fetched from each source table by this big query when I run it?
Basically what we are trying to do is to create the bare minimum number of rows in the source tables so that the outer most big query returns at least a single record.
I have tried to run the smaller queries individually. But it is really time consuming and tedious. So I was wondering if there is a smarter way of doing this.
You can use Fine Grained Access Control. Here is how you might do it:
Step 1: Create a table to hold the list of rowids (i.e., the results you are looking for in this exercise)
CREATE TABLE matt_selected_rowids ( row_id rowid );
Step 2: Create a FGAC handler that will add a predicate whenever a base table is selected.
CREATE OR REPLACE PACKAGE matt_fgac_handler IS
FUNCTION record_rowid ( p_rowid rowid ) RETURN NUMBER DETERMINISTIC;
FUNCTION add_rowid_predicate (d1 varchar2, d2 varchar2 ) RETURN VARCHAR2;
END matt_fgac_handler;
CREATE OR REPLACE PACKAGE BODY matt_fgac_handler IS
FUNCTION record_rowid ( p_rowid rowid ) RETURN NUMBER DETERMINISTIC IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO matt_selected_rowids( row_id ) values ( p_rowid );
COMMIT;
RETURN -1;
END record_rowid;
FUNCTION add_rowid_predicate (d1 varchar2, d2 varchar2 ) RETURN VARCHAR2 IS
BEGIN
RETURN 'matt_fgac_handler.record_rowid (rowid) = -1';
END add_rowid_predicate;
END matt_fgac_handler;
Step 3: Add a policy to each base table used by your view (you can get the list by using DBA_DEPENDENCIES recursively, or just doing an explain plan and eyeballing it).
E.g.,
CREATE TABLE matt_table ( a number, b varchar2(80) );
CREATE INDEX matt_table_n1 on matt_table(a);
insert into matt_table values (1,'A');
insert into matt_table values (2,'B');
insert into matt_table values (3,'C');
insert into matt_table values (3,'D');
insert into matt_table values (3,'E');
insert into matt_table values (3,'F');
insert into matt_table values (4,'G');
insert into matt_table values (4,'H');
COMMIT;
BEGIN
DBMS_RLS.ADD_POLICY('APPS','MATT_TABLE','record_rowids_policy', NULL, 'matt_fgac_handler.add_rowid_predicate', 'select');
END;
So, at this point, whenever a user selects from my table, Oracle is automatically going to add a condition that calls my record_rowid function for each rowid.
For example:
delete from matt_selected_rowids;
SELECT /*+ INDEX */ * FROM matt_table where a = 2;
-- This gives your the rowids selected...
select r.row_id, o.object_name from matt_selected_rowids r left join dba_objects o on o.object_id =dbms_rowid.rowid_object(row_id);