I'm currently studying PL/SQL and was doing experiment by declaring a private PL/SQL in the package index-by table called jobs_tab_type that should be indexed by a string type based on the jobs.job_id%type.
Now I really don't want to put it in a package right away and just test it first so first I created it in an anonymous block.
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
BEGIN
for rec in(select job_id from jobs)
LOOP
jobstab(rec.job_id);
END LOOP;
END;
I compiled it and ran into this error 'PLS-00221: 'JOBSTAB' is not a procedure or is undefined .
I don't know what i did wrong based on the documentations i'm currently reading online. But I don't get what is wrong, i didn't even declared a constraint and just referenced it with the data type that is on the database.
Update:
This is the description of my jobs table:
I changed the following code to:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
BEGIN
for rec in(select job_id from jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
END;
Related
I'm trying to figure out why my table keeps mutating even though I have already created another package to maintain in memory a copy of the rows in the jobs table.
This is my emp_pkg that handles all the code for the employee, i will only be adding the procedure that i'm using in this package so it will be short:
CREATE OR REPLACE PACKAGE BODY emp_pkg
IS
PROCEDURE SET_SALARY(pjobid jobs.job_id%type,
psal jobs.min_salary%type)
IS
BEGIN
FOR i IN (SELECT * FROM jobs)
LOOP
UPDATE employees
SET salary = psal
WHERE job_id = pjobid;
END LOOP;
END SET_SALARY;
END emp_pkg;
Then I created a trigger on the jobs table that invokes the set_salary procedure on my emp_pkg package when the minimum salary in the jobs table is updated for a specified job id:
CREATE OR REPLACE TRIGGER upd_minsalary_trg
AFTER UPDATE OF min_salary ON jobs
FOR EACH ROW
WHEN (new.min_salary != old.min_salary)
BEGIN
emp_pkg.set_salary(:old.job_id, :new.min_salary);
END upd_minsalary_trg;
/
Now, I tested it by running the code below with the following errors as expected:
UPDATE jobs
SET min_salary = min_salary + 1000
WHERE job_id = 'IT_PROG';
--ORA-04091: table SQL_WLALHQOYXPNRGAIYBLVJOQTEB.JOBS is mutating, trigger/function may not see it
Now like I said before, to fix this i have created another package to maintain in memory a copy of the rows in the jobs table:
CREATE OR REPLACE PACKAGE BODY jobs_pkg
IS
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
PROCEDURE initialize
IS
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
END;
FUNCTION get_minsalary(p_jobid VARCHAR2)
RETURN NUMBER
IS
vmin_salary jobs.min_salary%type;
BEGIN
SELECT min_salary
INTO vmin_salary
FROM jobs
WHERE job_id = jobstab(p_jobid).job_id;
RETURN vmin_salary;
END get_minsalary;
FUNCTION get_maxsalary(p_jobid VARCHAR2)
RETURN NUMBER
IS vmax_salary jobs.max_salary%type;
BEGIN
SELECT max_salary
INTO vmax_salary
FROM jobs
WHERE job_id = jobstab(p_jobid).job_id;
RETURN vmax_salary;
END get_maxsalary;
PROCEDURE set_minsalary(p_jobid VARCHAR2, pmin_salary NUMBER)
IS
BEGIN
UPDATE jobs
SET min_salary = pmin_salary
WHERE job_id = jobstab(p_jobid).job_id;
END set_minsalary;
PROCEDURE set_maxsalary(p_jobid VARCHAR2, pmax_salary NUMBER)
IS
BEGIN
UPDATE jobs
SET max_salary = pmax_salary
WHERE job_id = jobstab(p_jobid).job_id;
END set_maxsalary;
END jobs_pkg;
Then I implemented another trigger for before inserting or updating on the jobs table that invoke my jobs_pkg.initialize procedure on the jobs package to ensure that the package state is current before the DML operations are performed:
CREATE OR REPLACE TRIGGER init_jobpkg_trg
BEFORE UPDATE OR INSERT ON jobs
FOR EACH ROW
BEGIN
jobs_pkg.initialize;
END init_jobpkg_trg;
Now the problem is when I tried to run the code below it still mutates:
UPDATE jobs
SET min_salary = min_salary + 1000
WHERE job_id = 'IT_PROG';
I keep searching on why it still mutates but can't seem to find the problem.
Any help will be appreciated.
Triggers are a very powerful tool, but there are a couple of limitations, one of which is that you can't select from the table the trigger is on (the dreaded ORA-04091). This limitation makes total sense. There are plenty of articles on google explaining why this error occurs.
There are 2 ways to solve it.
Option 1: avoid the SELECT ... FROM jobs in the row level trigger
CREATE OR REPLACE TRIGGER JOBS_BIU
AFTER UPDATE OF min_salary ON jobs
FOR EACH ROW
WHEN (new.min_salary != old.min_salary)
BEGIN
UPDATE employees SET salary = :new.min_salary WHERE job_id = :new.job_id;
END;
/
Open 2: Do not use a row level trigger but a statement level trigger. This will fire after the statement has completed:
CREATE OR REPLACE TRIGGER JOBS_BIU
AFTER UPDATE OF min_salary ON jobs
BEGIN
FOR r IN (SELECT * FROM jobs) LOOP
UPDATE employees
SET salary = r.min_salary
WHERE job_id = r.job_id;
END LOOP;
END;
/
Thanks to Koen, I got an idea on how to fix my problem so here it is:
First I have updated my jobs package and eliminated all SELECT statement that was being used:
This is my code before:
CREATE OR REPLACE PACKAGE BODY jobs_pkg
IS
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
PROCEDURE initialize
IS
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
END;
FUNCTION get_minsalary(p_jobid VARCHAR2)
RETURN NUMBER
IS
vmin_salary jobs.min_salary%type;
BEGIN
SELECT min_salary
INTO vmin_salary
FROM jobs
WHERE job_id = jobstab(p_jobid).job_id;
RETURN vmin_salary;
END get_minsalary;
FUNCTION get_maxsalary(p_jobid VARCHAR2)
RETURN NUMBER
IS vmax_salary jobs.max_salary%type;
BEGIN
SELECT max_salary
INTO vmax_salary
FROM jobs
WHERE job_id = jobstab(p_jobid).job_id;
RETURN vmax_salary;
END get_maxsalary;
PROCEDURE set_minsalary(p_jobid VARCHAR2, pmin_salary NUMBER)
IS
BEGIN
UPDATE jobs
SET min_salary = pmin_salary
WHERE job_id = jobstab(p_jobid).job_id;
END set_minsalary;
PROCEDURE set_maxsalary(p_jobid VARCHAR2, pmax_salary NUMBER)
IS
BEGIN
UPDATE jobs
SET max_salary = pmax_salary
WHERE job_id = jobstab(p_jobid).job_id;
END set_maxsalary;
END jobs_pkg;
c. Copy the CHECK_SALARY procedure from Practice 9, Exercise 1a, and modify the code by replacing the query on the JOBS table with statements to set the local minsal and maxsal variables with values from the JOBS_PKG data by calling the appropriate GET_*SALARY functions. This step should eliminate the mutating trigger exception.
CREATE OR REPLACE PROCEDURE check_salary (pjobid employees.job_id%type, psal employees.salary%type)
IS
vminsalary jobs.min_salary%type;
vmaxsalary jobs.max_salary%type;
BEGIN
vminsalary := jobs_pkg.get_minsalary(pjobid);
vmaxsalary := jobs_pkg.get_maxsalary(pjobid);
END check_salary;
and this is my code now:
CREATE OR REPLACE PACKAGE BODY jobs_pkg
IS
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
PROCEDURE initialize
IS
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
END;
FUNCTION get_minsalary(p_jobid VARCHAR2)
RETURN NUMBER
IS
BEGIN
RETURN jobstab(p_jobid).min_salary;
END get_minsalary;
FUNCTION get_maxsalary(p_jobid VARCHAR2)
RETURN NUMBER
IS
BEGIN
RETURN jobstab(p_jobid).max_salary;
END get_maxsalary;
PROCEDURE set_minsalary(p_jobid VARCHAR2, pmin_salary NUMBER)
IS
BEGIN
jobstab(p_jobid).min_salary := pmin_salary;
END set_minsalary;
PROCEDURE set_maxsalary(p_jobid VARCHAR2, pmax_salary NUMBER)
IS
BEGIN
jobstab(p_jobid).max_salary := pmax_salary;
END set_maxsalary;
END jobs_pkg;
Then I removed the for each row from my initializer procedure that is found on my jobs package:
From this:
CREATE OR REPLACE TRIGGER init_jobpkg_trg
BEFORE UPDATE OR INSERT ON jobs
FOR EACH ROW
BEGIN
jobs_pkg.initialize;
END init_jobpkg_trg;
To this:
CREATE OR REPLACE TRIGGER init_jobpkg_trg
BEFORE INSERT OR UPDATE ON jobs
BEGIN
DBMS_OUTPUT.PUT_LINE('-----Initializing-----');
jobs_pkg.initialize;
END init_jobpkg_trg;
After testing of updaing the min_salary of the jobs table, it all now works :)
I have this structure of table in my database
Now i want all the values inside of it in a associative array that is indexed by the job_id%type which is a varchar2, so i created this anonymous block first before creating a procedure to test it and i want to populate my associative array with the results:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec.job_id;
END LOOP;
END;
I know for sure that this is a wrong way to do it since I've encountered this error: PLS-00382: expression is of wrong type , but at the same time i don't know if there is any proper way to do this. I have seen the documentation but all the examples were to use a user defined string that will hold the job_id, but the problem is that i need the job_id to be indexed and not a user defined string. Is there any way to fix this problem in PL/SQL?
You don't need the index by INDEX BY jobs.job_id%type. It is making your code more complicated. The INDEX BY determines the index of the collection - that is the character used to indicate the position in the collection. That doesn't need to be a column from the table you are taking a %rowtype from.
Typically an INDEX BY VARCHAR2 is used if you want to reference the collection based on a meaningful string, like capitals('United States') = 'Washingston'
Now, about your question. Here is the answer to the original question:
create table jobs
(job_id VARCHAR2(10)
,job_title VARCHAR2(30)
,min_salary NUMBER
,max_salary NUMBER
);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('a','CLERK',100,500);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('b','PRESIDENT',1000,5000);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('c','SALESMAN',500,800);
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
l_idx VARCHAR2(100);
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
l_idx := jobstab.FIRST;
WHILE (l_idx IS NOT NULL) LOOP
dbms_output.put_line(l_idx ||': '||jobstab(l_idx).job_title);
l_idx := jobstab.NEXT(l_idx);
END LOOP;
END;
/
a: CLERK
b: PRESIDENT
c: SALESMAN
Notice that in this block, it's a bit tedious to loop through the elements, just because the index is not an integer. Now lets look at the same functionality, but using an INDEX BY BINARY_INTEGER:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY BINARY_INTEGER;
jobstab jobs_tab_type;
l_idx NUMBER := 1;
BEGIN
-- example 1: using a cursor for loop
-- FOR rec IN (SELECT * FROM jobs)
-- LOOP
-- jobstab(l_idx) := rec;
-- l_idx := l_idx + 1;
-- END LOOP;
-- example 2: using bulk collect
SELECT * BULK COLLECT INTO jobstab FROM jobs;
FOR r IN 1 .. jobstab.COUNT LOOP
dbms_output.put_line(r ||': '||jobstab(r).job_title);
END LOOP;
END;
/
1: CLERK
2: PRESIDENT
3: SALESMAN
It's simpler to loop through the values of a collection because of the numeric index and it also can be implicitly assigned as well, using the BULK COLLECT statement.
The value of your associative array is the entire record. Hence just remove .job_id from this line of your code.
jobstab(rec.job_id) := rec.job_id;
In other words, the line should be
jobstab(rec.job_id) := rec;
Refer to this db<>fiddle
here is my PLSQL code:
declare
headerStr varchar2(1000):='C1~C2~C3~C5~C6~C7~C8~C9~C10~C11~C12~C16~C17~C18~C19~RN';
mainValStr varchar2(32000):='1327~C010802~9958756666~05:06AM~NO~DISPOSAL~NDL~4~P32~HELLO~U~28-OCT-2017~28-OCT-2017~Reject~C010741~1;1328~C010802~9958756666~06:07AM~MH~DROP~NDL~1~P32~~U~28-OCT-2017~28-OCT-2017~Reject~C010741~2;1329~C010802~9999600785~01:08AM~BV~DROP~NDL~2~P32~MDFG~U~28-OCT-2017~28-OCT-2017~Reject~C010741~3';
valStr varchar2(4000);
headerCur sys_refcursor;
mainValCur sys_refcursor;
valCur sys_refcursor;
header varchar2(1000);
val varchar2(1000);
iterator number:=1000;
strIdx number;
strLen number;
idx number;
TYPE T_APPROVAL_RECORD IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(1000);
headerTable T_APPROVAL_RECORD;
cnt number;
begin
open headerCur for select * from table(split_str(headerStr,'~'));
open mainValCur for select * from table(split_str(mainValStr,';'));
loop
fetch mainValCur into valStr;
exit when mainValCur%notfound;
insert into header_test values(cnt, valStr); -- for testing purpose
open valCur for select * from table(split_str(valStr,'~'));
loop
fetch valCur into val;
fetch headerCur into header;
exit when valCur%notfound;
exit when headerCur%notfound;
insert into header_test values(header, val);
headerTable(header):= val;
end loop;
idx := headerTable.FIRST; -- Get first element of array
WHILE idx IS NOT NULL LOOP
insert into header_test values (idx, headerTable(idx));
idx := headerTable.NEXT(idx); -- Get next element of array
END LOOP;
headerTable.delete;
end loop;
commit;
end;
c1 c2 ..... c19 are column name and RN is rownumber,
data for the columns of each will be in mainValString seperated by ;
Why i am getting ORA-14551 when i am trying to access collection "headerTable"?
Please help.
Problem is with this line.
idx := headerTable.FIRST;
The index of headertable is of TYPE VARCHAR2 whereas idx is defined as NUMBER.
declare idx as VARCHAR2(1000), it should work.
Having said that, ORA-14551 - Cannot perform DML ... is not related to this error. It is unclear to me why should you encounter this error.
Oh but it does:
EXCEPTION WHEN OTHERS THEN
v_msg:=sqlcode||sqlerrm;
insert into err_data_transfer values('SPLIT_STR',v_msg,sysdate,null);
It may only be during an exception, but it's still DML during a select statement. You may be able to create another procedure as an AUTONOMOUS_TRANSACTION to create the error log. Also, you should either re-raise or raise_application_error afterward. If not your procedure will continue as though the error did not occur; which leads to more problems as to why your main process does not work (including running to completion but doing the wrong thing).
I have a set of product that I want to tag to a certain value so what I did this:
declare
type array_produit_auto is varray(3) of varchar(50);
array array_produit_auto := array_produit_auto('code_product1', 'code_product2', 'code_product3');
begin
for i in 1..array.count loop
update F_PRODUITASS pas
set PAS_NONGES_IDA = 0
WHERE PAS.PAS_CODE_PRODUIT = array(i;
end loop;
end;
commit;
however, the list of these products is too long. Instead I'd like to do this:
declare
type array_produit_auto is varray(3) of varchar(50);
array array_produit_auto := array_produit_auto('code_product4', 'code_product5', 'code_product6');
begin
update F_PRODUITASS pas
set PAS_NONGES_IDA = 1
WHERE PAS.PAS_CODE_PRODUIT NOT IN array;
end;
commit;
except this doesn't work since apparently I can't mix a query and an array this way.
Any idea of how I could make this work?
If you used a nested table then you could query from the nested table, something like this:
DECLARE
v_exclude_list t_array_produit_auto :=
t_array_produit_auto('code_product4', 'code_product5', 'code_product6');
BEGIN
UPDATE F_PRODUITASS pas
SET PAS_NONGES_IDA = 1
WHERE PAS.PAS_CODE_PRODUIT NOT IN ( SELECT *
FROM TABLE(v_exclude_list) )
;
END;
/
Also, you meant varchar2, right?
Update regarding the Opaque error: The type declaration would need to be an object type (create with the CREATE OR REPLACE TYPE syntax rather than a local plsql type as in the DDL below.
CREATE TABLE F_PRODUITASS(PAS_NONGES_IDA number, PAS_CODE_PRODUIT VARCHAR2(50));
INSERT INTO F_PRODUITASS VALUES(3, 'code_product3');
INSERT INTO F_PRODUITASS VALUES(4, 'code_product4');
CREATE OR REPLACE TYPE t_array_produit_auto IS TABLE OF VARCHAR2(50);
If you did not wish to create your own object type, you could use pre-existing varchar2 or number types such as sys.odcivarchar2list as described here:
Anonymous TABLE or VARRAY type in Oracle
am creating a package in pl/sql . with in this i declared the ref cursor . With in procedure am using select statement with multiple column name . but am not able to get the result.
here i attached my code. Help me to correct the error. Am new to pl/sql
code
CREATE OR REPLACE PACKAGE types AS
TYPE cursor_type IS REF CURSOR;
END Types;
/
CREATE OR REPLACE
PROCEDURE get_CDR_rs (p_no IN zkv.FLD_callingPartyNumber%TYPE,
CDR_recordset OUT SYS_REFCURSOR) AS
BEGIN
OPEN CDR_recordset FOR
SELECT FLD_callingPartyNumber,
FLD_dateTimeConnect
FROM CISCOCUIC_TBL
WHERE FLD_callingPartyNumber= p_no
ORDER BY FLD_callingPartyNumber,;
END get_CDR_rs;
/
SET SERVEROUTPUT ON SIZE 1000000
DECLARE
l_cursor SYS_REFCURSOR;
l_callingPartyNumber zkv.FLD_callingPartyNumber%TYPE;
l_dateTimeConnect zkv.FLD_dateTimeConnect%TYPE;
BEGIN
LOOP
FETCH l_cursor
INTO l_callingPartyNumber, l_dateTimeConnect;
EXIT WHEN l_cursor%NOTFOUND;
END LOOP;
CLOSE l_cursor;
END;
/
Error
9/41 PL/SQL: ORA-00936: missing expression
5/5 PL/SQL: SQL Statement ignored
First thing is there is a syntax error in the procedure. It should be
CREATE OR REPLACE
PROCEDURE get_CDR_rs (p_no IN zkv.FLD_callingPartyNumber%TYPE,
CDR_recordset OUT SYS_REFCURSOR) AS
BEGIN
OPEN CDR_recordset FOR
SELECT FLD_callingPartyNumber,
FLD_dateTimeConnect
FROM CISCOCUIC_TBL
WHERE FLD_callingPartyNumber= p_no
ORDER BY FLD_callingPartyNumber; -- there was a comma which is not required or you
-- missed a column
END get_CDR_rs;
/
Secondly where is get_CDR_rs being called to retrieve the results?
Thirdly why do you need the following? because you are using sys_refcursor
CREATE OR REPLACE PACKAGE types AS
TYPE cursor_type IS REF CURSOR;
END Types;
/
If you would like to see the results of your procedure which returns sys_refcursor, do as follows
variable rset refcursor;
DECLARE
p_no zkv.FLD_callingPartyNumber%TYPE;
BEGIN
p_no := '123';
get_CDR_rs (p_no, :rset);
END;
/
print rset