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

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

Related

How can I handle this compilation error through exception?

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;

Oracle SQL - UNION breaks when "table does not exist"

I am new to Oracle SQL (and not generally experienced in SQL anyway). I would appreciate it if I could get some help with my problem.
I need to query several tables and views (30 or so) and do it as a UNION across all and return a single result set. However, some of the tables may or may not exist. I know the exact names of all 30 tables/views to be queried. I may do other queries prior to doing the large UNION, but eventually, I must return a single result of all rows. I cannot "define" variables in my sql script.
These are constraints placed on my and beyond my control. In order to work with the existing workflow, I have to operate within these contraints.
The problem is that if any of the tables/views do not exist, then the entire UNION fails and I get no results at all. I get this at the first occurrence of the missing view/table:
SQL Error: ORA-00942: table or view does not exist
00942. 00000 - "table or view does not exist"
*Cause:
*Action:
I would like be able to handle this without breaking. I'd like to simply skip over missing tables and return the remaining rows (though I know this is poor design to try to query without knowing if the table exists). It's ok if I end up including error messages as these will be skipped by the parser eventually.
I have something like this. If any of the views do not exist, everything fails and I get no results:
select col1 || col2 || ... from view1
union
select col1|| col2 || ... from view2
union
select name from view3
.
.
select ... from view30;
I can switch to PL/SQL if that helps. I don't have much experience with PL/SQL but I can use it if it can solve the problem. I did some reading on PL/SQL but could not figure out some straightforward way of doing this.
Any help is highly appreciated. Let me know if I can provide any other details.
Thanks.
Colleagues don't say that you CAN'T do that - they are saying you SHOULDN'T, cause of the fundamental problems. But the world isn't perfect - it often gives you a saw and tells you to hammer a nail. There is a solution to your problem - it involves dynamic SQL and some custom PL/SQL code. Simplified solution:
Create a package and a function:
function does_tbl_exists(p_tbl_name in varchar2) return number is
l_stmt varchar2(60);
begin
l_stmt := 'select count(*) from ' ||p_tbl_name;
execute immediate l_stmt;
return 1;
exception
when others then
return 0 ;
end;
Created a test table TEST_TBL with some data. And then we build up our SQL statement in a pl/sql block:
declare
l_count number;
l_stmt varchar2(400);
l_tab_name_1 varchar2(30) := 'TEST_TBL';
l_tab_name_2 varchar2(30) := 'TEST_TBL2';
begin
if test_pck.does_tbl_exists(p_tbl_name => l_tab_name_1) = 1 then
l_stmt := 'select count(*) count_number from ' || l_tab_name_1;
end if;
if test_pck.does_tbl_exists(p_tbl_name => l_tab_name_2) = 1 then
if l_stmt is not null then
l_stmt := l_stmt || ' union all' || chr(10);
end if;
l_stmt := l_stmt || 'select count(*) count_number from ' || l_tab_name_2;
end if;
if l_stmt is not null then
l_stmt := 'select sum(tmp.count_number) from (' || l_stmt || ') tmp';
dbms_output.put_line(l_stmt);
execute immediate l_stmt into l_count;
dbms_output.put_line('count: '||l_count);
else
dbms_output.put_line('Nothing todo.');
end if;
end;
By this template, you can build up your final SQL statement.
But use this as a last resort - I think you should still go to higher-ups and talk with them, that their current "requirement" is "bad" and they should feel "bad" (Insert meme here).

How to check If column exists and table row count togather before running the query in PL/SQL?

I need to execute one query, Before executing the query I need to check the following two conditions,
1) Table row count is 0
2) One of table column exists.
So basing on my requirement I wrote the procedure as follows,
DECLARE
rowCount INT;
column_not_exists exception;
pragma exception_init (column_not_exists , -00904);
BEGIN
SELECT count(*) INTO rowCount FROM Settings;
IF (rowCount = 0) THEN
Insert into Settings (ID,PROCESSID,AREA) select ID,PROCESSID,AREA from POINT WHERE CLASSNAME = 'ENDPOINT' and PROCESSID IS NOT NULL;
END IF;
exception WHEN column_not_exists THEN NULL;
END;
/
But somehow the exception is not handled and still I can see the error message in the logs
PL/SQL: ORA-00904: "PROCESSID": invalid identifier
ORA-06550: line 8, column 1:
PL/SQL: SQL Statement ignored
Can somebody help me here to validate both the conditions togather.
You had better use system view ALL_TAB_COLUMNS (of course, you need to have priveleges to access it). Also you need to use Oracle dynamic SQL statement "EXECUTE IMMEDIATE", otherwise your code will not compile.
I've altered you code here, please replace accordingly these places:
lower(t.OWNER)='owner',
lower(t.TABLE_NAME)='point'
code:
declare
rowcount int;
l_cnt number;
begin
select count(*) into rowcount from settings;
if (rowcount = 0) then
select count(*) into l_cnt from ALL_TAB_COLUMNS t
where lower(t.OWNER)='owner' and lower(t.TABLE_NAME)='point'
and lower(t.COLUMN_NAME) in ('processid','area');
if l_cnt = 2 then
execute immediate 'insert into settings
(id, processid, area)
select id, processid, area
from point
where classname = ''ENDPOINT''
and processid is not null';
end if;
end if;
end;

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.

recompile invalid objects in Oracle SQL

As part of a database build, we have some sql which recompiles all invalid objects in the DB. This was working fine, but now we see the following error:
ORA-04047: object specified is incompatible with the flag specified
ORA-06512: at "SYS.DBMS_UTILITY", line 156
ORA-06512: at "SYS.DBMS_DDL", line 157
ORA-06512: at line 51
Could it be something to do with the fact that we've just created a load of editioning views for edition-based redefinition?
Here's the code:
DECLARE
v_counter INTEGER := 1;
CURSOR cur_invalid_objects
IS
SELECT object_name,
object_type
FROM user_objects
---*** exclude VIEW and MATERIALIZED VIEW object types
WHERE status = 'INVALID'
and object_type NOT IN ('VIEW','MATERIALIZED VIEW');
FUNCTION get_total_invalid_objects
RETURN INTEGER
IS
CURSOR get_total_invalid_objs_cur
IS
SELECT count(*)
FROM user_objects
---*** exclude VIEW and MATERIALIZED VIEW object types
WHERE status = 'INVALID'
AND object_type NOT IN ('VIEW','MATERIALIZED VIEW');
v_total INTEGER := 0;
BEGIN
OPEN get_total_invalid_objs_cur;
FETCH get_total_invalid_objs_cur
INTO v_total;
CLOSE get_total_invalid_objs_cur;
RETURN v_total;
END get_total_invalid_objects;
BEGIN
WHILE get_total_invalid_objects > 0
LOOP
IF v_counter <= 100 -- Failsafe: exit while loop if the objects cannot be recompiled after 100 tries
THEN
FOR cur_invalid_objects_rec IN cur_invalid_objects
LOOP
EXIT WHEN cur_invalid_objects%NOTFOUND;
dbms_ddl.alter_compile(cur_invalid_objects_rec.object_type,NULL,cur_invalid_objects_rec.object_name);
END LOOP;
v_counter := v_counter + 1;
ELSE
dbms_output.put_line('Unable to recompile objects to a status of: VALID. Please investigate further.');
EXIT;
END IF;
END LOOP;
END;
/
EXIT
/
I tried removing the exclusion clauses for VIEW, but I got the same error.
The object types supported by DBMS_DDL.ALTER_COMPILE are PROCEDURE, FUNCTION, PACKAGE, PACKAGE BODY and TRIGGER (see Oracle documentation).
Maybe you have invalid synonyms. Try restricting your query to these types.
BTW, there's also DBMS_UTILITY.compile_schema (as mentioned by a_horse_with_no_name)
Maybe this helps also:
UTL_RECOMP