PLS-00201: identifier must be declared in ORACLE TRIGGER - oracle11g

I have this set of data. I want to develop a trigger which fires when rows with ICICUT = IB are updated. So far it's proving a challenge because of error "PLS-00201: identifier 'OLD.ICICUT' must be declared". What am I missing?
ICICUT ICICU ICAME
IB 11368 65625
V 711340 63808
V 711313 24812
IB 711265 60238
O 711322 21570
RB 711370 348590
my trigger....
CREATE OR REPLACE TRIGGER EOGONY.F0011_audit
BEFORE UPDATE
ON INVOICES
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
WHEN (OLD.ICICUT = 'IB')
ENABLE
DECLARE
v_date varchar2(30);
BEGIN
SELECT TO_CHAR(sysdate, 'DD/MON/YYYY HH24:MI:SS') INTO v_date from dual;
INSERT INTO AUDIT_HISTORY_F0011 (ICICUT, ICICU, ICUSER, ICDICJ, OLD_ICAME, NEW_ICAME, ENTRY_DATE, OPERATION)
VALUES (:OLD.ICICUT, :OLD.ICICU , :NEW.ICUSER, :OLD.ICDICJ, :OLD.ICAME, :NEW.ICAME, v_date, 'Updating');
END;

I managed to compile code without errors after applying these changes...
CREATE OR REPLACE TRIGGER EOGONY.F0011_audit
BEFORE UPDATE
ON INVOICES
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
ENABLE
DECLARE
v_date varchar2(30);
BEGIN
IF ( :OLD.ICICUT = 'IB' ) THEN
SELECT TO_CHAR(sysdate, 'DD/MON/YYYY HH24:MI:SS') INTO v_date from dual;
INSERT INTO AUDIT_HISTORY_F0011 (ICICUT, ICICU, ICUSER, ICDICJ, OLD_ICAME, NEW_ICAME, ENTRY_DATE, OPERATION)
VALUES (:OLD.ICICUT, :OLD.ICICU , :NEW.ICUSER, :OLD.ICDICJ, :OLD.ICAME, :NEW.ICAME, v_date, 'Updating');
END IF;
END;
/

Related

Error ORA-04079: invalid trigger specification

Is it possible to have multiple tables in one TRIGGER? Let say I have Employee, Skill and Customer Tables and I have Eventlogs table to capture the audit. I tried to add Skill_T but I got ORA-04079 error. Any correction? Thank you!
NOTE: I am using Oracle SQL Developer Oracle11gEE
CREATE OR REPLACE TRIGGER AUDIT_REC
AFTER INSERT OR DELETE OR UPDATE ON EMPLOYEE_T, SKILL_T
FOR EACH ROW
DECLARE
V_LOGID NUMBER;
V_USER VARCHAR(30);
V_DATE VARCHAR(30);
BEGIN
SELECT EVENTLOG_ID_SEQ.NEXTVAL, USER, SYSDATE INTO V_LOGID, V_USER, V_DATE FROM DUAL;
IF INSERTING THEN
INSERT INTO EVENTLOGS(Eventlog_id, User_name, Date_done, Action_done)
VALUES (V_LOGID, V_USER, V_DATE, 'INSERT');
ELSIF DELETING THEN
INSERT INTO EVENTLOGS(Eventlog_id, User_name, Date_done, Action_done)
VALUES (V_LOGID, V_USER, V_DATE, 'DELETE');
ELSIF UPDATING THEN
INSERT INTO EVENTLOGS(Eventlog_id, User_name, Date_done, Action_done)
VALUES (V_LOGID, V_USER, V_DATE, 'UPDATE');
END IF;
END;
/
A DML Trigger is associated (tied to) with only ONE table. It executes when DML is submitted against that table only.
See http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS99888

error while executing insert triggers

DROP TRIGGER EPI_BOREHOLE_INI;
CREATE OR REPLACE TRIGGER EPI_BOREHOLE_INI
INSTEAD of Insert ON EPI_BOREHOLE for each row
DECLARE
V_ID number(10);
V_USER varchar2(100);
BEGIN
if (:new.UBHI is null or :new.NAME is null or) then
Raise_Application_Error(-20101, 'Insert failed. The key values of BOREHOLE(UBHI, NAME) cannot be null');
end if;
begin
select BOREHOLE_ID.nextval into V_ID from dual;
SELECT USER INTO V_USER FROM DUAL;
INSERT INTO EPI_BOREHOLE (ID, UBHI, NAME, INSERT_DATE ,INSERT_NAME, UPDATE_DATE,UPDATE_NAME) VALUES (V_ID,:NEW.UBHI, :NEW.NAME, SYSDATE, V_USER, SYSDATE, V_USER);
end;
END;
I see little error in your code: Please see below:
if (:new.UBHI is null or :new.NAME is null or) then <-- An additional OR written.
remove it and try or put one more condition.
I dont see any issue with the code.
Try doing
SET DEFINE OFF

Insertion using triggers by passing values

Lets say I have a table as follows--
create table employees
(
eno number(4) not null primary key,
ename varchar2(30),
zip number(5) references zipcodes,
hdate date
);
And I'm trying to create a trigger with--
CREATE OR REPLACE TRIGGER TWELVE_ONE
BEFORE INSERT OR UPDATE
ON EMPLOYEES
FOR EACH ROW
DECLARE
V_DATE VARCHAR2 (10);
BEGIN
SELECT TO_CHAR (SYSDATE, 'hh24:mi:ss') INTO V_DATE FROM DUAL;
IF (V_DATE >= '12:00:01' AND V_DATE < '13:00:00')
THEN
INSERT INTO TABLE ?????
ELSE
ROLLBACK? TERMINATE TRANSACTION?
END IF;
END;
Purpose of the trigger is to allow an insertion/update during 12:00-13:00 and prevent the insertion at any other time. The trigger construction (thanks to #Melkikun) is seems ok. However now I'm facing the following issues--
How is it possible to pass the values here? I mean lets say my create statement is:
Insert into employees Values (1, 'someone', 11111, '17-12-2015')
And lets say the time is 12:30:01 now. How would the trigger perform the insertion without knowing the values?
And lets say the time is now 13:00:1 now. How would the trigger stop/prevent the insertion?
I'm using Oracle SQL Developer 4.02.15
Many Thanks
You just have to do it the other way.
If the time is not correct,then you raise an exception, so the insert won't be done.
CREATE OR REPLACE TRIGGER TWELVE_ONE
BEFORE INSERT OR UPDATE
ON EMPLOYEES
FOR EACH ROW
DECLARE
V_DATE VARCHAR2 (10);
MyException exception;
BEGIN
SELECT TO_CHAR (SYSDATE, 'hh24:mi:ss') INTO V_DATE FROM DUAL;
IF (V_DATE < '12:00:01' OR V_DATE > '13:00:00')
THEN
raise MyException;
END IF;
EXCEPTION
When MyException then
ROLLBACK;
//output message ...
END;
How would the trigger perform the insertion without knowing the values?
The trigger knows the value thanks to :NEW and :OLD.
You normally use the terms in a trigger using :old to reference the old value and :new to reference the new value.So you will have :NEW.eno ,:NEW.ename ...
Here is an example from the Oracle documentation :
CREATE OR REPLACE TRIGGER Print_salary_changes
BEFORE DELETE OR INSERT OR UPDATE ON Emp_tab
FOR EACH ROW
WHEN (new.Empno > 0)
DECLARE
sal_diff number;
BEGIN
sal_diff := :new.sal - :old.sal;
dbms_output.put('Old salary: ' || :old.sal);
dbms_output.put(' New salary: ' || :new.sal);
dbms_output.put_line(' Difference ' || sal_diff);
END;

PL/SQL Trigger gets a mutating table error

My trigger wants to check if a 'new' manager supervises no more than 5 employees.
Manager supervising only 5 people are in BLOCKED_MANAGER table(ssn,numberofemployees).
Finally, every update is recorded in SUPERLOG table(date,user,old_manager,new_manager).
I get no compiling error about the trigger, but when I update a superssn I get this error:
SQL> update employee set superssn='666666607' where ssn='111111100';
update employee set superssn='666666607' where ssn='111111100'
*
ERROR at line 1:
ORA-04091: Table FRANK.EMPLOYEE is mutating, the trigger/function
can't read it
ORA-06512: a "FRANK.TLOG", line 20
ORA-04088: error during execution of trigger 'FRANK.TLOG'
How can I solve this trigger? Thank you
create or replace trigger tlog
before update of superssn on employee
for each row
declare
t1 exception;
n number:=0;
cont number:=0;
empl varchar2(16);
cursor cur is (select ssn from blocked_manager where ssn is not null);
begin
open cur;
loop
fetch cur into empl;
exit when cur%notfound;
if(:new.superssn = empl) then
n:=1;
end if;
end loop;
close cur;
if n=1 then
raise t1;
end if;
select count(*) into cont from employee group by superssn having superssn=:new.superssn;
if(cont=4) then
insert into blocked_manager values(:new.superssn,5);
end if;
insert into superlog values(sysdate,user,:old.superssn, :new.superssn );
exception
when t1 then
raise_application_error(-20003,'Manager '||:new.superssn||' has already 5 employees');
end;
Probably the quickest way around this is to use a carefully constructed statement trigger instead of a row trigger. Row triggers have the phrase FOR EACH ROW in them, are invoked for each row which is modified (based on the BEFORE/AFTER INSERT, BEFORE/AFTER UPDATE, and BEFORE/AFTER DELETE constraints on the trigger), can see the appropriate :NEW and :OLD values, and are subject to the "can't look at the table on which the trigger is defined" rule. Statement triggers are invoked at the appropriate time for each statement which is executed, can't see row values, but aren't subject to the limits on looking at the particular table on which they're defined. So for the portions of your logic which don't need to work with :NEW or :OLD values a trigger such as this might prove useful:
CREATE OR REPLACE TRIGGER EMPLOYEE_S_BU
BEFORE UPDATE ON EMPLOYEE
-- Note: no BEFORE EACH ROW phrase, so this is a statement trigger
BEGIN
-- The following FOR loop should insert rows into BLOCKED_MANAGER for all
-- supervisors which have four or more employees under them and who are not
-- already in BLOCKED_MANAGER.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
LEFT OUTER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
WHERE b.SSN IS NULL
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) >= 4)
LOOP
INSERT INTO BLOCKED_MANAGER
(SSN, EMPLOYEE_COUNT)
VALUES
(aRow.SUPERSSN, aRow.EMP_COUNT);
END LOOP;
-- Remove rows from BLOCKED_MANAGER for managers who supervise fewer
-- than four employees.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
INNER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) <= 3)
LOOP
DELETE FROM BLOCKED_MANAGER
WHERE SSN = aRow.SUPERSSN;
END LOOP;
-- Finally, if any supervisor has five or more employees under them,
-- raise an exception. Note that we go directly to EMPLOYEE to determine
-- the number of employees supervised.
FOR aRow IN (SELECT SUPERSSN, COUNT(*) AS EMP_COUNT
FROM EMPLOYEE
GROUP BY SUPERSSN
HAVING COUNT(*) >= 5)
LOOP
-- If we get here we've found a supervisor with 5 (or more) employees.
-- Raise an exception
RAISE_APPLICATION_ERROR(-20000, 'Found supervisor ' || aRow.SUPERSSN ||
' supervising ' || aRow.EMP_COUNT ||
' employees');
END LOOP;
END EMPLOYEE_S_BU;
Note that if you get rid of the BLOCKED_MANAGER table (which this trigger still maintains, although I don't know if it's truly necessary) the logic gets cut down considerably.
You'll still need a row trigger to handle the logging, but as that's just a matter of cutting down your existing trigger I'll leave that to you. :-)
Share and enjoy.
As you have discovered, you cannot select from the same table that a row-level trigger is defined against; it causes a table mutating exception.
In order to properly create this validation using a trigger a procedure should be created to obtain user-specified locks so the validation can be correctly serialized in a multi-user environment.
PROCEDURE request_lock
(p_lockname IN VARCHAR2
,p_lockmode IN INTEGER DEFAULT dbms_lock.x_mode
,p_timeout IN INTEGER DEFAULT 60
,p_release_on_commit IN BOOLEAN DEFAULT TRUE
,p_expiration_secs IN INTEGER DEFAULT 600)
IS
-- dbms_lock.allocate_unique issues implicit commit, so place in its own
-- transaction so it does not affect the caller
PRAGMA AUTONOMOUS_TRANSACTION;
l_lockhandle VARCHAR2(128);
l_return NUMBER;
BEGIN
dbms_lock.allocate_unique
(lockname => p_lockname
,lockhandle => p_lockhandle
,expiration_secs => p_expiration_secs);
l_return := dbms_lock.request
(lockhandle => l_lockhandle
,lockmode => p_lockmode
,timeout => p_timeout
,release_on_commit => p_release_on_commit);
IF (l_return not in (0,4)) THEN
raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return);
END IF;
-- Must COMMIT an autonomous transaction
COMMIT;
END request_lock;
This procedure can then be used in a compound trigger (assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions)
CREATE OR REPLACE TRIGGER too_many_employees
FOR INSERT OR UPDATE ON employee
COMPOUND TRIGGER
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal employee supervisor table
g_superssns := sys.odcivarchar2list();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the inserted/updated supervisors of employees
IF ( ( INSERTING
AND :new.superssn IS NOT NULL)
OR ( UPDATING
AND ( :new.superssn <> :old.superssn
OR :new.superssn IS NOT NULL AND :old.superssn IS NULL) ) )
THEN
g_superssns.EXTEND;
g_superssns(g_superssns.LAST) := :new.superssn;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(g_superssns) sup
ORDER BY sup.column_value;
CURSOR csr_constraint_violations
(p_superssn employee.superssn%TYPE)
IS
SELECT count(*) employees
FROM employees
WHERE pch.superssn = p_superssn
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated employee there exists more than
-- 5 employees for the same supervisor. Serialise the constraint for each
-- superssn so concurrent transactions do not affect each other
FOR r_supervisor IN csr_supervisors LOOP
request_lock('TOO_MANY_EMPLOYEES_' || r_supervisor.superssn);
OPEN csr_constraint_violations(r_supervisor.superssn);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Supervisor ' || r_supervisor.superssn || ' now has ' || r_constraint_violation.employees || ' employees');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
You do not need the blocked_manager table to manage this constraint. This information can be derived from the employee table.
Or in versions earlier than Oracle 11i:
CREATE OR REPLACE PACKAGE employees_trg
AS
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
END employees_trg;
CREATE OR REPLACE TRIGGER employee_biu
BEFORE INSERT OR UPDATE ON employee
IS
BEGIN
-- Reset the internal employee supervisor table
employees_trg.g_superssns := sys.odcivarchar2list();
END;
CREATE OR REPLACE TRIGGER employee_aiur
AFTER INSERT OR UPDATE ON employee
FOR EACH ROW
IS
BEGIN
-- Store the inserted/updated supervisors of employees
IF ( ( INSERTING
AND :new.superssn IS NOT NULL)
OR ( UPDATING
AND ( :new.superssn <> :old.superssn
OR :new.superssn IS NOT NULL AND :old.superssn IS NULL) ) )
THEN
employees_trg.g_superssns.EXTEND;
employees_trg.g_superssns(employees_trg.g_superssns.LAST) := :new.superssn;
END IF;
END;
CREATE OR REPLACE TRIGGER employee_aiu
AFTER INSERT OR UPDATE ON employee
IS
DECLARE
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(employees_trg.g_superssns) sup
ORDER BY sup.column_value;
CURSOR csr_constraint_violations
(p_superssn employee.superssn%TYPE)
IS
SELECT count(*) employees
FROM employees
WHERE pch.superssn = p_superssn
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated employee there exists more than
-- 5 employees for the same supervisor. Serialise the constraint for each
-- superssn so concurrent transactions do not affect each other
FOR r_supervisor IN csr_supervisors LOOP
request_lock('TOO_MANY_EMPLOYEES_' || r_supervisor.superssn);
OPEN csr_constraint_violations(r_supervisor.superssn);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Supervisor ' || r_supervisor.superssn || ' now has ' || r_constraint_violation.employees || ' employees');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END;

How to insert into a table correctly using table of records and forall in pl/sql

I want to insert records into MY_TABLE using forall. But the no. of records dat gets inserted keeps on changing with each test run! I think it has something to do with loop counter but I am not able to figure out. Here's the code snippet.
DECLARE
TYPE l_rec_type IS RECORD (
datakey SOURCE_TABLE.datakey%TYPE,
sourcekey SOURCE_TABLE.sourcekey%TYPE,
DESCRIPTION SOURCE_TABLE.DESCRIPTION%TYPE,
dimension_name SOURCE_TABLE.dimension_name%TYPE ,
data_type SOURCE_TABLE.data_type%TYPE
);
TYPE l_table_type IS TABLE OF l_rec_typeINDEX BY PLS_INTEGER;
l_table l_table_type;
l_cntr NUMBER;
BEGIN
FOR rec_dimname IN (SELECT dimension_name FROM dimension_table) LOOP
l_cntr1 := 1
FOR rec_source IN (SELECT * FROM source_table WHERE data_type IS NOT NULL) LOOP
l_table(l_ctr1).datakey := rec_source.datakey;
l_table(l_ctr1).sourcekey := rec_source.sourcekey;
l_table(l_ctr1).DESCRIPTION := rec_source.DESCRIPTION;
l_table(l_ctr1).dimension_name := rec_source.dimension_name;
l_table(l_ctr1).data_type := rec_source.data_type;
l_cntr1 := l_cntr1+1;
END LOOP
FORALL j IN l_table.FIRST..l_table.LAST
INSERT INTO my_table VALUES(l_table(j).datakey,
l_table(j).sourcekey,
l_table(j).DESCRIPTION,
l_table(j).dimension_name,
l_table(j).data_type,
1,
SYSDATE,
login_id
);
END LOOP;
END;
What am I doing wrong? Normal insert using for loop is inserting 5000 records. Another problem that I am facing is how to handle WHEN DUP_VAL_ON_INDEX and WHEN OTHERS exception using forall. In nornal for loop its easy. But I have to use FORALL for fast inserts. Please help!
Looking at your code I can see that you not delete the data stored in the pl/table inside your loop and you don't have a order by to your query's. So if the first iteration have more data then the second you will have duplicate data.
So after initializing your l_cntr1 var (l_cntr1 := 1) you must clear your pl/table:
l_table.delete;
Hope that helps.
Here's the fixed code. Plus SAVE EXCEPTIONS really saved my day!. Here is how I implemented the solution. Thank you all for your valuable time and suggestions.
DECLARE
TYPE l_rec_type IS RECORD (
datakey SOURCE_TABLE.datakey%TYPE,
sourcekey SOURCE_TABLE.sourcekey%TYPE,
DESCRIPTION SOURCE_TABLE.DESCRIPTION%TYPE,
dimension_name SOURCE_TABLE.dimension_name%TYPE ,
data_type SOURCE_TABLE.data_type%TYPE
);
TYPE l_table_type IS TABLE OF l_rec_typeINDEX BY PLS_INTEGER;
l_table l_table_type;
l_cntr NUMBER;
ex_dml_errors EXCEPTION;
PRAGMA EXCEPTION_INIT(ex_dml_errors, -24381);
login_id NUMBER := -1;
errm VARCHAR2(512);
err_indx NUMBER
BEGIN
FOR rec_dimname IN (SELECT dimension_name FROM dimension_table) LOOP
l_cntr1 := 1;
l_table.DELETE; -- Added
FOR rec_source IN (SELECT * FROM source_table WHERE data_type IS NOT NULL) LOOP
l_table(l_ctr1).datakey := rec_source.datakey;
l_table(l_ctr1).sourcekey := rec_source.sourcekey;
l_table(l_ctr1).DESCRIPTION := rec_source.DESCRIPTION;
l_table(l_ctr1).dimension_name := rec_source.dimension_name;
l_table(l_ctr1).data_type := rec_source.data_type;
l_cntr1 := l_cntr1+1;
END LOOP
FORALL j IN l_table.FIRST..l_table.LAST SAVE EXCEPTIONS
INSERT INTO my_table VALUES(l_table(j).datakey,
l_table(j).sourcekey,
l_table(j).DESCRIPTION,
l_table(j).dimension_name,
l_table(j).data_type,
1,
SYSDATE,
login_id
);
END LOOP;
END LOOP;
EXCEPTION
WHEN ex_dml_errors THEN
l_error_count := SQL%BULK_EXCEPTIONS.count;
DBMS_OUTPUT.put_line('Number of failures: ' || l_error_count);
errm := SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE);
err_indx := SQL%BULK_EXCEPTIONS(i).error_index
FOR i IN 1 .. l_error_count LOOP
DBMS_OUTPUT.put_line('Error: ' || i ||
' Array Index: ' || SQL%BULK_EXCEPTIONS(i).error_index ||
' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
IF errm LIKE '%unique%constraint%violated' THEN -- Insert into my_multiple_entries_tbl on duplicate value on index DATAKEY
INSERT INTO my_multiple_entries_tbl(my_multiple_entries_tbl_seq.NEXTVAL,
l_table(err_indx).datakey,
l_table(err_indx).sourcekey,
l_table(err_indx).data_type,
SYSDATE,
login_id );
ELSE -- Insert into my_other_errors_tbl on other errors
INSERT INTO my_other_errors_tbl ( my_other_errors_tbl_seq.NEXTVAL,
l_table(err_indx).datakey,
l_table(err_indx).sourcekey,
l_table(err_indx).data_type,
SYSDATE,
login_id );
END IF;
END;
Your seem to be inserting exactly the same thing multiple times - you're solely looping through the count of dimension_table, which means it can be simplified to the following, which will be faster. At the bottom is a forall version.
You can't use exception when dup_val_on_index with either version, you have to do it row by row. Judging solely by what you've posted I suspect that you can actually achieve what you're trying to do in a single query and save all this problem completely ( including dealing with duplicate values ).
declare
i integer;
begin
select count(*)
into i
from dimension_table;
for j in 1 .. i loop
insert into my_table (datakey, sourcekey, description
, dimension_name, someother_column
, some_date_column, login_id
select datakey, sourcekey, description, dimension_name
, data_type, 1, sysdate, login_id -- previously missing
from source_table
where data_type is not null;
end loop;
commit;
end;
/
If, however, you really want to use forall you can do something like this:
declare
cursor c_src is
select datakey, sourcekey, description, dimension_name
, data_type, 1, sysdate, login_id -- previously missing
from source_table
where data_type is not null;
type t__src is table of c_src%rowtype index by binary_integer;
t_src t__src;
i integer;
begin
select count(*)
into i
from dimension_table;
for j in 1 .. i loop
open c_src;
loop
fetch c_src bulk collect into t_src;
forall k in t_src.first .. t_src.last
insert into my_table (datakey, sourcekey, description
, dimension_name, someother_column
, some_date_column, login_id
values t_src;
end loop;
close c_src;
end loop;
commit;
end;
/

Resources