PL/SQL Storing and calling procedure - sql

I am currently working on a PL/SQL problem where I have to create a cursor inside a procedure that will give the appropriate discount for a given item (10% for prices >= $100 and 5% for prices >= $10). When I have to call the procedure, I need to display the Order Number, customer First Name, Last Name, and the Total Net Cost of the items after the discount for a specific order number (in this case I need to display for order number 2). I can't get it to display this information.
Here is my code for creating the procedure with a cursor so far.
CREATE OR REPLACE PROCEDURE ComputeOrderTotal
(no_id IN orders.o_id%TYPE,
cfirst IN customer.c_first%TYPE,
clast IN customer.c_last%TYPE,
TotalNetCost OUT orders.ordertotal%TYPE) IS
CURSOR OrderCursor IS
SELECT order_line.inv_id, inv_price, ol_quantity, inv_price*ol_quantity AS ExtPrice,
CASE
WHEN inv_price*ol_quantity >= 100 THEN 0.9*(inv_price*ol_quantity)
WHEN inv_price*ol_quantity >= 10 THEN 0.95*(inv_price*ol_quantity)
ELSE
inv_price*ol_quantity
END AS NetCost
FROM inventory, order_line, orders, customer
WHERE orders.o_id = customer.c_id;
OrderRow OrderCursor%ROWTYPE;
BEGIN
OPEN OrderCursor;
LOOP
FETCH OrderCursor INTO OrderRow;
EXIT WHEN OrderCursor%NOTFOUND;
TotalNetCost :=TotalNetCost + OrderRow.NetCost;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Order Number: ' || no_id || 'First Name: ' || cfirst || 'Last Name: ' ||
clast || 'Total Net Cost: ' || TO_CHAR(TotalNetCost, '$0999.99'));
END;
And here is my code for calling the procedure.
DECLARE
no_id orders.o_id%TYPE;
cfirst customer.c_first%TYPE;
clast customer.c_last%TYPE;
TotalNetCost orders.ordertotal%TYPE;
BEGIN
ComputeOrderTotal(2, cfirst, clast, TotalNetCost);
END;
Thanks for the help!

i am not sure but your conditions could be the problem as your second condition contradicts the first. for example 110 satisfy the first condition but it also satisfy the second condition.
WHEN inv_price*ol_quantity >= 100 THEN 0.9*(inv_price*ol_quantity)
WHEN inv_price*ol_quantity >= 10 THEN 0.95*(inv_price*ol_quantity)
i would suggest changing second condition to <100 and >=10

Add this line to the top of the code calling the procedure.
Alter session set serveroutput on;

Related

adding to variables inside of a oracle procedure

I created a procedure that takes the account number of a user and it returns every transaction they've made plus their running balance. everything works except the balance. The code compiles and runs but nothing shows up in the running balance column of the dbms.output
CREATE OR REPLACE PROCEDURE print_dett(
in_account_nbr NUMBER
)
IS
dCount NUMBER;
BEGIN
dbms_output.put_line(' Date : transaction amount : balance - For: ' || in_account_nbr);
FOR r IN(
SELECT t.tx_nbr, t.tx_date, t.tx_amount, t.tx_type_code, a.balance
FROM transaction t, account a
WHERE t.account_nbr = a.account_nbr
AND t.account_nbr = in_account_nbr
ORDER BY tx_date)
LOOP
IF r.tx_type_code = 'D' OR r.tx_type_code = 'R' THEN
dCount := dCount + r.tx_amount;
ELSE
dCount := dCount - r.tx_amount;
END IF;
dbms_output.put_line(r.tx_date || ' : ' || r.tx_amount || ' : ' || dCount );
END LOOP;
END;
What can I change in the code so the running balance will actually show up when printed?
Number variables get initialized with NULL. Your variable dCount is hence null first and adding values doesn't change this.
Initialize it with zero instead:
CREATE OR REPLACE PROCEDURE print_dett(
in_account_nbr NUMBER
)
IS
dCount NUMBER := 0;
...

How to search for a month that is input by the user

I am working on some homework and have been stuck on this for a week. I have tried using TO_CHAR, MONTH(search), and EXTRACT(MONTH from...) and they all end up with either identifier 'JAN'(the month I am searching for) is not declared, or expression is of the wrong type. This assignment is to display all the rows for pledges made in a specified month. The column PLEDGEDATE is of type Date in the format 'dd-mmm-yy'. Any ideas how to make this work?
Declare
Pledges UNIT_2_ASSIGNMENT%ROWTYPE;
SEARCH DATE;
Begin
SEARCH := &M0NTH;
FOR PLEDGES IN
(SELECT IDPLEDGE, IDDONOR, PLEDGEAMT,
CASE
WHEN PAYMONTHS = 0 THEN 'LUMP SUM'
ELSE'MONHTLY - '||PAYMONTHS
END AS MONTHLY_PAYMENT
FROM UNIT_2_ASSIGNMENT
WHERE TO_CHAR(PLEDGEDATE,'MMM') = 'SEARCH'
ORDER BY PAYMONTHS)
LOOP
DBMS_OUTPUT.PUT_LINE('Pledge ID: '||UNIT_2_ASSIGNMENT.IDPLEDGE||
' Donor ID: '||UNIT_2_ASSIGNMENT.IDDONOR||
' Pledge Amount: '||TO_CHAR(UNIT_2_ASSIGNMENT.PLEDGEAMT)||
' Lump Sum: '||MONTHLY_PAYMENT);
END LOOP;
END;
You can use (comments on changes are inline):
DECLARE
Pledges UNIT_2_ASSIGNMENT%ROWTYPE;
SEARCH VARCHAR2(3); -- Use VARCHAR2 not DATE data type.
BEGIN
SEARCH := 'JAN'; -- Replace with your substitution variable.
FOR PLEDGES IN (
SELECT IDPLEDGE,
IDDONOR,
PLEDGEAMT,
CASE
WHEN PAYMONTHS = 0
THEN 'LUMP SUM'
ELSE 'MONHTLY - '||PAYMONTHS
END AS MONTHLY_PAYMENT
FROM UNIT_2_ASSIGNMENT
WHERE TO_CHAR(PLEDGEDATE,'MON') = SEARCH -- Unquote variable and use MON not MMM
ORDER BY PAYMONTHS
)
LOOP
DBMS_OUTPUT.PUT_LINE(
'Pledge ID: '||Pledges.IDPLEDGE|| -- Use rowtype variable name not table name.
' Donor ID: '||Pledges.IDDONOR||
' Pledge Amount: '||TO_CHAR(Pledges.PLEDGEAMT)||
' Lump Sum: '||Pledges.MONTHLY_PAYMENT
);
END LOOP;
END;
/
Which, for the sample data:
CREATE TABLE unit_2_assignment( idpledge, iddonor, pledgeamt, pledgedate, paymonths ) AS
SELECT LEVEL,
'Donor' || LEVEL,
LEVEL * 1000,
ADD_MONTHS( DATE '2020-01-01', LEVEL - 1 ),
LEVEL
FROM DUAL
CONNECT BY LEVEL <= 12;
Outputs:
Pledge ID: 1 Donor ID: Donor1 Pledge Amount: 1000 Lump Sum: MONHTLY - 1
You should enclose your substitution variable into single quotes ('&MONTH') because SQLPlus treats it as simple word and can substitute anything, according to examples in so old 8i reference. And it can be figured out by the error message: he tries to use JAN as identifier, so it is not properly enclosed.
Declare
Pledges UNIT_2_ASSIGNMENT%ROWTYPE;
SEARCH DATE;
Begin
SEARCH := '&M0NTH';
What it says:
For example, if the variable SORTCOL has the value JOB and the variable
MYTABLE has the value EMP, SQL*Plus executes the commands
SQL> BREAK ON &SORTCOL
SQL> SELECT &SORTCOL, SAL
2 FROM &MYTABLE
3 ORDER BY &SORTCOL;
But for your task there's no need to use PL/SQL, just format an output of SQLPlus script in appropriate way (have no SQLPlus console to put direct formatting options, but better to read the doc on SQLPlus by yourself).

Using multiple procedures and multiple cursors in a package

I have created a package with two procedures and two cursors in it, but while executing the procedure, it is executed successful, but same record executed multiple times and a buffer overflow occurred.
I also tried removing the loop from the cursor but for 1 record that will be fine and for multiple record it won't work as expected.
EXPECTED
I just need to remove multiple execution of same record from the procedure where i am getting multiple execution of same record
for single procedure and single cursor it is working properly but for multiple cursor and multiple procedure i am getting problem here which caused buffer overflow too where i need different record
Is there any alternative way that I can fix the problem ?
CREATE OR REPLACE PACKAGE test.report AS
PROCEDURE distribution (
code_in IN user.test.code%TYPE,
fromdate date,
todate date
);
PROCEDURE tdvalue (
id IN user.test.custid%TYPE
);
END report;
/
Package Body
CREATE OR REPLACE PACKAGE BODY test.report as
----------VARIABLE DECLARATION----------------
code_in user.test.code%TYPE;
custidin user.test.custid%TYPE;
fromdate DATE;
todate DATE;
diff number(17,2);
---------------CURSOR DECLARATION--------------
CURSOR td_data(code_in user.test.code%TYPE,
fromdate date,
todate date
) IS
( SELECT
test.code,
COUNT(test.code) AS count,
SUM(test2.Deposit_amount) AS total,
test.currency
FROM
user.test2
JOIN user.test ON test2.acid = test.acid
WHERE
user.test2.open_effective_date BETWEEN TO_DATE(fromdate, 'dd-mm-yyyy') AND TO_DATE(todate, 'dd-mm-yyyy')
and
user.test.code = code_in
GROUP BY
test.code,test.currency
);
td__data td_data%rowtype;
CURSOR C_DATA(custidin user.test.custid%TYPE) IS SELECT
test.custid,
test2.id,
TO_DATE(test2.initial_date, 'dd-mm-yyyy') - TO_DATE(test2.end_date, 'dd-mm-yyyy') AS noofdays,
round(((test2.deposit_amount *((TO_DATE(test2.initial_date, 'dd-mm-yyyy') - TO_DATE(test2.end_date, 'dd-mm-yyyy'
)) / 365) * test4.interest_rate) / 100), 2) + test2.deposit_amount AS calculated_amount,
SUM(test.flow_amt) + test2.deposit_amount AS system_amount
FROM
user.test
JOIN user.test2 ON test3.entity_id = test2.id
WHERE
test.custid = custidin
GROUP BY
test.custid,
test2.id;
c__data c_data%ROWTYPE;
PROCEDURE distribution
(
code_in IN user.test.code%TYPE,
fromdate in date,
todate in date
)
AS
BEGIN
OPEN td_data(code_in,fromdate,todate);
loop
FETCH td_data INTO td__data;
dbms_output.put_line(td__data.code
|| ' '
|| td__data.count
|| ' '
||td__data.currency
||' '
||td__data.total
);
end loop;
CLOSE td_data;
END distribution;
PROCEDURE tdvalue (
custidin IN user.test.custid%TYPE
)
AS
BEGIN
open c_data(custidin);
fetch c_data into c__data;
loop
diff:= c__data.calculated_amount- c__data.system_amount;
dbms_output.put_line(c__data.custid
|| ' '
|| c__data.noofdays
|| ' '
|| c__data.end_date
|| ' '
|| c__data.initial_date
|| ' '
|| c__data.calculated_amount
||' '
||diff
);
end loop;
close c_data;
END tdvalue;
END report;
/
To run
ALTER SESSION set nls_date_format='dd-mm-yyyy';
SET SERVEROUTPUT ON;
EXEC REPORT.DISTRIBUTION('872328','01-02-2016','08-02-2019');
/
EXEC REPORT.tdvalue('S9292879383SS53');
Buffer overflow - ORU-10027 - happens when the total number of bytes displayed through DBMS_OUTPUT exceeds the size of the serveroutput buffer. The default is only 20000 bytes (who knows why?). Your session is using that default because of how you enable serveroutput. Clearly one record is less than 2000 and you only hit that limit when you run for multiple records.
To fix this try this
SET SERVEROUTPUT ON size unlimited
It's not actually unlimited, but the upper bound is the PGA limit (session memory) and you really shouldn't hit that limit with DBMS_OUTPUT. Apart from anything else who would read all that?
So the other problem with your code - as #piezol points out - is that your loops have no exit points. You should test whether the FETCH actually fetched anything and exit if it didn't:
loop
FETCH td_data INTO td__data;
exit when td_data%notfound;
dbms_output.put_line(td__data.code
|| ' '
|| td__data.count
|| ' '
||td__data.currency
||' '
||td__data.total
);
end loop;
Remembering to do this is just one reason why implicit cursors and cursor for loops are preferred over explicit cursors.
The second cursor loop is even worse because not only does it not have an exist point, the fetch is outside the loop. That's why you have repeated output for the same record.
So let's rewrite this ...
open c_data(custidin);
fetch c_data into c__data; -- should be inside
loop
diff:= c__data.calculated_amount- c__data.system_amount;
… as a cursor for loop:
PROCEDURE tdvalue (
custidin IN user.test.custid%TYPE
)
AS
BEGIN
for c__data in c_data(custidin)
loop
diff:= c__data.calculated_amount- c__data.system_amount;
dbms_output.put_line(c__data.custid
|| ' '
|| c__data.noofdays
|| ' '
|| c__data.end_date
|| ' '
|| c__data.initial_date
|| ' '
|| c__data.calculated_amount
||' '
||diff
);
end loop;
END tdvalue;
No need for OPEN, CLOSE or FETCH, and no need to check when the cursor is exhausted.
In PL/SQL, the preferred mechanism for setting the DBMS_OUTPUT buffer size would be within your procedure. This has the benefit of working in any client tool, such as Java or Toad (though it is still up to the client tool to retrieve the output from DBMS_OUTPUT).
DBMS_Output.ENABLE
Pass in a parameter of NULL for unlimited buffer size.
It would go like this:
BEGIN
DBMS_OUTPUT.ENABLE(NULL);
FOR I IN 1..1000 LOOP
DBMS_OUTPUT.PUT_LINE('The quick red fox jumps over the lazy brown dog.');
END LOOP;
END;
/
Bonus fact:
You can use the other functions and procedures in DBMS_OUTPUT to roll your own if you aren't using SQL*Plus or a DBMS_OUTPUT-savvy tool like Toad.
You can use the GET_LINE or GET_LINES procedures from your client code to get whatever may have been written to DBMS_OUTPUT.
GET_LINE

Looking at IF, WHERE, and WHEN in PL SQL

I am completing an academic assignment that asks to prompt the user for their target sales and their employee id. If the target sales exceeds or equates to that of the actual company sales for 2015, then raises can be applied.
I've composed a majority of code but I am stuck on the END IF; statements on line 25. I'm receiving error(s)
ORA-06550, PLS-00103: Encountered the symbol "WHERE" when expecting
one of the following.
I think I might be struggling to integrate the if statement that compares the user input to the company sales for 2015.
Insights greatly appreciated! Thank you!
accept emp_target prompt 'Please enter your company sales target: '
accept empno prompt 'Please enter your employee ID: '
DECLARE
emp_target NUMBER := &emp_target;
cmp_target NUMBER;
empno emp_employees.emp_id%type := &empno;
new_sal emp_employees.salary%type;
cnt number;
CURSOR sales_cur IS
SELECT SUM(oe_orderDetails.quoted_price)
FROM oe_orderDetails
JOIN oe_orderHeaders
ON oe_orderDetails.order_id = oe_orderHeaders.order_id
WHERE oe_orderHeaders.order_date >= to_date('1.1.' || 2015, 'DD.MM.YYYY')
and oe_orderHeaders.order_date < to_date('1.1.' || (2015 + 1), 'DD.MM.YYYY');
BEGIN
OPEN sales_cur;
FETCH sales_cur INTO cmp_target;
IF cmp_target >= emp_target THEN
UPDATE emp_employees SET
emp_employees.salary = case WHEN emp_employees.dept_id = 10 THEN emp_employees.salary * 1.1
WHEN emp_employees.emp_id = 145 THEN emp_employees.salary * 1.15
WHEN emp_employees.dept_id = 80 THEN emp_employees.salary * 1.2
ELSE emp_employees.salary
END IF;
END
WHERE emp_employees.emp_id = empno
returning emp_employees.salary into new_sal;
cnt := sql%rowcount;
IF cnt > 0 THEN
dbms_output.put_line('Employee ' || empno || ', new salary = ' || new_sal);
ELSE
dbms_output.put_line('Nobody got new salary');
END IF;
END;
/
The main issue is that you have misplaced the CASE block's end and where clause out after END IF.
Apart from that I would say that the cursor block was not required to store a simple SUM, but i'll let you use it since you are learning for an assignment.
The other problem after you fix the first one is your returning emp_employees.salary into new_sal. A scalar variable can't contain multiple rows returned from the dml which updates more than one row. You should use a collection(nested table) instead. Use RETURNING BULK COLLECT INTO to load it and loop over later to display your final message.
Please read all my code comments carefully. I cannot test the whole code for any errors as I have none of your tables. You should fix if there are any if you can or let us know if you can't.
ACCEPT emp_target PROMPT 'Please enter your company sales target: '
ACCEPT empno PROMPT 'Please enter your employee ID: '
DECLARE
emp_target NUMBER := &emp_target;
cmp_target NUMBER;
empno emp_employees.emp_id%TYPE := &empno;
TYPE sal_type IS
TABLE OF emp_employees.salary%TYPE; --nested table(collection) of salary
new_sal sal_type; -- a collection variable
cnt NUMBER;
CURSOR sales_cur IS SELECT SUM(oe_orderdetails.quoted_price)
FROM oe_orderdetails
JOIN oe_orderheaders ON oe_orderdetails.order_id = oe_orderheaders.order_id
WHERE oe_orderheaders.order_date >= TO_DATE('1.1.' || 2015,'DD.MM.YYYY') AND
oe_orderheaders.order_date < TO_DATE('1.1.' || (2015 + 1),'DD.MM.YYYY'); --is it required to specify (2015 + 1) instead of 2016?
BEGIN
OPEN sales_cur;
FETCH sales_cur INTO cmp_target;
IF
cmp_target >= emp_target
THEN
UPDATE emp_employees
SET
emp_employees.salary =
CASE
WHEN emp_employees.dept_id = 10 THEN emp_employees.salary * 1.1
WHEN emp_employees.emp_id = 145 THEN emp_employees.salary * 1.15
WHEN emp_employees.dept_id = 80 THEN emp_employees.salary * 1.2
ELSE emp_employees.salary
END
WHERE emp_employees.emp_id = empno --misplaced where and end
RETURNING emp_employees.salary BULK COLLECT INTO new_sal;
END IF;
cnt := new_sal.count; --no of records in nested table
IF
cnt > 0
THEN
FOR i IN new_sal.first..new_sal.last LOOP
dbms_output.put_line('Employee ' || empno || ', new salary = ' || new_sal(i) );
END LOOP;
ELSE
dbms_output.put_line('Nobody got new salary');
END IF;
CLOSE sales_cur; -- always remember to close the cursor
END;
/

SQL Function and Anonymous block

I'm taking a class about databases. I am very new to it, so excuse me if this is an obvious error, but I've been working on this problem for hours and not sure what else to do.
The code is suppose to create a function to that
A.) Outputs the average netwin for a year given by a parameter
(given by the formula (SeasonW-Seasonl) + (PlayoffW - PlayoffL)
B.) Outputs all of the coaches that have a netwin over the average
C.) Returns the number of coaches that fits this criteria.
D.)Have a anonymous block that calls this function, and outputs two different messages depending on the return on the function.
Now I have sucessfully done parts A, C, and D. But for some reason my function will not be created when I insert part B.
create or replace function GOOD_COACHES(season IN INT)
return INT
IS
netwin INT;
CNT INT;
BEGIN
--Calculated netwin
select AVG((SEASON_WIN-SEASON_LOSS) + (PLAYOFF_WIN-PLAYOFF_LOSS)) into netwin from COACHESSEASON where YEAR = season;
--Prints out A
dbms_output.put_line('Average Netwin is: ' || netwin);
--This Line messes up the function, I don't know why
select T.FIRSTNAME, T.LASTNAME from COACHESSEASON T where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>netwin and YEAR = season;
--Calculates the number of teams that satisfy average
select count(T.FIRSTNAME) into CNT from COACHESSEASON T where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>netwin and YEAR = season;
return CNT;
END;
--End of the Function
--Start of the Anonymous Block
DECLARE
x int := GOOD_COACHES(1998);
BEGIN
if x = 0 then dbms_output.put_line('We didn''t find any good_coaches!');
else dbms_output.put_line('The No. of good coaches is ' || x);
end if;
END;
/
That third line in the function messes it up and doesn't allow it to be called. If I comment it out, it works properly.
When I take it out of the function and make it into a regular SQL statement, it works.
select T.FIRSTNAME, T.LASTNAME from COACHESSEASON T where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>0 and YEAR = /*RandYear*/;
If anyone understands why the function can not be created with that line it in, I would appreciate the advice. I also do not know how I would print out the results of the selected row in the function.
You are not selecting the T.FIRSTNAMEs and T.LASTNAMEs into anything so the statement is not valid for inclusion in a PL/SQL block.
There are likely to be multiple "good coaches" so you can't put the values into a single variable and will have to use a cursor or a collection.
Using collections, if you create a table type to collect the first and last names into:
CREATE TYPE VARCHAR2s_Table AS TABLE OF VARCHAR2(30);
/
Then you can use this to collect the first and last names (and as a bonus the collection size will tell you how many good coaches there are and you can skip the last query):
create or replace function GOOD_COACHES(season IN INT)
return INT
IS
netwin INT;
CNT INT;
firstnames VARCHAR2s_Table;
lastnames VARCHAR2s_Table;
BEGIN
--Calculated netwin
select AVG((SEASON_WIN-SEASON_LOSS) + (PLAYOFF_WIN-PLAYOFF_LOSS))
into netwin
from COACHESSEASON
where YEAR = season;
--Prints out A
dbms_output.put_line('Average Netwin is: ' || netwin);
--This Line messes up the function, I don't know why
select T.FIRSTNAME, T.LASTNAME
BULK COLLECT INTO firstnames, lastnames
from COACHESSEASON T
where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>netwin
and YEAR = season;
FOR i IN 1 .. firstnames.COUNT LOOP
DBMS_OUTPUT.put_line( firstnames(i) || ' ' || lastnames(i) );
END LOOP;
return firstnames.COUNT;
END;
/
SQLFIDDLE