UTL_FILE WITH %ROWTYPE .. ERROR - utl-file

I want to create procedure that get rows from employees record whose salary more then input number.
And then get that output to text file.
I don’t understand where i am doing wrong. Please help me out. Thanks in advance.
I have employees table as structured below.
Desc TABLE employees ;
Name Null? Type
EMPLOYEE_ID NOT NULL NUMBER(6)
FIRST_NAME VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
COMMISSION_PCT NUMBER(2,2)
MANAGER_ID NUMBER(6)
DEPARTMENT_ID NUMBER(4)
and when I want to run below script.
CREATE OR REPLACE PROCEDURE P_TEST3 ( P_SAL NUMBER) IS
TYPE EMP_TEMP IS TABLE OF employees%ROWTYPE;
V_EMP_ROW EMP_TEMP ;
V1 utl_file.file_type;
E1 utl_file.file_type;
BEGIN
V1 := utl_file.fopen('ABC','VALID.txt','W');
E1 := utl_file.fopen('ABC','ERROR.txt','W');
SELECT * BULK COLLECT INTO V_EMP_ROW FROM Employees WHERE salary > p_sal ;
FOR i IN V_EMP_ROW.FIRST .. V_EMP_ROW.LAST
LOOP
UTL_FILE.PUT_LINE (V1, V_EMP_ROW(I).EMPLOYEE_ID ||',' || V_EMP_ROW(I).FIRST_NAME ||','|| V_EMP_ROW(I).SALARY );
END LOOP;
dbms_output.put_line ('Total row inserted '||sql%rowcount || '. Please see valid.txt file ' );
EXCEPTION
WHEN OTHERS THEN
raise_application_error( -20002,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
utl_file.put_line (E1,SQLCODE || '---' || SQLERRM);
DBMS_OUTPUT.PUT_LINE ('PLEASE SEE Error.txt FILE');
utl_file.fclose(v1);
utl_file.fclose(E1);
END P_TEST3;
/
And the compiler give below error.
[Warning] ORA-24344: success with compilation error
21/42 PLS-00302: component 'EMPLOYEE_ID' must be declared
21/9 PL/SQL: Statement ignored
(1: 0): Warning: compiled but with compilation errors

The line giving you the error is 21, which appears to be:
UTL_FILE.PUT_LINE (V1, V_EMP_ROW(I).EMPLOYEE_ID ||',' || V_EMP_ROW(I).FIRST_NAME ||','|| V_EMP_ROW(I).SALARY );
I would suggest that the issue is that either no rows are being returned in your BULK SELECT statement, or they aren't in the format you expect.
Try putting in something like:
dbms_output.put_line('Record is: '|| V_EMPROW(i));
as the first line in your loop.
Or, maybe do a record count of the bulk select results prior to the loop:
dbms_output.put_line('Record count is: '|| V_EMPROW.COUNT);

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;

SQL Procedure compiling errors

we have two tables : Account and Client
CREATE TABLE Client ( NumClient NUMBER(3) NOT NULL PRIMARY KEY,
ClientName VARCHAR (25) NOT NULL,
City VARCHAR (25) NOT NULL
);
CREATE TABLE Compte ( NumCompte NUMBER(1) NOT NULL PRIMARY KEY,
NumClient NUMBER(3) NOT NULL REFERENCES Client(NumClient),
DateOpening DATE NOT NULL,
balance FLOAT ,
PMVR NUMBER DEFAULT 0
);
procedure is as follows:
OpenAccount(NumCli in number, Amount in number)
This procedure creates a new account for a customer (NumCli) with a first balance (Amount):
NumCaccount is automatically assigned by a sequence;
DateOpen is the system date;
Amount > 0;
PMVR is initialized to 0;
If the customer does not exist, there is an error.
I have a sequence called :
CREATE SEQUENCE seqClient START WITH 101 INCREMENT BY 1;
CREATE SEQUENCE seqAccount START WITH 1 INCREMENT BY 1;
Here is text of procedure
CREATE OR REPLACE PROCEDURE OpenAccount(NumCli IN NUMBER, Amount in NUMBER)
IS
non-existent_client EXCEPTION;
PRAGMA EXCEPTION_INIT (non-existent_client, -2291);
BEGIN
IF (Amount < 0)
THEN
RAISE_APPLICATION_ERROR (-20002,'the amount must be greater than 0');
ELSE
INSERT INTO Account (AccountNumber,
ClientNumber,
DateOpening date,
Balance,
PMVR)
VALUES (seqCount.NEXTVAL,
NumCli,
TO_DATE (sysdate,'DD.MM.YY'),
Amount,
0);
END IF;
EXCEPTION
WHEN
non-existent_customer
THEN
DBMS_OUTPUT.PUT_LINE (
Client No' ||| TO_CHAR (NumCli) ||| ' non-existent');
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (
Oracle error:' |||| SQLCODE ||| '; Oracle message: ||||| SQLERRM);
END;
When I run it like this
execute OpenAccount(101,1600);
I get this error :
9/9 PL/SQL: SQL Statement ignored
10/81 PL/SQL: ORA-00984: Column not permissible here
Translated with www.DeepL.com/Translator (free version)
A few objections:
this doesn't seem to be the whole code. What is supposed to raise non_existent_client exception?
where do values you're inserting come from?
what is the new function? In Oracle, we use sysdate
you do love pipes, that's obvious, but - don't use them that much (hint: their usage in dbms_output call)
spaces aren't allowed while naming table columns (insert statement)
minus shouldn't be used as word separator (exception name)
when others is useless; I suggest you remove it. Or, if you insist, raise immediately after dbms_output
This looks better; will it actually compile, no idea as I don't have your tables.
CREATE OR REPLACE PROCEDURE OpenAccount(NumCli IN NUMBER, Amount in NUMBER)
IS
non_existent_client EXCEPTION;
PRAGMA EXCEPTION_INIT (non_existent_client, -2291);
BEGIN
IF (Amount < 0)
THEN
RAISE_APPLICATION_ERROR (-20002, 'the amount must be greater than 0');
ELSE
INSERT INTO Account (Account_Number,
Client_Number,
OpeningDate,
Balance,
PRM)
VALUES (seqCompte.NEXTVAL,
NumCli,
TO_DATE (NOW (), 'DD.MM.YY'),
Amount,
PMVR);
END IF;
EXCEPTION
WHEN non_existent_client
THEN
DBMS_OUTPUT.PUT_LINE (
'Client No' || TO_CHAR (NumCli) || ' non-existent');
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (
'Oracle error:' || SQLCODE || '; Oracle message: ' || SQLERRM);
END;

How to get rid of compilation error in PL/SQL procedure?

Being new to PL/SQL, I am unable to understand how to manage user-defined exceptions in procedures. My code is giving the Warning 'Procedure created with compilation errors'.
CREATE OR REPLACE PROCEDURE raise_salary
(eid IN employees.e_id%TYPE:=&emp_id,
raise IN employees.salary:=&salary_raise)
IS
cnt INTEGER;
salary employees.salary%TYPE;
BEGIN
SELECT count(*) INTO cnt FROM employees WHERE e_id=eid;
IF cnt=0 THEN
RAISE INVALID_ID;
ELSE
SELECT salary INTO sal FROM employees WHERE e_id=eid;
IF sal IS NULL THEN
RAISE NULL_VALUE;
ELSE
UPDATE employees SET salary=salary+raise WHERE e_id=eid;
dbms_output.put_line('Salary raised!');
END IF;
END IF;
EXCEPTION
WHEN INVALID_ID THEN
dbms_output.put_line('User ID does not exist!');
WHEN NULL_VALUE THEN
dbms_output.put_line('Salary is null in table!');
WHEN others THEN
dbms_output.put_line('Error!');
END;
/
An example:
SQL> CREATE OR REPLACE PROCEDURE testException
2 IS
3 BEGIN
4 raise INVALID_ID;
5 EXCEPTION
6 WHEN INVALID_ID THEN
7 dbms_output.put_line('Invalid ID');
8 END;
9 /
Warning: Procedure created with compilation errors.
A way to know the errors:
SQL> sho err
Errors for PROCEDURE TESTEXCEPTION:
LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
4/5 PL/SQL: Statement ignored
4/11 PLS-00201: identifier 'INVALID_ID' must be declared
6/10 PLS-00201: identifier 'INVALID_ID' must be declared
You need to declare the exceptions you use:
SQL> CREATE OR REPLACE PROCEDURE testException
2 IS
3 INVALID_ID exception;
4 BEGIN
5 raise INVALID_ID;
6 EXCEPTION
7 WHEN INVALID_ID THEN
8 dbms_output.put_line('Invalid ID');
9 END;
10 /
Procedure created.
In this DEMO you will see all 3 situation that this PROCEDURE should handle:
Here is the correct procedure:
CREATE OR REPLACE PROCEDURE raise_salary
(eid IN employees.e_id%TYPE,
raise IN employees.salary%type)
IS
cnt INTEGER;
sal employees.salary%TYPE;
INVALID_ID exception;
NULL_VALUE exception;
BEGIN
SELECT count(*)
INTO cnt
FROM employees
WHERE e_id=eid;
IF cnt=0 THEN
RAISE INVALID_ID;
ELSE
SELECT salary
INTO sal
FROM employees
WHERE e_id=eid;
IF sal IS NULL THEN
RAISE NULL_VALUE;
ELSE
UPDATE employees
SET salary = (SAL + raise)
WHERE e_id = eid;
dbms_output.put_line('Salary raised!');
END IF;
END IF;
exception
WHEN INVALID_ID THEN
dbms_output.put_line('User ID does not exist!');
WHEN NULL_VALUE THEN
dbms_output.put_line('Salary is null in table!');
WHEN others THEN
dbms_output.put_line('Error!');
END;
/
You had more than one errors and one was nicely explained by #Aleksej in his answer VOTE UP.
You also have a line:
SELECT salary INTO sal FROM employees WHERE e_id=eid;
But you have not declared sal.
Hope this helps...

Assigning multiple fields in a loop using execute immediate

I am using PLPDF's libraries to create spreadsheets for various files - I am trying to get a procedure written to take the values of each field and one-by-one insert them into the spreadsheet. This operation can include many different tables going into many different spreadsheets, so just doing an export is not going to cut it.
This example is has two cursors created from tables - the USER_TAB_COLUMNS to select the column names, and the actual view query to pull the data. I have one loop to go through the data record by record, and second to go field-by-field within the record.
Before doing the actual writing to the spreadsheet blob, I'm simply running the dbms_output.putline to make sure that I am getting the data needed.
declare
q_str varchar2(100);
this_vc varchar2(3000);
cursor diet_table is
select * from vcars_diet where nhp_id = 8573;
cursor diet_stru is
select 'begin :1 := i.' || column_name || '; end;' line_o_code from user_tab_columns where table_name = 'VCARS_DIET';
begin
for i in diet_table loop
DBMS_OUTPUT.PUT_LINE ('--------------------------------------------------------');
for h in diet_stru loop
DBMS_OUTPUT.PUT_LINE ('Varchar Value for i: "' || h.line_o_code || '"');
EXECUTE IMMEDIATE (h.line_o_code) USING out this_vc;
DBMS_OUTPUT.PUT_LINE ('Varchar Value for i.' || h.line_o_code || ' is: '||this_vc);
end loop;
end loop;
end;
The fields in the diet table are:
NHP_ID
DATE_TIME
DIET_NO
FORM_NAME
DATA_TYPE
The results are:
ORA-06550: line 1, column 13:
PLS-00201: identifier 'I.NHP_ID' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
ORA-06512: at line 33
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I have copied this PL/SQL code from Connor McDonald's solution from this AskTom article.
It uses type dynamic SQL to parse any SQL query and converts the column names as well as values into a collection. I've used a sample query on employees table in HR schema. Replace it with your query.
set serverout on size 999999
set verify off
declare
p_query varchar2(32767) :=
q'{select * from employees
where rownum = 1
}';-- Here you put your query
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_descTbl dbms_sql.desc_tab;
l_colCnt number;
n number := 0;
procedure p(msg varchar2) is
l varchar2(4000) := msg;
begin
while length(l) > 0 loop
dbms_output.put_line(substr(l,1,80));
l := substr(l,81);
end loop;
end;
begin
execute immediate
'alter session set nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';
dbms_sql.parse( l_theCursor, p_query, dbms_sql.native );
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
for i in 1 .. l_colCnt loop
dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);
end loop;
l_status := dbms_sql.execute(l_theCursor);
while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
p( 'Value for '|| l_descTbl(i).col_name
|| ' is: ' ||
l_columnValue );
end loop;
dbms_output.put_line( '-----------------' );
n := n + 1;
end loop;
if n = 0 then
dbms_output.put_line( chr(10)||'No data found '||chr(10) );
end if;
end;
/
This gives the output:
Value for EMPLOYEE_ID is: 198
Value for FIRST_NAME is: Donald
Value for LAST_NAME is: OConnell
Value for EMAIL is: DOCONNEL
Value for PHONE_NUMBER is: 650.507.9833
Value for HIRE_DATE is: 21-jun-2007 00:00:00
Value for JOB_ID is: SH_CLERK
Value for SALARY is: 2600
Value for COMMISSION_PCT is:
Value for MANAGER_ID is: 124
Value for DEPARTMENT_ID is: 50
-----------------
PL/SQL procedure successfully completed.

Implicit cursor indicates that it updates all rows in the table for a given identifier

I have a simple PL/SQL procedure that increases the salary of an employee in the EMP table of the SCOTT schema. This receives the employee number per parameter and increment. The UPDATE statement that performs the update does not filter by that identifier and when accessing the ROWCOUNT of the cursor indicates that all the rows in the table were updated.
If this update I do from SQL Plus. It only updates a row.
CREATE OR REPLACE PROCEDURE INCREASE_SALARY(
empno EMP.EMPNO%TYPE,
incre NUMBER
)
AUTHID DEFINER
AS PRAGMA AUTONOMOUS_TRANSACTION;
INCREMENT_MUST_BE_GREATER_THAN_ZERO EXCEPTION;
BEGIN
IF incre = 0 THEN
RAISE INCREMENT_MUST_BE_GREATER_THAN_ZERO;
END IF;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE TO UPDATE: ' || empno);
UPDATE EMP
SET SAL = SAL + (SAL * incre / 100)
WHERE EMPNO = empno;
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQL%ROWCOUNT)||' rows affected.');
IF SQL%ROWCOUNT > 0 THEN
INSERT INTO TABLA_LOG VALUES(USER, SYSDATE);
DBMS_OUTPUT.PUT_LINE('SALARY UPDATED SUCCESSFULLY');
ELSE
DBMS_OUTPUT.PUT_LINE('NO EMPLOYEE FOUND FOR THAT ID');
END IF;
COMMIT WORK;
EXCEPTION
WHEN INCREMENT_MUST_BE_GREATER_THAN_ZERO THEN
DBMS_OUTPUT.PUT_LINE('THE INCREMENT PERCENTAGE MUST BE GREATER THAN 0');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR : ' || SQLCODE || 'MENSAJE: ' || SQLERRM);
END;
/
When executing the procedure with the EMPNO 7900 (JAMES)
SET SERVEROUTPUT ON;
EXEC INCREASE_SALARY(7900, 20);
I get the following output:
EMPLOYEE TO UPDATE: 7900
13 rows affected.
SALARY UPDATED SUCCESSFULLY
Procedimiento PL/SQL terminado correctamente.
Does anyone know I'm doing wrong? Thanks in advance.
select count(1) from EMP WHERE EMPNO = 7900;
Return 1.
I'm using Oracle Enterprise 12c. The procedure compiles. The identifier error too long does not appear.
Your problem is empno = empno. These are interpreted as the column name.
You should try to give your arguments and local variables different names:
CREATE OR REPLACE PROCEDURE INCREASE_SALARY (
in_empno EMP.EMPNO%TYPE,
iin_ncre NUMBER
)
AUTHID DEFINER
AS PRAGMA AUTONOMOUS_TRANSACTION;
INCREMENT_MUST_BE_GREATER_THAN_ZERO EXCEPTION;
BEGIN
IF in_incre = 0 THEN
RAISE INCREMENT_MUST_BE_GREATER_THAN_ZERO;
END IF;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE TO UPDATE: ' || in_empno);
UPDATE EMP
SET SAL = SAL + (SAL * in_incre / 100)
WHERE EMPNO = in_empno;
. . .