SQL Procedure compiling errors - sql

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;

Related

Error when calling PL/SQL function

I have created a PL/SQL function that is supposed to be adding a given number of records to a table with the help of new_dept_new sequence that I've created earlier. The function compiled successfully. But I'm getting an error when calling the function:
BEGIN
dbms_output.put_line(myfunction3(3, 'OPERATIONS', 'NEW YORK'));
END;
Error report -
ORA-06503: PL/SQL: Function returned without value
The function:
CREATE OR REPLACE FUNCTION myfunction3(
records_count_in IN NUMBER, dept_name_in IN dept.dname%TYPE, loc_name_in IN dept.loc%TYPE)
RETURN INTERVAL DAY TO SECOND
IS
thestart TIMESTAMP := CURRENT_TIMESTAMP;
stopwatch INTERVAL DAY TO SECOND;
records_count NUMBER := records_count_in;
dept_name dept.dname%TYPE := dept_name_in;
loc_name dept.loc%TYPE := loc_name_in;
BEGIN
FOR i IN 1..records_count LOOP
INSERT INTO dept VALUES(new_dept_new.nextval, dept_name || TO_CHAR(new_dept_new.currval), loc_name || TO_CHAR(new_dept_new.currval));
COMMIT;
END LOOP;
stopwatch := CURRENT_TIMESTAMP - thestart;
DBMS_OUTPUT.PUT_LINE('Time for processing function: ');
RETURN stopwatch;
EXCEPTION WHEN OTHERS
THEN dbms_output.put_line('ERROR Processing Request for Department: ' || dept_name_in);
END;
Why is the function returned without value?
EDIT:
After receiving feedback from the comments I've edited the function and now the values are inserted into the table and the timestamp value is returned. I also want an application error to be raised if 0 rows were inserted.
So I did the following:
CREATE OR REPLACE FUNCTION myfunction3(
records_count_in IN NUMBER, dept_name_in IN dept.dname%TYPE, loc_name_in IN dept.loc%TYPE)
RETURN INTERVAL DAY TO SECOND
IS
thestart TIMESTAMP := CURRENT_TIMESTAMP;
stopwatch INTERVAL DAY TO SECOND;
records_count NUMBER := records_count_in;
dept_name dept.dname%TYPE := dept_name_in;
loc_name dept.loc%TYPE := loc_name_in;
BEGIN
FOR i IN 1..records_count LOOP
INSERT INTO dept (deptno, dname, loc)
VALUES(new_dept_new.nextval, dept_name || TO_CHAR(new_dept_new.currval), loc_name || TO_CHAR(new_dept_new.currval));
IF SQL%ROWCOUNT = 0 THEN
raise_application_error (-20001, 'ERROR Processing Request for Department: ' || dept_name_in);
END IF;
END LOOP;
stopwatch := CURRENT_TIMESTAMP - thestart;
DBMS_OUTPUT.PUT_LINE('Time for processing function: ');
RETURN stopwatch;
END;
However if I'm calling the function like this and zero records are inserted, I'm not getting my custom error message.
BEGIN dbms_output.put_line(myfunction3(0, 'DEV_OPS', 'NEW YORK')); END;
How can I fix this?
Nothing seems to be wrong with your functionexcept for the exception block.I was able to simulate the scenario with the table and column names you used and it worked perfectly fine. If there are other issues like missing table,sequence it would not have compiled for you. So, the only reason for it to fail is your insert statement. It could be either constraint violation, length of value exceeds the column length limit etc.
You can make a slight change in your exception block by adding a raise statement.This will help you to identify the actual error in your calling program.
EXCEPTION WHEN OTHERS
THEN dbms_output.put_line('ERROR Processing Request for Department: ' || dept_name_in);
RAISE;
Additionally, Never run insert statements like
INSERT INTO dept VALUES (...)
It is not a good coding practice and difficult to know which order it is going to insert especially when there are many columns.
Always mention the columns explicitly
INSERT INTO dept ( dept_id,dname,loc) VALUES (..,..,..);
There is some exception in your function that causes your function to hit the exception block. There is no return statement in your exception block and hence the error. Add a RAISE statement to see what's causing the exception.

Trigger that restricts by date

Im trying to create a trigger that restricts the amount a reader can read in a given month.
CREATE OR REPLACE trigger Readings_Limit
Before update or insert on reading
for each row declare
readingcount integer;
max_read integer := 5;
Begin
Select count(*) into readingcount
from (select *
from Reading
where to_char(DateRead, 'YYYY-MM') = to_char(DateRead, 'YYYY-MM'))
where employeeid = :new.employeeid;
if :old.employeeid = :new.employeeid then
return;
else
if readingcount >= max_read then
raise_application_error (-20000, 'An Employee can only read 5 per month');
end if;
end if;
end;
This restricts the reader to 5 max in total no matter the month, i can't seem to get it to be 5 max each month. any ideas greatly appreciated!
Try to rewrite your trigger like this:
CREATE OR REPLACE trigger Readings_Limit
Before update or insert on reading
for each row
declare
readingcount integer;
max_read integer := 5;
Begin
Select count(*) into readingcount
from Reading
where DateRead between trunc(sysdate,'MM') and last_day(sysdate)
and employeeid = :new.employeeid;
if :old.employeeid = :new.employeeid then
return;
else
if readingcount >= max_read then
raise_application_error (-20000, 'An Employee can only read 5 per month');
end if;
end if;
end;
You add actual month into yout select and you avoid unnecessary date conversion.
I don't understand the condition
if :old.employeeid = :new.employeeid then
Does it mean, the trigger should not fire on updates? In this case it is better to make trigger only for insert or use clause if inserting then...
In order to properly create this validation using a trigger a procedure should be created to obtain user-specified locks so the validation can be correctly serialized in a multi-user environment.
PROCEDURE request_lock
(p_lockname IN VARCHAR2
,p_lockmode IN INTEGER DEFAULT dbms_lock.x_mode
,p_timeout IN INTEGER DEFAULT 60
,p_release_on_commit IN BOOLEAN DEFAULT TRUE
,p_expiration_secs IN INTEGER DEFAULT 600)
IS
-- dbms_lock.allocate_unique issues implicit commit, so place in its own
-- transaction so it does not affect the caller
PRAGMA AUTONOMOUS_TRANSACTION;
l_lockhandle VARCHAR2(128);
l_return NUMBER;
BEGIN
dbms_lock.allocate_unique
(lockname => p_lockname
,lockhandle => p_lockhandle
,expiration_secs => p_expiration_secs);
l_return := dbms_lock.request
(lockhandle => l_lockhandle
,lockmode => p_lockmode
,timeout => p_timeout
,release_on_commit => p_release_on_commit);
IF (l_return not in (0,4)) THEN
raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return);
END IF;
-- Must COMMIT an autonomous transaction
COMMIT;
END request_lock;
To have the least impact on scalability the serialization should be done at the finest level, which for this constraint is per employeeid and month. Types may be used in order to create variables to store this information for each row before the constraint is checked after the statement has completed. These types can be either defined in the database or (from Oracle 12c) in package specifications.
CREATE OR REPLACE TYPE reading_rec
AS OBJECT
(employeeid NUMBER(10) -- Note should match the datatype of reading.employeeid
,dateread DATE);
CREATE OR REPLACE TYPE readings_tbl
AS TABLE OF reading_rec;
The procedure and types can then be used in a compound trigger (assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions)
CREATE OR REPLACE TRIGGER too_many_readings
FOR INSERT OR UPDATE ON reading
COMPOUND TRIGGER
-- Table to hold identifiers of inserted/updated readings
g_readings readings_tbl;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal readings table
g_readings := readings_tbl();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the inserted/updated readings
IF ( INSERTING
OR :new.employeeid <> :old.employeeid
OR :new.dateread <> :old.dateread)
THEN
g_readings.EXTEND;
g_readings(g_readings.LAST) := reading_rec(:new.employeeid, :new.dateread);
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_readings
IS
SELECT DISTINCT
employeeid
,trunc(dateread,'MM') monthread
FROM TABLE(g_readings)
ORDER BY employeeid
,trunc(dateread,'MM');
CURSOR csr_constraint_violations
(p_employeeid reading.employeeid%TYPE
,p_monthread reading.dateread%TYPE)
IS
SELECT count(*) readings
FROM reading rdg
WHERE rdg.employeeid = p_employeeid
AND trunc(rdg.dateread, 'MM') = p_monthread
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated readings there exists more than
-- 5 readings for the same employee in the same month. Serialise the
-- constraint for each employeeid so concurrent transactions do not
-- affect each other
FOR r_reading IN csr_readings LOOP
request_lock('TOO_MANY_READINGS_'
|| r_reading.employeeid
|| '_' || to_char(r_reading.monthread, 'YYYYMM'));
OPEN csr_constraint_violations(r_reading.employeeid, r_reading.monthread);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Employee ' || r_reading.employeeid
|| ' now has ' || r_constraint_violation.readings
|| ' in ' || to_char(r_reading.monthread, 'FMMonth YYYY'));
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
You need to set the month you are looking at, so if you are considering the current month, make the inner query read like this:
( select * from Reading
where to_char(DateRead,'YYYY-MM') = to_char(DateRead,'YYYY-MM')
and to_char(sysdate,'YYYY-MM') = to_char(DateRead,'YYYY-MM'))
that way it will always compare to the current month and should move as your date moves.

Oracle Procedure - Return all rows

So I have several tables and I've created different roles to go with different users so that each can access a portion of the tables.
Right now, whenever I try to SELECT * FROM yaser.enrol; with a coordinator who is meant to see everything, I get the error numeric or value error: character to number conversion error which points to the lines where i'm querying the employee_no to determine the employee role.
Theres 4 overall types of users: student, tutor, lecturer, coordinator.
EDIT ** Added all code.
-- Create policy function to be called when ‘ENROL’ table is accessed under user Yaser.
create or replace function f_policy_enrol (schema in varchar2, tab in varchar2)
return varchar2
as
v_emp_no varchar2(10);
v_student_no varchar2(10);
v_tutor_emp_no varchar2(10);
v_lecturer_emp_no varchar2(10);
v_coord_emp_no varchar2(10);
is_tutor number:=0;
is_lecturer number:=0;
is_coordinator number:=0;
is_student number:=0;
is_employee number:=0;
v_program_code varchar2(10);
v_user varchar2(100);
out_string varchar2(400) default '1=2 ';
-- The return value will be out_string. '1=2' means 'Nothing to access'.
begin
-- get session user
v_user := lower(sys_context('userenv','session_user'));
-- Is the user a student?
begin
SELECT student_no INTO v_student_no FROM student WHERE lower(student_no) = v_user;
is_student:=1;
exception
when no_data_found then
v_student_no := 0;
end;
-- Is the user an employee?
begin
SELECT emp_no INTO v_emp_no FROM employee WHERE lower(emp_no) = v_user;
is_employee:=1;
exception
when no_data_found then
v_emp_no := 0;
end;
-- Query the employee number to determine role.
-- If Tutor.
SELECT MAX(tutor_emp_no) INTO v_tutor_emp_no FROM tutorial WHERE lower(tutor_emp_no) = v_user;
-- If Lecturer.
SELECT MAX(course_coord_emp_no) INTO v_lecturer_emp_no FROM course WHERE lower(course_coord_emp_no) = v_user;
-- If Coordinator.
SELECT MAX(prog_coord_emp_no) into v_coord_emp_no FROM program WHERE lower(prog_coord_emp_no) = v_user;
-- Get role of the employee if the user is an employee.
if v_emp_no != 0 and v_tutor_emp_no is NOT NULL then
-- Employee is a Tutor.
is_tutor := 1;
elsif v_emp_no != 0 and v_lecturer_emp_no is NOT NULL then
-- Employee is Lecturer.
is_lecturer := 1;
elsif v_emp_no != 0 and v_coord_emp_no is NOT NULL then
-- Employee is Coordinator.
is_coordinator := 1;
end if;
-- Create the string to be used as the WHERE clause.
if is_student = 1 then
-- Students are allowed to see their orders only.
out_string := out_string||'or student_no = '''||v_student_no||''' ';
end if;
if is_tutor = 1 then
-- Tutors are allowed to see enrolments of students that they tutor.
---- out_string := out_string||'or student_no in (select student_no from tutorial where tutor_emp_no = '||v_tutor_emp_no||') ';
---- NOT WORKING.
out_string := out_string||'or student_no in (select student_no from tutorial where lower(tutor_emp_no) = v_tutor_emp_no) ';
end if;
if is_coordinator = 1 then
-- The coordinator is allowed to see all records in ENROL (WHERE 1=1 or anything) means all rows.
out_string := out_string||'or 1=1 ';
end if;
return out_string;
end;
/
These are the tables i'm referencing:
CREATE TABLE course
(
course_code varchar(10),
course_title varchar(50),
course_coord_emp_no varchar(10),
primary key (course_code)
);
And - all employees:
CREATE TABLE employee
(
emp_no varchar(10),
name varchar(100)
);
All other tables are basically the same - VARCHARS
Any help would be good! :)
Yaser
The question has evolved a lot and much of this answer was about a missing end if that wasn't relevant to your actual code. To go actually go back to your original question, using or 1=1 as a catch-all is OK even though the other branch of the if if comparing strings - it makes no difference at all. If you really did want to compare strings you can do the same thing:
'or ''x''=''x'' '
... or
'or v_user=v_user '
But you can't compare empty strings as you seem to show in a comment. An empty string, '', is the same as null in Oracle , and you can't equate anything to null, even itself. (The previous check would fail if v_user was null, for the same reason). So another possibility would be:
'or null is null '
Note that the comparator here is is, not =.
None of which addresses why you get the VPD error, as all of those are equivalent really - they all always evaluate to true and it doesn't matter which you use. Anything that effectively ends up as or true would work exactly the same; just a shame that Oracle SQL doesn't understand booleans like that. The datatypes being compared in any other clauses are irrelevant to this clause.
You need to see what the function is actually returning in both cases, verify it's what you expect, and verify that it works as a restriction when you query the table the VPD is against directly.

ORA-06502: PL/SQL: numeric or value error: number precision too large

I'm trying to run the following insert command in Oracle SQL Developer:
insert into work_comp_rates (company_id, work_comp_rt)
values ('101', 0.11);
Which gives me this error: "ORA-06502: PL/SQL: numeric or value error: number precision too large"
There is a trigger attached:
create or replace
TRIGGER APPS.work_codes_trig
before insert or update ON APPS.WORK_COMP_RATES for each row
begin
if inserting then
if :NEW.RID is null then
:NEW.RID := it_api.gen_pk;
end if;
:NEW.CREATED_ON := sysdate;
end if;
if updating then
:NEW.MODIFIED_ON := sysdate;
end if;
end;
If I replace
:NEW.RID := it_api.gen_pk;
with
:NEW.RID := 599;
the insert statement works.
IT_API Body:
create or replace
package body it_api
as
-- generates and returns unique number used for primary key values
function gen_pk
return number
is
l_pk number := 0;
begin
for c1 in (
select to_number(sys_guid(),'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') pk
from dual )
loop
l_pk := c1.pk;
exit;
end loop;
return l_pk;
end gen_pk;
end it_api;
I don't know Oracle very well and that script was written by somebody else. So any help is appreciated!
I created a sequence rate_seq that increments values by 1 and replaced
:NEW.RID := it_api.gen_pk;
with
select rate_seq.nextval
into :new.rid
from dual;
That fixed the problem.

PLSQL -- calling function to calculate with char(1) and number variables

I'm just now teaching myself SQL and PLSQL and have haven't had too much trouble debugging the regular SQL, but I'm trying to call a PLSQL function that is throwing errors that I can't figure out from the Oracle docs and other online resources. I'm using Oracle SQL-Developer.
I've tried these but they are not helpful in this case:
How to return a record from an existing table from an Oracle PL/SQL function?
Writing a function in plsql
Oracle PLSQL function call into a second function
Can't seem to subract two numbers in a PLSQL function
I've tried writing the same function 2 different ways for practice:
Create Function Calc_Tax
(Quantity IN Number,Unit_Price IN Number,Taxable IN VarChar2)
Return Number IS amt Number;
Declare
price := Quantity * Unit_Price;
Begin
IF Taxable = 'Y' THEN
amt := price + 0.06 * price;
ELSE
amt := price;
END IF;
Return amt;
End;
-- or --
Create Or Replace Function Calc_Tax
(Quantity IN Number,Unit_Price IN Number,tax IN Sale_Item.Taxable%TYPE)
Return Number IS amt Number;
Declare
price := Quantity * Unit_Price;
Begin
IF tax = 'Y' THEN
amt := price + 0.06 * price;
ELSE
amt := price;
END IF;
Return amt;
End;
I had some trouble declaring the 'tax' parameter as a varchar2(1) so I left it just varchar2 which the docs seem to say is default for 1 space (if I read that right). Of course, I'd rather do it properly and declare the size of the varchar explicitly.
This is how I'm calling it:
DECLARE
g_last Guest.First_Name%type := 'Richard';
g_last Guest.Last_Name%type := 'Wharton';
g_id Guest.Guest_ID%type;
s_g_id Stay.Guest_ID%type;
sho_id Stay.Hotel_ID%type;
h_id Hotel.Hotel_ID%type;
h_name Hotel.Hotel_Name%type;
i_id Sale_Item.Item_ID%type;
i_name Sale_Item.Item_Name%type;
i_tax Sale_Item.Taxable%type;
cs_id Charge.Stay_ID%type;
c_date Charge.Trans_Date%type;
c_quant Charge.Quantity%type;
c_uprice Charge.Unit_Price%type;
BEGIN
SELECT H.Hotel_ID, H.Hotel_Name,C.Item_ID, Item_Name, C.Trans_Date, C.Quantity,
C.Unit_Price,Taxable INTO h_id,h_name,i_id,i_name,c_date,c_quant,c_uprice
FROM Hotel H,Charge C Where cs_id = (Select Stay_ID From Stay Where Guest_ID =
(Select Guest_ID From Guest Where First_Name='Richard' And Last_Name='Wharton'));
--WHERE id = c_id;
dbms_output.put_line
('Guest ' ||g_first || g_last || ' bought ' || i_name || ' with tax ' || Calc_Tax
(c_quant,c_uprice,i_tax));
END;
And it's throwing errors:
Error(18,1): PLS-00103: Encountered the symbol "DECLARE"
Error(43,4): PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following: ( begin case declare end exception exit for goto if loop mod null pragma raise return select update while with <an identifier> <a double-quoted delimited-identifier> <a bind variable> << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge
Here's the schema:
I'm just learning now, so I'm sure that any junior SQL programmers can quickly show me where I'm going wrong.
You don't use DECLARE inside a procedure or function -- use the AS keyword instead to indicate the variable declaration "block."
You also cannot put a constraint on parameters (e.g. you can have VARCHAR2 but not VARCHAR2(10) -- See Oracle docs
You also shouldn't have a semi-colon following your return statement.
Create Function Calc_Tax (Quantity IN Number, Unit_Price IN Number, Taxable IN VarChar2)
Return Number
AS
My_Variable VARCHAR2(2000);
BEGIN
-- code
END Calc_Tax;