I'm starting with PL/SQL, this is my first Procedure and i've trouble to make it work.
i've problem (think) with cursor. If you look my code are four comments; i've tested these four Update, but only one work! and i'm sure that two SELECT work, because if i send wrong parameter in input the select don't give a result.
of the four comments, I want that works:
UPDATE partecipa
SET punti= somma
WHERE ( (nomesquadra= nomesquadr) AND
(nometorneo= nometorn));
If there are other errors (also logical) please tell me. I would like to improve.
Thank you all for the answers
my procedure:
create or replace PROCEDURE calcola_giorn (giornata IN INTEGER) IS
-- si tenga presente che in realtà giornata=idPartita
somma NUMBER;
idcal NUMBER;
nometorn VARCHAR2(100);
idformaz NUMBER;
nomesquadr VARCHAR2(100);
CURSOR formazioni_di_giornata IS
SELECT id, nomesquadra FROM formazione where idpartita= giornata;
CURSOR giocatori_di_giornata IS
SELECT votogiocatore FROM schiera WHERE idformazione= idformaz;
Begin
SELECT idcalendario
INTO idcal
FROM partita
WHERE id= giornata;
SELECT nometorneo
INTO nometorn
FROM calendario
WHERE id= idcal;
FOR tupla_formazione IN formazioni_di_giornata LOOP
somma:=0;
FETCH formazioni_di_giornata INTO idformaz, nomesquadr;
FOR tupla_giocatore IN giocatori_di_giornata LOOP
somma:= somma + tupla_giocatore.votogiocatore;
/*DON'T WORK*/-- UPDATE partecipa SET punti= 123;
END LOOP;
/*WORK*/-- UPDATE partecipa SET punti= 12;
/*DON'T WORK*/-- UPDATE partecipa SET punti= 123 WHERE ( (nomesquadra= nomesquadr) AND (nometorneo= nometorn));
/*DON'T WORK*/-- UPDATE partecipa SET punti= somma WHERE ( (nomesquadra= nomesquadr) AND (nometorneo= nometorn));
END LOOP;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001, 'An error was encountered - ' ||
SQLCODE||' -ERROR- '||SQLERRM);
END;
I recommend you rewrite your procedure:
create or replace PROCEDURE calcola_giorn (giornata IN INTEGER) IS
-- si tenga presente che in realtà giornata=idPartita
somma NUMBER;
nometorn VARCHAR2(100);
nOuter_cursor_rows_fetched NUMBER := 0;
Begin
SELECT c.NOMETORNEO
INTO nometorn
FROM PARTITA p
INNER JOIN CALENDARIO c
ON c.ID = p.IDCALENDARIO
WHERE p.ID = giornata;
FOR tupla_formazione IN (SELECT id, nomesquadra
FROM formazione
where idpartita = giornata)
LOOP
DBMS_OUTPUT.PUT_LINE('Inside outer loop, FORMAZIONE.ID=' ||
tupla_formazione.ID);
nOuter_cursor_rows_fetched := nOuter_cursor_rows_fetched + 1;
somma := 0;
FOR tupla_giocatore IN (SELECT votogiocatore
FROM schiera
WHERE idformazione = tupla_formazione.nomesquadra)
LOOP
somma := somma + tupla_giocatore.votogiocatore;
-- The following statement will set PUNTI to 123 on every row in
-- PARTECIPA - are you sure this is what you wanted to do?
UPDATE partecipa SET punti= 123;
END LOOP;
-- The following statement will set PUNTI to 12 on every row in
-- PARTECIPA - are you sure this is what you wanted to do?
UPDATE partecipa SET punti= 12;
UPDATE partecipa
SET punti = 123
WHERE nomesquadra= tuplafomazione.nomesquadra AND
nometorneo = nometorn;
UPDATE partecipa
SET punti = somma
WHERE nomesquadra = tupla_formazione.nomesquadr AND
nometorneo = nometorn;
END LOOP;
DBMS_OUTPUT.PUT_LINE('# rows fetched by outer cursor=' ||
nOuter_cursor_rows_fetched);
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001, 'Error: SQLCODE=' ||
SQLCODE||' SQLERRM='''||SQLERRM || '''');
ROLLBACK;
RAISE;
END calcola_giorn;
I believe that #Allan must be right and that the outer loop (for tupla_formazione) is never being entered. The DBMS_OUTPUT.PUT_LINE at the top of this loop is there to demonstrate this. If you don't see any of these lines printed you might need to consider looking at the values which are input to the procedure.
Share and enjoy.
A for loop that invokes a cursor automatically fetches the row once for each loop. The fetch you added inside the loop isn't allowed.
The fact that you're not seeing an ORA-01001: invalid cursor error indicates that you're never getting to the interior of the loop: the formazioni_di_giornata cursor must not be returning any rows.
As an aside, I'd strongly suggest getting rid of the exception clause you're using. All it's doing is adding garbage to the error message.
Related
I have been searching online using different solution suggestion to handle no data in this code but to no avail. how can I handle the exception if no data is found. How can I solve this problem. I am not an expert in oracle though!
DECLARE
nCheckOption INT;
no_data_found EXCEPTION;
CURSOR TYPE_cursor IS
SELECT
D_NAL_REF.TRANS
, D_NAL_REF.INJ
, D_NAL_REF.REF
FROM D_NAL_REF D_NAL_REF
WHERE D_NAL_REF.REF IN
(SELECT AG_REF.REF
FROM AG_REF A_REF
WHERE A_REF.DESCEND_REF = 10
);
BEGIN
FOR rec IN TYPE_cursor
LOOP
nCheckOption := 0;
SELECT 1
INTO nCheckOption
FROM PERSON_TYPE WHERE TRANS = rec.TRANS AND INJ = rec.INJ;
IF nCheckOption = 1 THEN
UPDATE PERSON_TYPE
SET PERSON_TYPE.TYPE = rec.REF
WHERE TRANS = rec.TRANS
AND PERSON_TYPE.INJ = rec.INJ;
END IF;
EXCEPTION
WHEN no_data_found
THEN
DBMS_OUTPUT.PUT_LINE ('Trapped the error!?');
END LOOP;
END;
/
Rewrite your code to eliminate the inner SELECT, which is the only place in your code where I can see that a NO_DATA_FOUND exception could possibly be raised:
BEGIN
FOR rec IN (SELECT d.TRANS,
d.INJ,
d.REF
FROM D_NAL_REF d
WHERE d.REF IN (SELECT a.REF
FROM AG_REF a
WHERE a.DESCEND_REF = 10) AND
(d.TRANS, d.INJ) IN (SELECT DISTINCT TRANS, INJ
FROM PERSON_TYPE))
LOOP
UPDATE PERSON_TYPE
SET TYPE = rec.REF
WHERE TRANS = rec.TRANS AND
INJ = rec.INJ;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Trapped the error!?');
END;
I think you need to find if cursor contains any record or not. If cursor is empty then it must return that error message written in exception block.
Or you want to print error message if update statement do not find any record to update.
Here is the pseudo code.
Declare
ncheckoption number := 0;
Cursor type_cursor is ....
Begin
For rec in type_cursor loop
ncheckoption := ncheckoption + 1;
Update ...
If sql%rowcount = 0 then
Dbms_output.put_line('error message, if you want here in case no record found in table to update');
-- loop will continue
-- if you want loop to break then issue exit statement here
End if;
End loop;
If ncheckoption = 0 then
Dbms_output.put_line('error message you want to print in case cursor is empty');
End if;
End;
/
Cheers!!
I am trying to use dynamic SQL to sample all the data in a schema with a pattern:
DECLARE
xsql varchar2(5000);
c NUMBER;
d NUMBER;
col_cnt INTEGER;
f BOOLEAN;
rec_tab DBMS_SQL.DESC_TAB;
col_num NUMBER;
varvar varchar2(500);
PROCEDURE print_rec(rec in DBMS_SQL.DESC_REC) IS
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE('col_type = '
|| rec.col_type);
DBMS_OUTPUT.PUT_LINE('col_maxlen = '
|| rec.col_max_len);
DBMS_OUTPUT.PUT_LINE('col_name = '
|| rec.col_name);
DBMS_OUTPUT.PUT_LINE('col_name_len = '
|| rec.col_name_len);
DBMS_OUTPUT.PUT_LINE('col_schema_name = '
|| rec.col_schema_name);
DBMS_OUTPUT.PUT_LINE('col_schema_name_len = '
|| rec.col_schema_name_len);
DBMS_OUTPUT.PUT_LINE('col_precision = '
|| rec.col_precision);
DBMS_OUTPUT.PUT_LINE('col_scale = '
|| rec.col_scale);
DBMS_OUTPUT.PUT('col_null_ok = ');
IF (rec.col_null_ok) THEN
DBMS_OUTPUT.PUT_LINE('true');
ELSE
DBMS_OUTPUT.PUT_LINE('false');
END IF;
END;
BEGIN
c := DBMS_SQL.OPEN_CURSOR;
xsql:='
WITH got_r_num AS
(
SELECT e.* -- or whatever columns you want
, ROW_NUMBER () OVER (ORDER BY dbms_random.value) AS r_num
FROM dba_tab_columns e
)
SELECT * -- or list all columns except r_num
FROM got_r_num
WHERE r_num <= 10';
DBMS_SQL.PARSE(c, xsql, DBMS_SQL.NATIVE);
d := DBMS_SQL.EXECUTE(c);
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
NULL;
-- get column values of the row
DBMS_SQL.COLUMN_VALUE(c, 2, varvar);
--dbms_output.put_line('varvar=');
--DBMS_SQL.COLUMN_VALUE(source_cursor, 2, name_var);
--DBMS_SQL.COLUMN_VALUE(source_cursor, 3, birthdate_var);
-- Bind the row into the cursor that inserts into the destination table. You
-- could alter this example to require the use of dynamic SQL by inserting an
-- if condition before the bind.
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':id_bind', id_var);
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':name_bind', name_var);
--DBMS_SQL.BIND_VARIABLE(destination_cursor, ':birthdate_bind',
--birthdate_var);
--ignore := DBMS_SQL.EXECUTE(destination_cursor);
--ELSE
-- No more rows to copy:
--EXIT;
END IF;
END LOOP;
--EXIT WHEN d != 10;
--END LOOP;
col_num := rec_tab.first;
IF (col_num IS NOT NULL) THEN
LOOP
print_rec(rec_tab(col_num));
col_num := rec_tab.next(col_num);
EXIT WHEN (col_num IS NULL);
END LOOP;
END IF;
DBMS_SQL.CLOSE_CURSOR(c);
END;
/
When I run that it gives me this error from the line with the dbms_sql.column_value call:
ORA-01007: variable not in select list
If I comment out that dbms_sql.column_value call it still errors but now with:
ORA-01002: fetch out of sequence
What am I doing wrong?
You have two problems in the code you posted. Firstly you have skipped part of the execution flow because you haven't called the DEFINE_COLUMN procedure. That is what is causing the ORA-01007 error, as the dynamic SQL processing hasn't been told about the select list columns via that call. For your current code you only need to define column 2, but assuming you will actually want to refer to the others you can define them in a loop. To treat them all as string for display you could do:
...
DBMS_SQL.PARSE(c, xsql, DBMS_SQL.NATIVE);
d := DBMS_SQL.EXECUTE(c);
DBMS_SQL.DESCRIBE_COLUMNS(c, col_cnt, rec_tab);
FOR i IN 1..col_cnt
LOOP
-- dbms_output.put_line('col_name is ' || rec_tab(i).col_name);
DBMS_SQL.DEFINE_COLUMN(c, i, varvar, 500);
END LOOP;
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
...
If you want to do anything that needs to treat the variables as the right types you could have a local variable of each type and use the data type from the rec_tab information you already have from describe_columns to use the appropriately typed variable for each column.
The second problem, which you were hitting when you commented the column_value call, is still there once that definbe issue has been fixed. Your loop doesn't ever exit, so after you fetch the last row from the cursor you do a further invalid fetch, which throws ORA-01002. You have the code to avoid that already but it's commented out:
...
LOOP
IF DBMS_SQL.FETCH_ROWS(c)>0 THEN
-- get column values of the row
DBMS_SQL.COLUMN_VALUE(c, 2, varvar);
...
ELSE
-- No more rows to copy:
EXIT;
END IF;
END LOOP;
...
With those two changes your code runs, and dumps the view structure:
PL/SQL procedure successfully completed.
col_type = 1
col_maxlen = 30
col_name = OWNER
col_name_len = 5
col_schema_name =
col_schema_name_len = 0
col_precision = 0
col_scale = 0
col_null_ok = false
col_type = 1
col_maxlen = 30
col_name = TABLE_NAME
...
To those who find this question when accessing Oracle through ODP.NET, as I did:
We started getting this error whenever we would add column to an existing table in our application. I'm not sure what all the conditions were to make it fail, but ours were:
Run a SELECT * FROM "table".
Include a ROWNUM restriction in the WHERE clause (WHERE ROWNUM < 10).
Run that through the ODP.NET dataReader.GetSchemaTable() call.
Running unrestricted queries or running queries directly on Oracle SQL Developer did not seem to cause the error.
I've hit some pretty weird stuff in the past with Oracle connection pooling, so I eventually thought that could be the problem. The solution was to restart the web service to force all the connections to be fully dropped and recreated.
The theory is that the ODP.NET connection from the connection pool still had no idea the column existed on the table, but the column was returned by the database.
I've got an Oracle Error (ORA-01008): the thing is that this error appears for the first time, with no modification of table/Schema (but there was a database upgrade).
I tried execute immediate, but I still get the same error.
SET serveroutput ON;
set define off;
--alter session set "_subquery_pruning_enabled"=false;
DECLARE
z_value NUMBER := -1;
CURSOR listedocument
IS
select to_number(2310458) as coco from dual;
BEGIN
FOR l IN listedocument
LOOP
BEGIN
dbms_output.put_line (' l.coco= ' || l.coco );
SELECT oa.myvalue
INTO z_value
FROM schema2.table2 oa
INNER JOIN
schema1.table1 ed
ON ed.coco = oa.coco
WHERE ed.coco = l.coco
;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line ('NO_DATA_FOUND');
WHEN OTHERS THEN
dbms_output.put_line ('Exception de traitement ' || SQLERRM);
END;
END LOOP;
END;
The problem seems to be here WHERE ed.coco = l.coco
I get the error : Exception de traitement ORA-01008: not all variables bound [==>modification post message]
I get the error : ORA-01008: not all variables bound
When I use alter session set "_subquery_pruning_enabled"=false; everything works, but performance is bad.
I'm using Oracle database 11g (11.2.0.4.0).
It looks like you might have hit a bug.
Take a look:
https://community.oracle.com/thread/3894065?start=30&tstart=0
There are cases where the optimizer calls kkpap to do partition
pruning at compile-time. Sometimes partition pruning is done by
running subqueries against the table. If bind variable values are
required to run these subqueries then we cannot do the pruning at
compile-time.
The fix for bug 14458214 fixed this issue for the case where the subquery was used to prune at the partition-level. However, it is
possible that we use some other method at the partition level then use
subquery pruning at the subpartition level; this case was not
addressed by the fix for 14458214.
DECLARE
z_value NUMBER := -1;
coco number;
BEGIN
BEGIN
coco:=2310458;
dbms_output.put_line (' coco= ' || coco );
SELECT oa.myvalue
INTO z_value
FROM schema2.table2 oa
INNER JOIN
schema1.table1 ed
ON ed.coco = oa.coco
WHERE ed.coco = coco
;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line ('NO_DATA_FOUND');
WHEN OTHERS THEN
dbms_output.put_line ('Exception de traitement ' || SQLERRM);
END;
Using LOOP when You have 1 variable in cursor... not a good idea.
I am wondering if I am going about this the right way.
My main issue that compiler gives is for this line
IF SELECT 1 FROM works WHERE an_employee.employee_name IN works.manager_name THEN
Error(17,8): PLS-00103: Encountered the symbol "SELECT" when expecting one of the following: ( - + case mod new not null continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date pipe
What I am trying to do is use a cursor that detects the first condition if they work in a company that is in that city. In this example in my table it will give 2 companies, and about 7 or 8 employees. Then I put that into my variable an_employee. What I am trying to do is use that employee then to see if they are a manager from my manages table which has multiple rows/tuples. Not everyone is a manager. How can I see if my employee_name is in the list of manager_name?
Do I declare another cursor for a manager and then do a nested loop?
Can I use my existing cursor and do a select query to get the list of managers from the table manages.manager_name? If I do that, how can I use it in the IF statement as my condition?
-- Give all employees that work in a company located in city X
-- a Y percent raise if they are managers and
-- a Z percent raise if they are not a manager
-- X, Y, and Z will be the three parameters for the stored procedure.
-- Build / compile a stored procedure
CREATE OR REPLACE PROCEDURE give_raises(X company.city%TYPE, Y NUMBER, Z NUMBER) IS
an_employee works.employee_name%TYPE;
-- cursor declaration
cursor Cursor1 IS
select works.employee_name
from works
where works.company_name IN (select company_name from company where city = 'London');
BEGIN
SELECT manager_name INTO managers FROM manages;
OPEN Cursor1;
LOOP
FETCH Cursor1 INTO an_employee;
EXIT WHEN Cursor1%NOTFOUND;
-- is a manager give Y percent raise
IF SELECT 1 FROM works WHERE an_employee.employee_name IN works.manager_name THEN
update works
set works.salary = works.salary + (works.salary * Y)
where works.employee_name = an_employee.employee_name;
ELSE -- is not a manager give Z percent raise
update works
set works.salary = works.salary + (works.salary * Z)
where works.employee_name = an_employee.employee_name;
END IF;
END LOOP;
CLOSE Cursor1;
END;
Also you should know. I am using Oracle, Oracle Sql Developer and the IDE.
If works on a logical condition and does not support Select statements in the condition.
You need to rework your code like this
SELECT count(*)
INTO v_value
FROM works
WHERE an_employee.employee_name = works.manager_name;
IF v_value = 1 THEN
--do some stuff
ELSE
--do some other stuff
END IF;
If i understand your problem correctly you are trying to identify whether an employee is manager or not based on thatsalary is computed. Hope below snippet helps.
CREATE OR REPLACE PROCEDURE give_raises(
X company.city%TYPE,
Y NUMBER,
Z NUMBER)
AS
managers PLS_INTEGER;
BEGIN
FOR an_employee IN
(SELECT works.employee_name
FROM works
WHERE works.company_name IN
(SELECT company_name FROM company WHERE city = 'London'
)
)
LOOP
SELECT COUNT(1)
INTO managers
FROM manages m
WHERE m.manager_name = an_employee.employee_name;
-- is a manager give Y percent raise
IF managers <> 0 THEN
UPDATE works
SET works.salary = works.salary + (works.salary * Y)
WHERE works.employee_name = an_employee.employee_name;
ELSE -- is not a manager give Z percent raise
UPDATE works
SET works.salary = works.salary + (works.salary * Z)
WHERE works.employee_name = an_employee.employee_name;
END IF;
END LOOP;
END;
Your problem is essentially the same than e.g. in a question posted yesterday. PL/SQL if statement expects a boolean expression not a query result set.
A good practice is to encapsulate the query to a function returning a suitable value.
Example:
SQL> !cat so53.sql
declare
function is_foo_1(p_foo in varchar2) return boolean is
v_exists number;
begin
select count(*)
into v_exists
from dual
where dummy = p_foo
and rownum = 1
;
return
case v_exists
when 1 then true
else false
end;
end;
function is_foo_2(p_foo in varchar2) return number is
v_exists number;
begin
select count(*)
into v_exists
from dual
where dummy = p_foo
and rownum = 1
;
return v_exists;
end;
begin
-- is_foo_1 returns a boolean value than is a valid boolean expression
if is_foo_1('X')
then
dbms_output.put_line('1:X');
end if;
if not is_foo_1('Y')
then
dbms_output.put_line('1:not Y');
end if;
-- is_foo_2 returns a number that is not a valid boolean expression (PL/SQL
-- doesn't have implicit type conversions) so one have to use an operator
-- (in this example `=`-operator) to construct an explicit boolean
-- expression
if is_foo_2('X') = 1
then
dbms_output.put_line('2:X');
end if;
if is_foo_2('Y') = 0
then
dbms_output.put_line('2:not Y');
end if;
end;
/
Example run:
SQL> #so53.sql
1:X
1:not Y
2:X
2:not Y
PL/SQL procedure successfully completed.
SQL>
I have a fetch being executed inside of a loop. If this fetch fails (no data) I would like to CONTINUE the loop to the next record from within the EXCEPTION.
Is this possible?
I'm getting a ORA-06550 & PLS-00201 identifer CONTINUE must be declared
DECLARE
v_attr char(88);
CURSOR SELECT_USERS IS
SELECT id FROM USER_TABLE
WHERE USERTYPE = 'X';
BEGIN
FOR user_rec IN SELECT_USERS LOOP
BEGIN
SELECT attr INTO v_attr
FROM ATTRIBUTE_TABLE
WHERE user_id = user_rec.id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- user does not have attribute, continue loop to next record.
CONTINUE;
END;
END LOOP;
END;
The CONTINUE statement is a new feature in 11g.
Here is a related question: 'CONTINUE' keyword in Oracle 10g PL/SQL
In the construct you have provided, you don't need a CONTINUE. Once the exception is handled, the statement after the END is performed, assuming your EXCEPTION block doesn't terminate the procedure. In other words, it will continue on to the next iteration of the user_rec loop.
You also need to SELECT INTO a variable inside your BEGIN block:
SELECT attr INTO v_attr FROM attribute_table...
Obviously you must declare v_attr as well...
How about the ole goto statement (i know, i know, but it works just fine here ;)
DECLARE
v_attr char(88);
CURSOR SELECT_USERS IS
SELECT id FROM USER_TABLE
WHERE USERTYPE = 'X';
BEGIN
FOR user_rec IN SELECT_USERS LOOP
BEGIN
SELECT attr INTO v_attr
FROM ATTRIBUTE_TABLE
WHERE user_id = user_rec.id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- user does not have attribute, continue loop to next record.
goto end_loop;
END;
<<end_loop>>
null;
END LOOP;
END;
Just put end_loop at very end of loop of course. The null can be substituted with a commit maybe or a counter increment maybe, up to you.
For this example you really should just use an outer join.
declare
begin
FOR attr_rec IN (
select attr
from USER_TABLE u
left outer join attribute_table a
on ( u.USERTYPE = 'X' and a.user_id = u.id )
) LOOP
<process records>
<if primary key of attribute_table is null
then the attribute does not exist for this user.>
END LOOP;
END;
Notice you can use WHEN exception THEN NULL the same way as you would use WHEN exception THEN continue. Example:
DECLARE
extension_already_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(extension_already_exists, -20007);
l_hidden_col_name varchar2(32);
BEGIN
FOR t IN ( SELECT table_name, cast(extension as varchar2(200)) ext
FROM all_stat_extensions
WHERE owner='{{ prev_schema }}'
and droppable='YES'
ORDER BY 1
)
LOOP
BEGIN
l_hidden_col_name := dbms_stats.create_extended_stats('{{ schema }}', t.table_name, t.ext);
EXCEPTION
WHEN extension_already_exists THEN NULL; -- ignore exception and go to next loop iteration
END;
END LOOP;
END;