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.
Related
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;
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
I'm creating a stored procedure to get data from a bucket in Object store and load it into a table. But I'm getting a error ORA-00904 when creating the SP. Any idea what could be wrong?
create or replace procedure watcher_load
is
v_exists varchar2(1) := 'F';
CURSOR c_filelist is
select replace(regexp_substr(OBJECT_NAME ,'[^_]*(_|$)',1,2), '_', '' )
object_name,
bytes
from DBMS_CLOUD.LIST_OBJECTS('user_cred','https://objectstorage.oraclecloud.com/n/bunge/bingo_subside/o/')
where object_name like '20%_%_bingo_hourly_res.json';
begin
FOR i in c_filelist
LOOP
select 'T' into v_exists from watcher_table where filename = i.object_name;
if v_exists != 'T' then
insert into watcher_table (region, filename, bytes)
values (i.region, i.object_name, i.bytes);
end if;
END LOOP;
commit;
end watcher_load;
/
Errors for PROCEDURE WATCHER_LOAD:
LINE/COL ERROR
-------- -----------------------------------------------------------------
6/9 PL/SQL: SQL Statement ignored
9/15 PL/SQL: ORA-00904: "DBMS_CLOUD"."LIST_OBJECTS": invalid
identifier
17/4 PL/SQL: SQL Statement ignored
17/65 PL/SQL: ORA-00904: "I"."FILENAME": invalid identifier
17/65 PLS-00364: loop index variable 'I' use is invalid
19/9 PL/SQL: SQL Statement ignored
20/39 PLS-00364: loop index variable 'I' use is invalid
20/41 PL/SQL: ORA-00984: column not allowed here
As per oracle - Note:To run DBMS_CLOUD subprograms with a user other than ADMIN you need togrant EXECUTE privileges to that user. For example, run the following command as ADMIN to
grant privileges to adwc_user
Link here- https://docs.oracle.com/en/cloud/paas/autonomous-data-warehouse-cloud/user/dbms-cloud.html#GUID-3D0A5361-672C-467D-AA90-656B1B4D4E37
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;
How can I find the position of an error in a Dynamic SQL statement in PL/SQL or SQL?
From SQL*Plus I see the position of an error in, for example, an invalid SQL DML statement:
SYS#orcl> SELECT
2 X
3 FROM
4 TABLEX
5 /
TABLEX
*
ERROR at line 4:
ORA-00942: table or view does not exist
SQL*Plus shows the error with the line number, and prints and marks that line with an asterisk where the error is found.
Converting to Dynamic SQL, I can get the error code (SQLCODE) and error message (SQLERRM):
SYS#orcl> SET SERVEROUTPUT ON
SYS#orcl> BEGIN
2 EXECUTE IMMEDIATE 'SELECT X FROM TABLEX';
3 EXCEPTION
4 WHEN OTHERS THEN
5 DBMS_OUTPUT.PUT_LINE('SQLCODE:' || SQLCODE);
6 DBMS_OUTPUT.PUT_LINE('SQLERRM:' || SQLERRM);
7 END;
8 /
SQLCODE:-942
SQLERRM:ORA-00942: table or view does not exist
But how do I get the position of the error in the Dynamic SQL string?
I see that Oracle provides a SQL Communications Area (SQLCA) that contains interesting information about an error. In particular:
the SQLCODE and SQLERRM fields (that might be the source of the data retrieved with the respective PL/SQL functions),
the SQLERRD field where the SQLERRD(5) element that gives the 'parse error offset'.
Is it possible to access SQLERRD from PL/SQL or SQL? If so, how? If not, what other technique can give the location of the error from PL/SQL or SQL?
(Here http://docs.oracle.com/cd/B28359_01/appdev.111/b31231/chapter8.htm#BABIGBFF the SQLCA is documented and accessed with Pro*C.)
(The answer here how to declare SQLCA.SQLERRD? seems to indicate that SQLERRD is not defined in PL/SQL and therefore not accessible.)
(The discussion here Why doesn't Oracle tell you WHICH table or view does not exist? gives some suggestions to show bad SQL using trace files and to show the location of errors in some development tools.)
you got a package for extracting error messages in dbms_utility
begin
.. generate error
exception when others then
dbms_output.put_line(
dbms_utility.format_call_stack() || chr(10) ||
dbms_utility.format_error_backtrace() || chr(10) ||
dbms_utility.format_error_stack())
end;
Running the statement through dynamic PL/SQL will store the relevant line number in the error stack.
For example, this statement has an error on line 4:
declare
v_count number;
v_bad_sql varchar2(32767) :=
'SELECT
X
FROM
TABLEX';
begin
execute immediate v_bad_sql into v_count;
exception when others then
begin
execute immediate
'begin for i in ( '||v_bad_sql||') loop null; end loop; end;';
exception when others then
dbms_output.put_line(sqlerrm);
end;
end;
/
ORA-06550: line 4, column 4:
PL/SQL: ORA-00942: table or view does not exist
ORA-00942: table or view does not exist
ORA-06550: line 1, column 18:
PL/SQL: SQL Statement ignored
ORA-00942: table or view does not exist
There are some drawbacks to this method:
It requires some extra, ugly code to catch the exception and re-try the SQL.
The example only works for selects. You'll need to tweak it for insert, update, delelete, merge, dynamic PL/SQL, etc. Normally you should know what kind of SQL statement it is. If you're unlucky, you'll need to parse the statement, which can be very difficult.
The column number is wrong if the entire PL/SQL statement is on one line.