How can I handle this compilation error through exception? - sql

How can I handle this compilation error through exception?
declare
table_or_view_does_not_exist exception;
pragma exception_init(table_or_view_does_not_exist,-00942);
b exception;
pragma exception_init(b,-00942);
d_table varchar2(200);
c_table varchar2(200);
c_count Number;
begin
begin
d_table:='drop table audit_table PURGE';
execute immediate d_table;
exception
when table_or_view_does_not_exist then
null;
end;
<<lable>>
c_table := 'create table audit_table
(table_name varchar2(50),
column_name varchar2(50),
count_type varchar2(50),
v_count number)';
execute immediate c_table;
select count(*) into c_count from customer_profile where cust_id is null;
insert into audit_table columns (table_name,column_name,count_type,v_count) values('customer_profile','cust_id','null','c_count');
exception
when b then
GOTO lable;
end;
Error report:
ORA-06550: line 25, column 13:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 25, column 1:
PL/SQL: SQL Statement ignored
ORA-06550: line 28, column 2:
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'LABLE'
ORA-06550: line 28, column 2:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:

What you do, is just bad practice. In Oracle, we don't create tables in PL/SQL but at SQL level and then use them in our procedures.
In your case, you'd
-- create table first
create table audit_table ...;
-- use it in PL/SQL procedure
declare
...
begin
...
insert into audit_table ...
end;
/
You can't "handle" compilation error through exception. What you could do is to put insert statement into dynamic SQL. Also, it wouldn't harm if you used valid syntax (there's no columns "keyword" there).
execute immediate q'[insert into audit_table
(table_name, column_name, count_type, v_count)
values('customer_profile', 'cust_id', 'null', :a)]'
using c_count;
but - once again - that's just bad practice. Don't do it that way, there's no benefit and many disadvantages.
As of goto - well, jumping around your code is almost always wrong. Error you got says that you can't jump out of the exception handler so your idea was wrong anyway.

You actually can trap compilation errors, by wrapping the whole block in an execute immediate so compilation is postponed until runtime. But that's not the right way to go.
Your real issue is that you are referring to the object you are dynamically dropping/creating as a hard-coded dependency of your PL/SQL block:
insert into audit_table ... -- this is a hard dependency
You can't compile PL/SQL if hard dependencies don't exist until run time. If you really must dynamically drop/recreate the table (and I agree with the above reviewer that this is architecturally questionable), you must also make this insert dynamic as well in order to break the dependency chain:
execute immediate 'insert into audit_table values (:col1, :col2, :col3, :col4)' using 'customer_profile','cust_id','null','c_count'; -- this creates no dependency
That will prevent Oracle from creating an object dependency on this table so your code can compile even though the table does not exist at compile time.
But once again, please be sure this is the right technique. There are only a few rare cases where this actually makes good architectural sense. Oh, and please remove the GOTO and label junk. We really don't use that in modern languages; they create unmanageable spaghetti code.

Could you consider another way to control the flow - comments in the code:
DECLARE
cmd VarChar2(200);
c_count Number(6);
--
Status VarChar2(255);
BEGIN
cmd :='drop table audit_table';
Begin -- 1st nested PL/SQL block
Execute Immediate cmd;
Status := 'OK';
Exception -- this could be raised if the table doesn't exist which is probably OK
When OTHERS Then
Status := 'OK'; -- SQLERRM = ORA-00942: table or view does not exist - nothing happens - it would be droped anyway
-- Status := 'ERR - ' || SQLERRM; -- this is alternative if you want to do something else with this ERR
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
GoTo EndIt;
End If;
--
<<lable>> -- in this code there is no need for this label
cmd := 'create table audit_table (table_name varchar2(50), column_name varchar2(50), count_type varchar2(50), v_count number)';
Begin -- 2nd nested PL/SQL block
Execute Immediate cmd;
Status := 'OK';
Exception
When OTHERS Then
Status := 'ERR - ' || SQLERRM;
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
dbms_output.putline(Status); -- send error message and exit - there is no audit_table
GoTo EndIt;
Else
Null; -- here you can do something else (if maybe the table already exists)
End If;
--
-- think about what could go wrong below (2 lines) and either leave it as it is or put it in the 3rd nested PL/SQL block
Select count(*) Into c_count From customer_profile Where cust_id Is Null;
insert into audit_table (table_name, column_name, count_type, v_count) values('customer_profile', 'cust_id', 'Null', c_count);
<<EndIt>> -- used to end the block if needed
Null;
EXCEPTION
When OTHERS Then
dbms_output.putline(SQLERRM); -- You can not get out of here since it is main PL/SQL blok that went into an exception
END;
Addition
Below is an option of basic steps to do the job (3rd block) without compilation errors. I have no possibility to check it out so please do it yourself (there could be some syntax problem or something else). The goal is to be sure that you can insert the record...
I declared sq variable to construct insert command and the end of the code above could be (please check the command yourself - I can't test it right now) something like here:
DECLARE
...
sq VarChar2(1) := ''''; -- this is single-quote character
...
BEGIN
...
...
--
-- think about what could go wrong below (2 lines) and either leave it as it is or put it in the 3rd nested PL/SQL block
Select count(*) Into c_count From customer_profile Where cust_id Is Null;
cmd := 'insert into audit_table (table_name, column_name, count_type, v_count) values(' || sq || 'customer_profile' || sq || ', ' || sq || 'cust_id' || sq || ', ' || sq || 'Null' || sq || ', ' || c_count || ')';
Begin -- 3rd nested PL/SQL block
Select Count(*) Into c_count From all_tables Where table_name = 'audit_table' and owner = 'your_table_owner'; -- check if table exist - if it doesn't set Status
If c_count = 1 Then -- if table exists then you can insert the record
Execute Immediate cmd;
Status := 'OK'; -- all done
Else
Status := 'ERR';
End If;
Exception
When OTHERS Then
Status := 'ERR - ' || SQLERRM; -- catch error and pass it to the main block
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
dbms_output.putline(Status); -- send error message and exit - there is no audit_table -->> OR DO SOMETHING ELSE
GoTo EndIt;
Else
Null; -- here you can do something else if you wish
End If;
--
<<EndIt>> -- used to end the block if needed
Null;
EXCEPTION
When OTHERS Then
dbms_output.putline(SQLERRM); -- You can not get out of here since it is main PL/SQL blok that went into an exception
END;

Related

PL/SQL: ORA-00942, error reported in logically unreachable else block. Needed to add EXECUTE IMMEDIATE for it to work. Why?

I am trying to write a PL/SQL script, which executes a few SQL statements if a certain table exists/not exists.
For eg:
SET SERVEROUTPUT ON;
DECLARE
cnt NUMBER := 0;
cnt_2 NUMBER := 0;
BEGIN
SELECT count(*) INTO cnt FROM all_tables where TABLE_NAME='DOES_NOT_EXIST';
IF cnt = 0 THEN
dbms_output.put_line('Table does not exist');
ELSE
dbms_output.put_line('Table exists ' || cnt);
SELECT COUNT(*) INTO cnt_2 from DOES_NOT_EXIST;
END IF;
END;
/
When I execute this, I get below error
SELECT COUNT(*) INTO cnt_2 from DOES_NOT_EXIST;
*
ERROR at line 10:
ORA-06550: line 10, column 37:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 10, column 5:
PL/SQL: SQL Statement ignored
But, if I add EXECUTE IMMEDIATE in line 11. Then it works fine.
SET SERVEROUTPUT ON;
DECLARE
cnt NUMBER := 0;
cnt_2 NUMBER := 0;
BEGIN
SELECT count(*) INTO cnt FROM all_tables where TABLE_NAME='DOES_NOT_EXIST';
IF cnt = 0 THEN
dbms_output.put_line('Table does not exist');
ELSE
dbms_output.put_line('Table exists ' || cnt);
EXECUTE IMMEDIATE 'SELECT COUNT(*) INTO cnt_2 from DOES_NOT_EXIST';
END IF;
END;
/
Now it works
Table does not exist
PL/SQL procedure successfully completed.
Could you please help me understand why an error is being reported if the ELSE block cannot be reached logically? The cnt variable will always be 0, then, I would assume the ELSE block is never reached.
How come adding EXECUTE IMMEDIATE is not producing the error?
Does this mean, I should add EXECUTE IMMEDIATE for all such statements in the ELSE block?
I searched on SO and other places but couldn't find an answer. Honestly, I don't know what other search terms to use. All search hits leads to generic errors. So, I am asking this question. If this is already answered, please point me to it and close this.
Thank you.
What you are missing is that statements are processed in two steps; they are compiled before they are executed.
During the compilation phase, the identifiers (tables and columns and more) are identified and looked up. If a table name (or column name or whatever) is not found, then you get a compilation error.
Using execute immediate short-circuits this process. Instead of "executing immediately" this is really "delaying compilation". The statement is both compiled and executed during run time. That is why execute immediate prevents the error.
Looks like you need something like this:
select
owner,
table_name,
xmlcast(
xmlquery(
'/ROWSET/ROW/CNT'
passing xmltype(dbms_xmlgen.getXML('select count(*) cnt from "'||owner||'"."'||table_name||'"'))
returning content null on empty
)
as int
) as cnt
from all_tables
where owner in ('XTENDER',user);
This query returns number of rows for each table from all_tables filtered by your predicates

How can I solve PL/SQL: Statement ignored error?

I'm trying to write a procedure. The procedure will be queried with the school number and the course_name, midterm_not, final_not, the average will be revealed, but the midterm and final grades will be calculated in %. If it is under 60, it will be over and it will be passed.
set serveroutput on;
CREATE OR REPLACE PROCEDURE student_grade(
p_school_no IN lessons.school_number%type,
p_lesson OUT lessons.lesson_name%type,
p_midterm_1 OUT lessons.midterm_notu_1%type,
p_midterm_2 OUT lessons.midterm_notu_2%type,
p_final OUT lessons.final_notu%type,
p_average OUT NUMBER
)
IS
BEGIN
SELECT
d.lesson,
d.midterm_notu_1,
d.midterm_notu_2,
d.final_notu
INTO
p_lesson,
p_midterm_1,
p_midterm_2,
p_final
FROM lessons d
WHERE d.shool_number = p_school_no
p_average := (((d.midterm_notu_1 * 25)/100) + ((d.midterm_notu_2 * 30)/100) + ((d.final_notu * 45)/100));
END;
DECLARE
v_school_no lessons.school_number%type := 20201754;
v_lesson lessons.lesson_name%type;
v_midterm_1 lessons.midterm_notu_1%type;
v_midterm_2 lessons.midterm_notu_2%type;
v_final lessons.final_notu%type;
v_average NUMBER;
BEGIN
student_grade(
v_lesson,
v_midterm_1,
v_midterm_2 ,
v_final,
v_average );
DBMS_OUTPUT.put_line ('Student Grade');
DBMS_OUTPUT.put_line ('School Number: ' ||v_school_no);
DBMS_OUTPUT.put_line ('Midterm 1: ' || v_midterm_1);
DBMS_OUTPUT.put_line ('Midterm 2: ' || v_midterm_2 );
DBMS_OUTPUT.put_line ('Final: ' || v_final);
DBMS_OUTPUT.put_line ('Average: ' || v_average );
END;
I did something like this when I run the procedure it says "Procedure student_grade compiled" but when I try to run the DECLARE part it gives an error like this;
Error report -
ORA-06550: line 9, column 5:
PLS-00905: SYSTEM.STUDENT_GRADE object is invalid
ORA-06550: line 9, column 5:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
I think it has a problem in p_average :=
Can anyone help me with these issues?
You are almost done everything fine. Few things with respect to procedure,
Run the below command (may be you over look the part for error)
ALTER PROCEDURE student_grade COMPILE;
Warning: Procedure altered with compilation errors
you do not need to make the p_average parameter as IN OUT , OUT should be enough as you calculate it inside.
to assign some value to OUT parameter you don't need to use SET. Only assignment using assignment operator is fine. see below
CREATE OR REPLACE PROCEDURE student_grade
(
p_school_no IN lessons.school_number%TYPE
,p_lesson OUT lessons.lesson_name%TYPE
,p_midterm_1 OUT lessons.midterm_notu_1%TYPE
,p_midterm_2 OUT lessons.midterm_notu_2%TYPE
,p_final OUT lessons.final_notu%TYPE
,p_average OUT NUMBER
) IS
BEGIN
SELECT d.lesson
,d.midterm_notu_1
,d.midterm_notu_2
,d.final_notu
INTO p_lesson
,p_midterm_1
,p_midterm_2
,p_final
FROM lessons d
WHERE d.shool_number = p_school_no;
--assign to the output variable for average
p_average := (((d.midterm_notu_1 * 25) / 100) + ((d.midterm_notu_2 * 30) / 100) + ((d.final_notu * 45) / 100));
END;
/
I believe because of error exists in procedure you were unable to test, after doing above changes it should work.
You also can test it with a PL/SQL anonymous block instead with SQL command window which would be easier.
E.g.
DECLARE
--assign the input directly here in the declare section
v_school_no lessons.school_number%type := 10;
v_lesson lessons.lesson_name%type;
v_midterm_1 lessons.midterm_notu_1%type;
v_midterm_2 lessons.midterm_notu_2%type;
v_final lessons.final_notu%type;
v_average NUMBER;
BEGIN
-- call the procedure
student_grade(
v_lesson,
v_midterm_1,
v_midterm_2 ,
v_final,
v_average );
DBMS_OUTPUT.put_line ('Student Grade');
DBMS_OUTPUT.put_line ('School Number: ' ||v_school_no);
DBMS_OUTPUT.put_line ('Midterm 1: ' || v_midterm_1);
DBMS_OUTPUT.put_line ('Midterm 2: ' || v_midterm_2 );
DBMS_OUTPUT.put_line ('Final: ' || v_final);
DBMS_OUTPUT.put_line ('Average: ' || v_average );
END;
/
Let me know if it solves your problem;
REVISED ANSWER WITH SPECIFIC PROBLEM WITH THE USE AND TOOL USAGE
I strongly recommend/suggests you to look into the documentation of PL/SQL before continue with next assignment. It will help you understanding the errors you get so you can correct it. Also there are many videos on the tool SQL Developer , how to use them efficiently.Check them too.
Coming back to you problem I have tried in my machine and there are many problems to the script which I had to fix one by one looking into the error messages. Please find the points and final solution which should work or else I am done.
Problems: there are differences in names of the column in table with the code you have written
WHERE d.shool_number = p_school_no; --> shool_number is not an existing column, it is propbably d.school_number
v_lesson lessons.lesson_name%type; --> the actual column is lesson and not lesson_name. I can say it from your select clause in the procedure
p_lesson OUT lessons.lesson_name%type, --> same as point 2
p_average := (((d.midterm_notu_1 * 25)/100) + ((d.midterm_notu_2 * 30)/100) + ((d.final_notu * 45)/100)); -- you cannot refer the columns like this , what it means with "d." where code doesn't know what it refers. d you have used in the select query as alias to table lessons and with the select statement ends the scope of d finishes there itself. As you have already taken the values to the output variables such as p_midterm_1, p_midterm_2, p_final use them instead.
Additionally make sure the select statement returns only one rows per school_number=20201754 otherwise you end of with error ORA-01422: exact fetch returns more than requested number of rows and then there are other ways to handle it. (for the moment I will not say anything with respect to that)
Final point where you try to test the procedure like student_grade( v_lesson, v_midterm_1, v_midterm_2 , v_final, v_average ); --> you are passing wrong number of arguments to the procedure by not including v_school_no as the first parameter.
However i have created my own setup and modified the procedure and the test accordingly, see below.
--table definition
create table lessons (school_number number,lesson varchar2(100),midterm_notu_1 number,midterm_notu_2 number,final_notu number);
--inserting unique rows per school_number
insert into lessons values(20201754,'Maths',35,55,85);
insert into lessons values(20201755,'Science',45,65,95);
-- to enable the dbms_output
SET SERVEROUTPUT ON;
--procedure definition
CREATE OR REPLACE PROCEDURE student_grade(
p_school_no IN lessons.school_number%type,
p_lesson OUT lessons.lesson%type,
p_midterm_1 OUT lessons.midterm_notu_1%type,
p_midterm_2 OUT lessons.midterm_notu_2%type,
p_final OUT lessons.final_notu%type,
p_average OUT NUMBER
)
IS
BEGIN
SELECT
d.lesson,
d.midterm_notu_1,
d.midterm_notu_2,
d.final_notu
INTO
p_lesson,
p_midterm_1,
p_midterm_2,
p_final
FROM lessons d
WHERE d.school_number = p_school_no;
p_average := (((p_midterm_1 * 25)/100) + ((p_midterm_2 * 30)/100) + ((p_final * 45)/100));
END student_grade;
/
--testing the procedure
DECLARE
v_school_no lessons.school_number%type := 20201754;
v_lesson lessons.lesson%type;
v_midterm_1 lessons.midterm_notu_1%type;
v_midterm_2 lessons.midterm_notu_2%type;
v_final lessons.final_notu%type;
v_average NUMBER;
BEGIN
student_grade(
v_school_no,
v_lesson,
v_midterm_1,
v_midterm_2 ,
v_final,
v_average );
DBMS_OUTPUT.put_line ('Student Grade');
DBMS_OUTPUT.put_line ('School Number: ' ||v_school_no);
DBMS_OUTPUT.put_line ('Midterm 1: ' || v_midterm_1);
DBMS_OUTPUT.put_line ('Midterm 2: ' || v_midterm_2 );
DBMS_OUTPUT.put_line ('Final: ' || v_final);
DBMS_OUTPUT.put_line ('Average: ' || v_average );
END;
/
The 'accept' (accept p_school_no prompt ) is a sqlplus directive, not a pl/sql statement. PL/SQL runs entirely inside the database and has no means of 'accepting' input from the user. The only means of 'accepting' run-time values is by supplying on the command line when you call the procedure. That's what those IN parameters are for.
exec student_grade('schoolname');
Also, 'set' (SET p_average := ) is not valid for a SELECT. It belongs with UPDATE.

Statements in if statement with false condition are executed

I have created this guard function:
create or replace
FUNCTION checkTableExists (tableName varchar2)
RETURN BOOLEAN
IS c INT;
BEGIN
SELECT COUNT(*) INTO c FROM user_tables where table_name = upper(tableName);
return c = 1;
END;
I try to use it like:
IF checkTableExists ('NO_TABLE') THEN
DELETE FROM NO_TABLE;
END IF;
Even though the table doesn't exist, I get:
Error report:
ORA-06550: line 6, column 17:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 6, column 5:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
How do I get around this? Dynamic SQL?
UPDATE: I get no error if I run:
IF checkTableExists ('NO_TABLE') THEN
dbms_output.put_line('argh');
END IF;
And argh is not output. If I run the above with a table that does exist, argh is output as expected.
Your statement is parsed completly. So if you replace
DELETE FROM NO_TABLE;
by
null;
your statement should work.
Your error message was a little bit misleading because in line 6 is the select on user_tables and the delete is in line 2 in a different statement. This makes it harder to debug.
So you have to use dynamic sql:
execute immidiate 'delete from ' || 'NO_TABLE';
Don't use a guard function; just try to delete the table and catch the exception if it does not exist:
DECLARE
table_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT( table_not_exists, -942 );
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM no_table';
DBMS_OUTPUT.PUT_LINE( 'deleted' );
EXCEPTION
WHEN table_not_exists THEN
DBMS_OUTPUT.PUT_LINE( 'did not exist' );
END;
/
db<>fiddle
If you don't want to initialise the exception in lots of different PL/SQL blocks then initialise once it in a package:
CREATE PACKAGE exceptions IS
table_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT( table_not_exists, -942 );
END;
/
and the code is then simply:
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM no_table';
DBMS_OUTPUT.PUT_LINE( 'deleted' );
EXCEPTION
WHEN EXCEPTIONS.table_not_exists THEN
DBMS_OUTPUT.PUT_LINE( 'did not exist' );
END;
/
db<>fiddle
If you do want to use your guard function (I'd advise that you don't) then just use EXECUTE IMMEDIATE:
IF checkTableExists ('NO_TABLE') THEN
EXECUTE IMMEDIATE 'DELETE FROM NO_TABLE';
END IF;

Incomplete/Malformed cursor

I have a PL/SQL script to perform some aggregation tasks. I am having compilation errors, logic is not top priority at this moment as that can be changed once the errors are resolved. The script is as follows :
SET SERVEROUTPUT ON;
ALTER SESSION SET NLS_DATE_FORMAT = 'dd-MON-yy';
CREATE OR REPLACE PROCEDURE updateFeaturePerformanceTable
(noOfDays IN NUMBER, runDate IN DATE, timeSpan IN VARCHAR2)
AS
CURSOR c_feature_performance IS
SELECT distinct mkt_id,dow,device_type,feature_name
FROM gfmdev.feature_performance
WHERE timespan = 'ONE_DAY'
AND feature_performance_day >= TO_DATE('17-AUG-15','dd-MON-yy');
rowsExtracted c_feature_performance%ROWTYPE;
extractDate DATE;
timespan_test varchar2(20);
BEGIN
OPEN c_feature_performance;
extractDate := runDate - noOfDays;
timespan_test := timeSpan;
LOOP
FETCH c_feature_performance INTO rowsExtracted;
EXIT WHEN c_feature_performance%NOTFOUND;
dbms_output.put_line(extractDate || ' ' || timespan_test);
INSERT INTO gfmdev.feature_performance
SELECT
rowsExtracted.mkt_id,
rowsExtracted.dow,
rowsExtracted.device_type,
rowsExtracted.feature_name,
SUM(OPS),
SUM(GV_COUNT),
runDate,
timespan_test
FROM gfmdev.feature_performance
WHERE feature_performance_day BETWEEN extractDate AND runDate
AND timespan = 'ONE_DAY'
AND mkt_id = rowsExtracted.mkt_id
AND dow = rowsExtracted.dow
AND device_type = rowsExtracted.device_type
AND feature_name = rowsExtracted.feature_name
group by mkt_id, dow, device_type, feature_name, timespan;
END LOOP;
CLOSE c_feature_performance;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('Trying to insert too many rows in SELECT...INTO');
ROLLBACK;
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('No rows returned in SELECT...INTO');
ROLLBACK;
WHEN STORAGE_ERROR THEN
dbms_output.put_line('Too much data to handle! Storage error');
ROLLBACK;
WHEN OTHERS THEN
dbms_output.put_line('Oops! Something went wrong');
ROLLBACK;
RAISE;
END updateFeaturePerformanceTable;
Show compilation errors:
SHOW ERRORS PROCEDURE updateFeaturePerformanceTable;
Run the procedure:
DECLARE
runDate DATE;
BEGIN
FOR j IN 0 .. 5 LOOP
SELECT (TO_DATE('17-AUG-15','dd-MON-yy') + j) INTO runDate FROM dual;
updateFeaturePerformanceTable(6,runDate, 'ONE_WEEK');
END LOOP;
DBMS_OUTPUT.PUT_LINE(' WEEKLY RECORDS UPDATED ');
FOR j IN 0 .. 28 LOOP
SELECT (TO_DATE('17-AUG-15','dd-MON-yy') + j) INTO runDate FROM dual;
updateFeaturePerformanceTable(29,runDate, 'ONE_MONTH');
END LOOP;
DBMS_OUTPUT.PUT_LINE(' MONTHLY RECORDS UPDATED ');
COMMIT;
END;
/
When I execute it I get the following error message :
Errors for PROCEDURE UPDATEFEATUREPERFORMANCETABLE:
LINE/COL ERROR
-------- -----------------------------------------------------------------
4/9 PLS-00341: declaration of cursor 'C_FEATURE_PERFORMANCE' is
incomplete or malformed
5/3 PL/SQL: SQL Statement ignored
6/15 PL/SQL: ORA-00942: table or view does not exist
8/16 PL/SQL: Item ignored
16/3 PL/SQL: SQL Statement ignored
16/36 PLS-00320: the declaration of the type of this expression is
incomplete or malformed
21/3 PL/SQL: SQL Statement ignored
LINE/COL ERROR
-------- -----------------------------------------------------------------
36/22 PL/SQL: ORA-00904: "ROWSEXTRACTED"."FEATURE_NAME": invalid
identifier
36/22 PLS-00320: the declaration of the type of this expression is
incomplete or malformed
updateFeaturePerformanceTable(6,runDate, 'ONE_WEEK');
*
Any help on where I am going wrong is highly appreciated?
PL/SQL is a framework for running SQL statements programmatically. So often we find that PL/SQL errors are caused by the SQL errors in our code.
That is the case here. You have several PL/SQL errors indicating that the Cursor declaration is invalid. And why is it invalid? This line holds the answer:
6/15 PL/SQL: ORA-00942: table or view does not exist
So the problem is, the owner of the procedure UPDATEFEATUREPERFORMANCETABLE (ugly name by the way) does not have rights on gfmdev.feature_performance.
To solve, the table owner GFMDEV needs to grant SELECT and INSERT directly to the account which owns this procedure.
SQL> conn gfmdev/password
SQL> grant select, insert on feature_performance to whoever;
Note that granting privileges through a view won't cut it. The Oracle security model only permits us to build objects - PL/SQL programs, views - with privileges granted directly to our user.

NO_DATA_FOUND exception handling with Select Into MULTIPLE variables

I have looked for a while now for a solution to this issue, and all the NO_DATA_FOUND handling tutorials I found only show how to do it when doing a Select Into with only one column into one variable.
First, here is my code:
BEGIN
OPEN c_no_source;
LOOP
DECLARE
l_price_code VARCHAR2(20) := '';
l_price_level VARCHAR(10) := '';
l_code_date DATE := p_effective_date;
BEGIN
FETCH c_no_source INTO c_no_source_row;
exit WHEN c_no_source%NOTFOUND;
BEGIN
WITH codeList AS /* liste des dates ayant une donnée avant la date effective incluse. */
(
SELECT distinct
NVL(effective_date,p_effective_date) as effective_date,
NVL(user_group2,'') as price_code,
NVL(user_group3,'') as price_level
FROM DGA_Service.Hjvsecmaster_Hist
WHERE effective_date <= p_effective_date
AND user_group2 IS NOT NULL
AND security_alias = c_no_source_row.security_alias
ORDER BY 1 DESC
)
SELECT price_code, price_level, effective_date
INTO l_price_code, l_price_level, l_code_date
FROM codelist
WHERE ROWNUM = 1;
EXCEPTION WHEN no_data_found THEN NULL;
...
[UPDATE statement using the variables]
...
END;
END;
END LOOP;
CLOSE c_no_source;
END;
What I'm trying to do is take the top 1 result of my codeList temp result set into three separate variables.
However one of the three columns in codeList will often be NULL. And none of my 3 variables will be set.
I tried to handle the exception but I can't get my code to insert the columns that DO have a value into their respective variables. If even one of the columns is NULL then none of them are inserted into their variable.
I would like the Select Into statement to set the variables that it CAN set.
I tried to handle them all separately using something like this:
EXCEPTION WHEN NO_DATA_FOUND THEN
BEGIN
IF price_code IS NOT NULL THEN l_price_code := price_code; END IF;
IF price_level IS NOT NULL THEN l_price_level := price_level; END IF;
IF effective_date IS NOT NULL THEN l_code_date := effective_date; END IF;
END;
But I got the following error message:
ORA-06550: line 294, column 18:
PLS-00201: identifier 'PRICE_CODE' must be declared
ORA-06550: line 294, column 15:
PL/SQL: Statement ignored
ORA-06550: line 295, column 18:
PLS-00201: identifier 'PRICE_LEVEL' must be declared
ORA-06550: line 295, column 15:
PL/SQL: Statement ignored
ORA-06550: line 296, column 18:
PLS-00201: identifier 'EFFECTIVE_DATE' must be declared
ORA-06550: line 296, column 15:
PL/SQL: Statement ignored
So I ran out of ideas. I tried specifying the temp table name, to no avail, like this:
IF codeList.price_code IS NOT NULL
I would love any help on this issue. The package this piece of code runs in is already heavy and I would prefer not to have to get each column with a separate With ... As () Select Into clause.
Okay, I think I get you; most of your problem is caused by you nesting PL/SQL blocks incorrectly.
Your update statement is contained within the EXCEPTION block, which means it'll only get executed if the exception is thrown. Secondly, you're referencing the columns directly in the following:
EXCEPTION WHEN NO_DATA_FOUND THEN
BEGIN
IF price_code IS NOT NULL THEN l_price_code := price_code; END IF;
IF price_level IS NOT NULL THEN l_price_level := price_level; END IF;
IF effective_date IS NOT NULL THEN l_code_date := effective_date; END IF;
END;
This is the cause of your compilation error.
Lastly, if there is a single row in your select into then every variable will be set, so there's no need to try to deal with this.
BEGIN
OPEN c_no_source;
LOOP
DECLARE
l_price_code VARCHAR2(20);
l_price_level VARCHAR(10);
l_code_date DATE := p_effective_date;
BEGIN
FETCH c_no_source INTO c_no_source_row;
exit WHEN c_no_source%NOTFOUND;
BEGIN
WITH codelist AS (
SELECT DISTINCT effective_date
, user_group2 AS price_code
, user_group3 AS price_level
FROM hjvsecmaster_hist
WHERE effective_date <= p_effective_date
AND user_group2 IS NOT NULL
AND security_alias = c_no_source_row.security_alias
ORDER BY 1 DESC
)
SELECT price_code, price_level, effective_date
INTO l_price_code, l_price_level, l_code_date
FROM codelist
WHERE ROWNUM = 1;
EXCEPTION WHEN no_data_found THEN
-- All variables are their initial setting
null;
END;
...
[UPDATE statement using the variables]
...
END;
END LOOP;
CLOSE c_no_source;
END;
This is normally a highly inefficient way of doing this. If you can fit everything into a single UPDATE or MERGE then I would do so. You don't appear to have any additional logic so it should be possible.
However one of the three will often be NULL, and trigger the
NO_DATA_FOUND error, and none of my 3 variables will be set
NO_DATA_FOUND is raised when the query does not return any rows. It has nothing to do with the column values. They could all be null and the statement would succeed.
EXCEPTION WHEN NO_DATA_FOUND THEN
BEGIN
IF price_code IS NOT NULL THEN l_price_code := price_code; END IF;
IF price_level IS NOT NULL THEN l_price_level := price_level; END IF;
IF effective_date IS NOT NULL THEN l_code_date := effective_date; END IF;
END;
your variables are prefixed with "l_" and you are using the column names eg.price_code in your comparision, hence the error. More importantly, NO_DATA_FOUND would mean all the variable values are null, so this exception block does not do anything.
All you'd probably need is this.
if you don't want to insert null values for a given id (if all are null)
BEGIN
insert into target_table (id, col1, col2, col3)
select id, col1, col2, col3
from (target_table)
where not (col1 is null and col2 is null and col3 is null);
commit;
end;
/
If there is no data for a given id, nothing is inserted. If atleast one of them is not null, then these values are inserted.