I'm trying to get a process to be kicked off when a query finds a table is empty - oracle

I want to code a way to check if there's data in a table prior to executing a stored procedure.
I've created some 'stripped down' test code that 'almost' meets the conditions that I seek, I was hoping someone might be able to help me get that to work. If so, then I can substitute the values for my procedure, instead of just the dbms_output and be up and running.
Creates a test table, with no rows.
CREATE TABLE t (c VARCHAR2(20));
Then the way I am trying to do this...
declare
no_such_table exception;
pragma exception_init( no_such_table, -942 );
EXISTS_1 integer;
BEGIN
for tst in (
select count (c) x from t
)
loop
execute immediate' select count (c) Z from ' ||tst.t into EXISTS_1;
if EXISTS_1 <= 0
then
dbms_output.put_line( 'a' );
else dbms_output.put_line( 'b' );
end if;
end loop;
exception
when no_such_table
then
dbms_output.put_line( 'c' );
WHEN NO_DATA_FOUND THEN
dbms_output.put_line( 'd' );
end;
The first part, with the count, is supposed to hold a numeric value to indicate if there are any rows in the table. Then the execute immediate into EXISTS_1 holds the value to decide what output to give.
Firstly, I can't get the execute immediate bit to actually work. But if I could get it working, I want the output to record 'a' if there's no rows in the table. (Actually, I would execute the procedure here) and to record 'b' if there was data in there, which you can insert with:
insert into t (c) values('x');
commit;
The 'c' and 'd' outputs are just attempts to handle other potential issues that may occur.
As things stand, I get an error indicating that component t must be declared. Can you understand what I 'm trying to do, and if so, hopefully suggest a means to achieve my goal please?

your first select in a loop will always return one row, so there now Need to do a loop.
using tst.t in a Loop is not possible.
is that what are you looking for?
declare
x number := 0;
begin
select count(c) into x from t;
if x <= 0
then
dbms_output.put_line( 'a' );
else
dbms_output.put_line( 'b' );
end if;
end;

Related

how to cancel Pipe row?

I have a pipeline function. If I catch an exception, I want to return no lines. Even if the exception occurs after the first utilisation of pipe row.
CREATE TYPE a_r IS OBJECT (a1 VARCHAR (1), a2 VARCHAR (1));
CREATE TYPE a_t IS TABLE OF a_r;
CREATE or replace FUNCTION get_a
RETURN a_t
PIPELINED
IS
BEGIN
FOR c IN (SELECT '1' a , '2' b FROM DUAL)
LOOP
PIPE ROW (a_r(c.a,c.b));
END LOOP;
FOR a
IN (SELECT 'a2' a,
'b' b
FROM DUAL)
LOOP
PIPE ROW (a_r(a.a,a.b));
END LOOP;
exception
WHEN VALUE_ERROR
THEN
DBMS_OUTPUT.put_line ('VALUE_ERROR');
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('other');
END;
select * from table(get_a())
In this example, Before the error ('ab' is not varchar2(1)) occurs, the first line is already piped. But I want nothing to be returned. Is there a way to write cancel what was piped in the exception block?
code
No because the client may already have consumed the row.
You could code your function so that it fills a local collection with data and only at the end decides whether to iterate through the collection to actually return the rows. That will require more memory since you're materializing the entire result set before returning it and defeats some of the performance benefits of using pipelined table functions.
Something like this would appear to do what you want
CREATE or replace FUNCTION get_a
RETURN a_t
PIPELINED
IS
l_a_t a_t := new a_t();
BEGIN
FOR c IN (SELECT '1' a , '2' b FROM DUAL)
LOOP
l_a_t.extend;
l_a_t( l_a_t.count ) := a_r(c.a,c.b);
END LOOP;
FOR a
IN (SELECT 'a2' a,
'b' b
FROM DUAL)
LOOP
l_a_t.extend;
l_a_t( l_a_t.count ) := a_r(a.a,a.b);
END LOOP;
for i in 1 .. l_a_t.count
loop
pipe row( l_a_t(i) );
end loop;
exception
WHEN VALUE_ERROR
THEN
DBMS_OUTPUT.put_line ('VALUE_ERROR');
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('other');
END;
/
See this dbfiddle

Ignore lines that causes errors

I have a big Oracle script with thousands of package call inside a BEGIN - END;
Is there a way to ignore the lines that causes error and continue executing the next lines? Some sort of "On Error Resume Next" in vb.
If you have only one BEGIN END section, then you can use EXCEPTION WHEN OTHERS THEN NULL.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
exception when others then null;
end;
SQL> /
PL/SQL procedure successfully completed.
SQL> declare
v_var pls_integer;
begin
select 1 into v_var from dual;
-- now error
select 'A' into v_var from dual;
--exception when others then null;
end;
/
declare
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 6
SQL>
The whole concept of "ignore errors" is a bug, and a lie if any errors occur. That is not to say you cannot trap errors and continue processing, just that you MUST handle the errors. For example, assume the use case: "Data has been loaded into a stage table from multiple .csv files. Now load into the tables A and Table B according to ....".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, col_a2);
insert into table_b (col1, col2)
values (rec.col_b1, col_b2);
exception
when others then null;
end;
end loop;
process_message := 'Load Tables A,B Complete';
end ;
Now suppose a user created the a .csv file entered "n/a" in numeric columns where there was no value or the value was unknown. The result of this all too common occurrence is all such rows were not loaded, but you have no way to know that until the user complains their data was not loaded even though you told them it was. Further you have no way of determining the problem.
A much better approach is to "capture and report".
create procedure
Load_Tables_A_B_from_Stage(process_message out varchar2)
is
load_error_occurred boolean := False;
Begin
For rec in (select * from stage)
loop
begin
insert into table_a (col1, col2)
values (rec.col_a1, rec.col_a2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
begin
insert into table_b (col1, col2)
values (rec.col_b1, rec.col_b2);
exception
when others then
log_load_error('Load_Tables_A_B_from_Stage', stage_id, sqlerrm);
load_error_occurred := True;
end;
end loop;
if load_error_occurred then
process_message := 'Load Tables A,B Complete: Error(s) Detected';
else
process_message := 'Load Tables A,B Complete: Successful No Error(s)';
end if;
end Load_Tables_A_B_from_Stage ;
Now you have informed the user of the actual status, and where you are contacted you can readily identify the issue.
User here is used in the most general sense. It could mean a calling routine instead of an individual. Point is you do not have to terminate your process due to errors but DO NOT ignore them.
I don't think there is any magic one-liner that will solve this.
As others have, use a editor to automate the wrapping of each call within a BEGIN-EXCEPTION-END block might be quicker/easier.
But, if feel a little adventurous, or try this strategy:
Let's assume you have this:
BEGIN
proc1;
proc2;
proc3;
.
.
.
proc1000;
END;
You could try this (untested, uncompiled but might give you an idea of what to try):
DECLARE
l_progress NUMBER := 0;
l_proc_no NUMBER := 0;
e_proc_err EXCEPTION;
-- A 'runner' procedure than manegrs the counters and runs/skips dpending on these vals
PROCEDURE run_proc ( pname IN VARCHAR2 ) IS
BEGIN
l_proc_no := l_proc_no + 1;
IF l_proc_no >= l_progress
THEN
-- log 'Running pname'
EXECUTE IMMEDIATE 'BEGIN ' || pname || '; END;' ;
l_progress := l_progress + 1;
ELSE
-- log 'Skipping pname'
END IF;
EXCEPTION
WHEN OTHERS THEN
-- log 'Error in pname'
l_progress := l_progress + 1;
RAISE e_proc_err;
END;
BEGIN
l_progress := 0;
<<start>>
l_proc_no := 0;
run_proc ( 'proc1' );
run_proc ( 'proc2' );
run_proc ( 'proc3' );
.
.
run_proc ( 'proc1000' );
EXCEPTION
WHEN e_proc_err THEN
GOTO start;
WHEN OTHERS THEN
RAISE;
END;
The idea here is to add a 'runner' procedure to execute each procedure dynamically and log the run, skip, error.
We maintain a global count of the current process number (l_proc_no) and overall count of steps executed (l_progress).
When an error occurs we log it, raise it and let it fall into the outer blocks EXCEPTION handler where it will restart via an (evil) GOTO.
The GOTO is placed such that the overall execution count is unchanged but the process number is reset to 0.
Now when the run_proc is called it sees that l_progress is greater than l_proc_no, and skips it.
Why is this better than simply wrapping a BEGIN EXCEPTION END around each call?
It might not be, but you make a smaller change to each line of code, and you standardise the logging around each call more neatly.
The danger is a potential infinite loop which is why I specify e_proc_err to denote errors within the called procedures. But it might need tweaking to make it robust.

Error when running stored procedure : maximum number of object durations exceeded seems when variable not pass it works

I'm newbie in stored procedures and I create a stored procedure, but when I run it by user input, I get an error; but when get value to variable daynumber, it is working.
Suggetions from SQL Developer are:
*Cause: This typically happens if there is infinite recursion in the PL/SQLfunction that is being executed.
*Action: User should alter the recursion condition in order to prevent infinite recursion.
How can I solve it?
create or replace procedure P_SiteNumber_Range_D(Sitenum NUMBER) is
daynumber number;
begin
p_sitenumber_range_d(Sitenum => daynumber);
-- daynumber := 2;
for l in (select PROVINCE from v_sitenumber_D_province_range)
loop
update PM4h_db.IND_D_3102
set IND_D_3102_029 =
(select countsite from some table where l1.province=province );
end loop;
end P_SiteNumber_Range_D;
Run procedure as :
DECLARE
SITENUM NUMBER;
BEGIN
SITENUM := 3;
P_SITENUMBER_RANGE_D(
SITENUM => SITENUM
);
END;
This procedure doesn't make much sense (at least, to me).
you are passing sitenum and never do anything with it; should it be used in where clause in cursor for loop and/or update statement?
this is a procedure, and then - in line #4 of your original code - you are calling itself (which then calls itself which calls itself etc., until Oracle stops it and returns an error)
the most obvious "solution" is to remove that statement:
p_sitenumber_range_d(Sitenum => daynumber);
but that probably won't be all, because of my first objection
Furthermore, maybe you don't need the loop at all, as the whole code can be rewritten as
create or replace procedure p_sitenumber_range_d (par_sitenum in number)
is
begin
update pm4h_db.ind_d_3102 set
ind_d_3102_029 = (select countsite
from some_table
where province = (select province
from v_sitenumber_d_province_range
where sitenum = par_sitenum
)
);
end;
It might, or might not work - there's a possibility of TOO_MANY_ROWS if select returns more than a single value. I don't know, as I don't have your tables, so - that might need to be fixed.
If you insist on the loop, then consider such a code:
create or replace procedure p_sitenumber_range_d (par_sitenum in number)
is
begin
for cur_r in (select province
from v_sitenumber_d_province_range
where sitenum = par_sitenum
)
loop
update pm4h_db.ind_d_3102 set
ind_d_3102_029 = (select countsite
from some_table
where province = cur_r.province
);
end loop;
end;
Are you aware that you've built in a recursion?
The first thing you do during the procedure is to call up the procedure itself!

how do I use if condition in cursor because our professor don't allow us use where clause in the select statement

Our question is showing all the countries that have names that are exactly 5 letter long. This is the cursor code and I want add if condition into it.
declare
cursor cursor_full is select * from country_cont;
begin
for counter in cursor_full
loop
dbms_output.put_line(counter.country|| ' ' || counter.continent);
end loop;
end;
However my professor said that you can't using where clause within the select statement and you should display all the countries and continent.
so i tried this code:
declare
country varchar(50);
cursor cursor_full is select * from country_cont;
begin
if length(country)=5 then
for counter in cursor_full
loop
dbms_output.put_line(counter.country|| ' ' || counter.continent);
end loop;
end if;
end;
the script output show that PL/SQL procedure successfully completed but nothing return in DBMS output
Hope someone can help me, I spent whole night to think about it,please!
Variable country doesn't contain any value, it is null so if condition is never true and loop is never executed. Sample data would help; meanwhile, see if this helps.
begin
for cur_r in (select * from country_cont) loop
if length(cur_r.country) > 5 then
dbms_output.put_line(cur_r.country|| ' ' || cur_r.continent);
end loop;
end;
Don't forget to set serveroutput on.

Are there alternative methods for saying 'next' in a pl/sql for loop?

So I've got a for loop that processes a list of IDs and has some fairly complex things to do. Without going into all the ugly details, basically this:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we will look for duplicate entries, so we can noop if duplicate is found
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
-- NEXT;! NOOP;! but there is no next!
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no dups found, proceeding');
END;
-- here we have code we only want to execute if there are no dupes already
IF v_dup_check IS NULL THEN
-- if not a duplicate record, proceed...
ELSE
-- reset duplicate check variable
v_dup_check := NULL;
END;
END LOOP;
END;
How I normally handle this is by selecting into a value, and then wrap the following code in an IF statement checking to make sure that duplicate check variable is NULL. But it's annoying. I just want to be able to say NEXT; or NOOP; or something. Especially since I already have to catch the NO_DATA_FOUND exception. I suppose I could write a letter to Oracle, but I'm curious how others handle this.
I could also wrap this in a function, too, but I was looking for something a little cleaner/simpler.
Oracle 11g adds a C-style "continue" loop construct to PL/SQL, which syntactically sounds like what you're looking for.
For your purposes, why not just eliminate the duplicates prior to entering the loop? This could be done by querying l_selected using a table function, and then filtering out records you don't want instead of iterating over every value. Something like...
declare
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
cursor no_dups_cur (p_selected APEX_APPLICATION_GLOBAL.VC_ARR2) is
select * from (
select selected.*,
count(*) over (partition by county_id) cnt -- analytic to find counts grouped by county_id
from table(p_selected) selected -- use table function to treat VC_ARR2 like a table
) where cnt = 1 -- remove records that have duplicate county_ids
;
begin
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
for i in no_dups_cur(l_selected) loop
null; -- do whatever to non-duplicates
end loop;
end;
Just substitute the logic for determining a "duplicate" with your own (didn't have enough info from your example to really answer that part)
Instead of catching NO_DATA_FOUND, how about SELECTing the number of matching entries into a variable, say l_count, and proceeding if this count works out to be zero? Something like the following:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
l_count INTEGER;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we will count duplicate entries, so we can noop if duplicate is found
SELECT COUNT(*) INTO l_count FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
IF l_count = 0 THEN
-- here we have code we only want to execute if there are no dupes already
-- if not a duplicate record, proceed...
END IF;
END LOOP;
END;
To count the number of rows is also possible (see Pourquoi Litytestdata) but you can also do what you want to do in the when_no_data_found exception block.
declare
l_selected apex_application_global.vc_arr2;
l_county_id org_county_accountable.count_id%type;
begin
l_selected := apex_util.string_to_table(:p4_select_lst);
for i in l_selected.first..l_selected.last loop
begin
select count_id
into l_county_id
from org_county_accountable
where organization_id = :p4_id
and county_id = v_county_id;
exception
when no_data_found then
-- here we have code we only want to execute if there are no dupes already
-- if not a duplicate record, proceed...
end;
end loop;
end;
<xmp>
<<next_loop>>
loop
...
...
if ....
then
goto next_loop;
</xmp>
This is a case where a GOTO statement might be useful. See the Oracle Documentation in the control structures to see how to do this. Also, you may want to search around here to find out how to query for the existence of a record. Running a query and waiting for an exception isn't optimal.
Another way - turn the check into a local function:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
...snip...
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
v_dup_check org_county_accountable.county_id%TYPE;
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN TRUE;
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END;
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we have code we only want to execute if there are no dupes already
IF NOT dup_exists (:P4_ID, v_county_id) THEN
-- if not a duplicate record, proceed...
END;
END LOOP;
END;
Of course, the local function could be re-written to use the count method if you prefer:
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
l_count INTEGER;
BEGIN
SELECT COUNT(*) INTO l_count
FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN (l_count > 0);
END;
Another method is to raise and handle a user-defined exception:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
duplicate_org_county EXCEPTION;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
BEGIN
-- do some data checking stuff...
-- here we will look for duplicate entries, so we can noop if duplicate is found
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
RAISE duplicate_org_county;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no dups found, proceeding');
END;
-- here we have code we only want to execute if there are no dupes already
EXCEPTION
WHEN duplicate_org_county THEN NULL;
END;
END LOOP;
END;
I wouldn't normally do this, but if there were half a dozen reasons to jump to the next record, this might be preferable to multiple nested IFs.
I know this is an oldie but I couldn't help notice that none of the answers above take into account the cursor attributes:
There are four attributes associated with cursors: ISOPEN, FOUND, NOTFOUND, and ROWCOUNT. These attributes can be accessed with the % delimiter to obtain information about the state of the cursor.
The syntax for a cursor attribute is:
cursor_name%attribute
where cursor_name is the name of the explicit cursor.
So in this case you could use ROWCOUNT (which indicates the number of rows fetched so far) for your purposes, like this:
declare
aux number(10) := 0;
CURSOR cursor_name is select * from table where something;
begin
select count(*) into aux from table where something;
FOR row IN cursor_name LOOP
IF(aux > cursor_name%ROWCOUNT) THEN 'do something is not over';
ELSE 'do something else';
END IF;
END LOOP;
end;

Resources