I'm new to PL/SQL and I've been struggling with this problem waay too long so I thought I'll ask here:
What I want to achieve:
I want to insert a new employee into my 'emp' table using pl/sql
blocks.
I want the data to be input from the keyboard.
Then check if the data is correct using exceptions.
After that I want the user to confirm it writing 'yes' or 'no'.
What doesn't work:
Exceptions are not being raised even though I input invalid data.
As it is now it asks the user to confirm at the same time as asking for the data. I want it to ask after inputing and after checking the
correctness
My code:
DECLARE
empno NUMBER;
ename Varchar2(30);
job Varchar2(30);
mgr NUMBER;
hiredate DATE;
sal NUMBER;
comm NUMBER;
deptno NUMBER;
confirm VARCHAR2(3);
negative_number EXCEPTION;
invalid_date EXCEPTION;
BEGIN
INSERT INTO emp
VALUES (&empno, '&ename', '&job', &mgr, '&hiredate', &sal, &comm, &deptno);
IF empno < 0 OR mgr < 0 OR sal < 0 OR comm < 0 OR deptno < 0 THEN
RAISE negative_number;
ELSIF hiredate > SYSDATE THEN
RAISE invalid_date;
END IF;
IF '&confirm' = 'yes' THEN commit;
ELSE rollback;
END IF;
EXCEPTION
WHEN negative_number THEN
dbms_output.put_line('number cannot be negative');
WHEN invalid_date THEN
dbms_output.put_line('cannot be future date');
END;
Just a little modification to your present code:
DECLARE
empno NUMBER := &empno;
ename Varchar2(30) := '&ename';
job Varchar2(30) := '&job';
mgr NUMBER := &mgr;
hiredate DATE := TO_DATE('&hiredate', 'mm/dd/yyyy');
sal NUMBER := &sal;
comm NUMBER := &comm;
deptno NUMBER := &deptno;
confirm VARCHAR2(3);
BEGIN
INSERT INTO emp
VALUES (empno, ename, job, mgr, hiredate, sal, comm, deptno);
IF empno < 0 OR mgr < 0 OR sal < 0 OR comm < 0 OR deptno < 0 THEN
RAISE_APPLICATION_ERROR ('-20001', 'Number cannot be negative!');
ELSIF hiredate > SYSDATE THEN
RAISE_APPLICATION_ERROR ('-20002', 'Cannot be future date!');
END IF;
confirm := '&confirm';
IF lower(confirm) IN ('yes', 'y') THEN
commit;
ELSE
rollback;
END IF;
END;
The last part - confirm is not possible using pure PL/SQL though. What the code does is get the value of confirm at the beginning of the execution itself, along with other variables. What you should do for this is build a front-end that would control the flow of your program before issuing a DML.
DECLARE
empno NUMBER := 10;
ename Varchar2(30) := 'sinha';
job Varchar2(30) := 'ITTTT';
mgr NUMBER := 100;
hiredate DATE := TO_DATE('09/09/2014', 'mm/dd/yyyy');
sal NUMBER := 2000;
comm NUMBER := 50;
deptno NUMBER := 10;
invalid_emp exception;
invalid_date exception;
BEGIN
INSERT INTO empl
VALUES (empno, ename, job, mgr, hiredate, sal, comm, deptno);
IF empno < 0 OR mgr < 0 OR sal < 0 OR comm < 0 OR deptno < 0 THEN
RAISE invalid_emp;
ELSIF hiredate > SYSDATE THEN
RAISE invalid_date;
END IF;
exception
when invalid_emp then
dbms_output.put_line('number cannot be negative');
when invalid_date then
dbms_output.put_line('cannot be future date');
END;
this is how you can use a user defined exception as you wanted
Related
I have a problem with a ORACLE Procedure, it seems that SELECT SUM ignores my WHERE clause, and Sums up ALL of the columns instead of only those that i want to (deptno).
However if i use one of this functions like this for example :
select SUM(SAL) AS SALSUM FROM SCOTT.EMP WHERE SCOTT.EMP.DEPTNO = 10;
It displays the proper SUM.
What may be the problem? Thanks for help.
CREATE OR REPLACE PROCEDURE PROCEDURE1(numerdept IN number, money OUT number) IS
SALSUM NUMBER;
COMMSUM NUMBER;
WYJATEK EXCEPTION;
BEGIN
IF numerdept IN (10, 20, 30) THEN
BEGIN
select SUM(SAL) INTO SALSUM FROM SCOTT.EMP WHERE SCOTT.EMP.DEPTNO = numerdept;
select SUM(COMM) INTO COMMSUM FROM SCOTT.EMP WHERE SCOTT.EMP.DEPTNO = numerdept;
money := SALSUM + COMMSUM;
END;
ELSE RAISE WYJATEK;
END IF;
EXCEPTION
WHEN WYJATEK THEN
BEGIN
dbms_output.put_line('Wrong deptno');
money := NULL;
END;
END;
-- checking --
SET SERVEROUTPUT ON;
DECLARE
pension number;
BEGIN
PROCEDURE1(10, pension);
dbms_output.put_line(pension);
END;
[EDIT 3] SOLUTION
select NVL(SUM(SAL), 0) INTO SALSUM FROM SCOTT.EMP WHERE SCOTT.EMP.DEPTNO = numerdept;
select NVL(SUM(COMM), 0) INTO COMMSUM FROM SCOTT.EMP WHERE SCOTT.EMP.DEPTNO = numerdept;
AND
BEGIN
dbms_output.put_line('Wrong deptno');
money := 10;
END;
Your problem is that your input parameter has the same name as your column, so when the query sees
WHERE SCOTT.EMP.DEPTNO = deptno
it interprets deptno as SCOTT.EMP.DEPTNO, meaning that it is true for all values of deptno. Change the name of your input parameter and the query will work as expected.
You should also use NVL to ensure that the individual SUM values are not NULL, as if either of them is NULL that will make the sum of them NULL as well i.e.
select NVL(SUM(SAL), 0) INTO SALSUM FROM SCOTT.EMP WHERE SCOTT.EMP.DEPTNO = numerdept;
select NVL(SUM(COMM), 0) INTO COMMSUM FROM SCOTT.EMP WHERE SCOTT.EMP.DEPTNO = numerdept;
Below given is my procedure takes too much time to execute.
BEGIN
DECLARE rank1 BIGINT DEFAULT 0;
DECLARE id1 BIGINT;
DECLARE rankskip BIGINT DEFAULT 0;
DECLARE mark DECIMAL(10,2) DEFAULT 0;
DECLARE oldmark DECIMAL(10,2) DEFAULT -100000;
DECLARE done int DEFAULT 0;
DECLARE cursor_i CURSOR FOR
SELECT
(rightmarks - negativemarks) as mark, id
FROM
testresult
WHERE
testid = testid1
ORDER BY
(rightmarks - negativemarks) DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO mark, id1;
IF done = 1 THEN
LEAVE read_loop;
END IF;
IF oldmark = mark THEN
BEGIN
IF IsRankSkip = 1 THEN
BEGIN
SET rankskip = rankskip + 1;
END;
END IF;
END;
ELSE
BEGIN
SET rank1 = rank1 + rankskip + 1;
SET rankskip = 0;
END;
END IF;
SET oldmark = mark;
UPDATE testresult SET rank = rank1 WHERE id=id1;
END LOOP;
CLOSE cursor_i;
END
This loop iterate minimum 2000 times.
Here IsRankSkip and testid1 is an argument passed to the procedure.
This procedure takes 65.343152046204 time to execute. If anybody guide me how can I reduce executing time?
Thank you in advance.
You can do this with a single update statement, making use of variables that change during the execution of it:
UPDATE testresult a
JOIN ( SELECT id,
#row := #row + 1 row_number,
#rank := if(mark = #lastmark, #rank, #row) as rank,
#dense_rank := #dense_rank + if(mark = #lastmark, 0, 1) as dense_rank,
#lastmark := mark as mark
FROM ( SELECT rightmarks - negativemarks as mark,
id
FROM testresult
WHERE testid = testid1
ORDER BY 1 DESC
) data,
(SELECT #row := 0, #dense_rank := 0) r
) b
ON a.id = b.id
SET a.rank = if(IsRankSkip, b.rank, b.dense_rank);
The query with alias b calculates the rank and adds it as a column in the result set. In fact, it adds three kinds of numbers:
#row: the sequential row number without special treatment of equal values
#rank: the same as row number of the value differs from the previous value, otherwise it is the same as in the previous row
#dense_rank: this increments when the value differs from the previous value, otherwise it is the same as in the previous row
You can choose which one to update your rank column with. In the above SQL I have used the two procedure variables IsRankSkip and testid1.
Remark
If you are always calling your procedure for all testid values, then the above can be further improved, so all these updates are done with only one update statement.
I have the below Stored Procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS spCashDonation$$
CREATE PROCEDURE spCashDonation(IN fname varchar(50),IN lname varchar(50),IN telNo bigint, IN pmode tinyint,IN amt decimal(8,2), OUT rno varchar(20))
BEGIN
Set #rmain := (select trim(concat('DNB', DATE_FORMAT(CURRENT_DATE(), '%y'), DATE_FORMAT(CURRENT_DATE(), '%m'))));
IF ((trim(DATE_FORMAT(CURRENT_DATE(),'%m')) = 01) OR (trim(DATE_FORMAT(CURRENT_DATE(),'%m')) = 1)) THEN
Set #rpart = 1;
END IF;
IF ((trim(DATE_FORMAT(CURRENT_DATE(),'%m')) != 01) OR (trim(DATE_FORMAT(CURRENT_DATE(),'%m')) != 1)) THEN
Set #rpart := (select coalesce(max(ReceiptPart),0) from Donation) + 1;
END IF;
INSERT INTO Donation (ReceiptMain, ReceiptPart, firstName, lastName, telNo, payMode, Amount) VALUES (#rmain, #rpart, fname, lname, telNo, pmode, amt);
Set #lid := (select LAST_INSERT_ID()from donation);
select concat(ReceiptMain,ReceiptPart) into rno from donation where id = #lid;
END$$
DELIMITER ;
Call spCashDonation ('RAJIV','IYER',7506033048,0,1000,#rno);
select #rno;
When the table has no record, the first insert goes through fine. The upon the second insert it throws an error as
Error Code: 1242. Subquery returns more than 1 row
When I query for the last insert id, I get more than 1 value. So, I modified the last part of the procedure to:
Set #lid := (select max(LAST_INSERT_ID()) from donation);
Please advice, if this is fine as it should not hinder any concurrent inserts and future CRUD operations. Thanks in advance.
Set #lid := (select LAST_INSERT_ID() from donation);
In the above line remove the FROM statement. If more than one record in the Donation table it will return the same number of times the LAST_INSERT_ID() value.
So simply use Set #lid := (SELECT LAST_INSERT_ID()); it will work in your case.
I have the below code , emp1 table has data of 12 rows in which 2 empno's already present in emp table. i'm trying to save the exceptions for these 2 records and to insert remaining all to emp. But everything is getting error out and i couldn't able to insert into emp. Anyone please help.
SET SERVEROUTPUT ON;
DECLARE
CURSOR C1
IS
SELECT EMPNO FROM EMP1;
TYPE T
IS
TABLE OF C1%ROWTYPE;
L_DATA T;
L_ERRORS_COUNT NUMBER;
EX_DML_ERRORS EXCEPTION;
PRAGMA EXCEPTION_INIT (ex_DML_ERRORS, -47);
L_ERRNO NUMBER;
L_MSG VARCHAR2(4000);
L_IDX NUMBER;
BEGIN
OPEN C1;
LOOP
FETCH C1 BULK COLLECT INTO L_DATA;
BEGIN
FORALL I IN 1..L_DATA.COUNT SAVE EXCEPTIONS
INSERT
INTO EMP
(
EMPNO,
ENAME,
MGR
)
VALUES
(
L_DATA(I).EMPNO,
'SCOTT',
L_DATA(I).EMPNO
);
EXCEPTION
WHEN dup_val_on_index THEN
DBMS_OUTPUT.PUT_LINE('WHD');
L_ERRORS_COUNT := SQL%BULK_EXCEPTIONS.COUNT;
DBMS_OUTPUT.PUT_LINE('WHD::::'||L_ERRORS_COUNT);
FOR J IN 1..L_ERRORS_COUNT
LOOP
L_ERRNO := SQL%BULK_EXCEPTIONS
(
J
)
.ERROR_CODE;
L_MSG := SQLERRM(-L_ERRNO);
L_IDX := SQL%BULK_EXCEPTIONS(J).ERROR_INDEX;
DBMS_OUTPUT.PUT_LINE('I am here SVS:'||L_ERRNO||':'|| L_MSG||':'||L_IDX);
--INSERT INTO EMPS_EXC1(SQLC ,SQLE ,INDX ) VALUES(L_ERRNO, L_MSG,L_IDX);
DBMS_OUTPUT.PUT_LINE('I am here SVS:'||L_ERRNO||':'|| L_MSG||':'||L_IDX);
END LOOP;
END;
EXIT
WHEN C1%NOTFOUND;
END LOOP;
CLOSE C1;
END;
1.what exactly does this code do?
2.what is number(2)?
cursor c1 is
select employee_id, department_id, commission_pct from hr.employees;
emprec c1%rowtype;
v_hike number(2);
begin
open c1;
loop
fetch c1 into emprec;
exit when c1%notfound;
if emprec.department_id = 40 then v_hike := 10;
elsif emprec.department_id = 70 then v_hike := 15;
elsif emprec.commission_pct > 0.30 then v_hike := 5;
else v_hike := 10;
end if;
update employees set salary = salary + salary * v_hike/100
where employee_id = emprec.employee_id;
end loop;
end;
It's oracle cursor definition.. What exactly is that piece of sql doing is so hard to know how hard you can read it.
In first row you can see select which works with many set of rows.. Then you have define local variable. v_hike. In loop it's doing some expresions like count v_hike and then it's set new salary to employer
number(2) is incorrect write, it should be: number(p,s) where p-s digits before the decimal and s digits after the decimal..
what exactly does this code do?
First of all, in Oracle PL/SQL, it is syntactically incorrect since you have missed the DECLARE keyword in your PL/SQL anonymous block. You will get a compilation error.
Let me explain step by step :
cursor c1 is
select employee_id, department_id, commission_pct from hr.employees;
emprec c1%rowtype;
It is an explicit cursor, it is a work area to store the processing information of the select query. And emprec is a collection type which will hold the resultset when you issue the FETCH statement.
v_hike number(2);
This is a variable declared of NUMBER data type. NUMBER(2) means it can hold a number value with PRECISION 2.
begin
open c1;
loop
fetch c1 into emprec;
exit when c1%notfound;
if emprec.department_id = 40 then v_hike := 10;
elsif emprec.department_id = 70 then v_hike := 15;
elsif emprec.commission_pct > 0.30 then v_hike := 5;
else v_hike := 10;
end if;
update employees set salary = salary + salary * v_hike/100
where employee_id = emprec.employee_id;
end loop;
end;
Next steps are,
a. Open the cursor
b. for each row in the cursor, loop through and prepare the
collection in emprec
c. When there are no records to fetch from cursor then exit
d. Assign a number value to the v_hike variable based on the IF-ELSE
construct.
e. execute the UPDATE statement.
f. Come out of loop.
g. Scope of block ends with END keyword.