I'm currently learning PL/SQL atm and I have run into an issue with one of my homework questions.
In the below code, I'm getting user input for a province and isolating select results using said province in the declaration of the cursor and trying to run the visitsandtotal procedure but all I'm getting is no data found, why?
user prompt
SET SERVEROUTPUT ON
ACCEPT prov PROMPT 'Enter Province: ';
DECLARE
customerprov VARCHAR2(4000);
customername VARCHAR2(4000);
visits NUMBER;
total FLOAT;
CURSOR prov_cursor is
Select custprovince, custname
into customerprov, customername
from si.customer
where upper(custprovince) = '&prov';
BEGIN
for c in prov_cursor loop
visitsandtotal(c.custname, visits, total);
dbms_output.put_line('Name: ' || c.custname || ' Visits: ' || visits || ' Total Labor Cost: ' || total);
end loop;
END;
Procedure
CREATE OR REPLACE PROCEDURE visitsandtotal (
userinput IN VARCHAR2
, visits OUT NUMBER
, total OUT FLOAT
) IS
BEGIN
SELECT
COUNT(*) AS visits
, SUM(s.laborcost) AS totalcost
INTO
visits
, total
FROM
si.customer c
INNER JOIN si.servinv s ON c.custname = s.custname
WHERE
s.custname = userinput
GROUP BY
c.custname
, s.custname ;
END;
Error
Error report -
ORA-01403: no data found
ORA-06512: at "S6_TRAN84.VISITSANDTOTAL", line 7
ORA-06512: at line 11
01403. 00000 - "no data found"
*Cause: No data was found from the objects.
*Action: There was no data from the objects which may be due to end of fetch.
I cannot comment due to less number of reputation.
NO_DATA_FOUND error comes from the procedure where you have where clause and group by..
and if no records with parameter "userinput" leads to the exception.
I would suggest to change the procedure as we certainly don't need the group by custname as the custname is part of where clause;
CREATE OR REPLACE PROCEDURE visitsandtotal
(
userinput IN VARCHAR2
,visits OUT NUMBER
,total OUT FLOAT
)
IS
BEGIN
SELECT COUNT(*) AS visits
,SUM(s.laborcost) AS totalcost
INTO visits
,total
FROM si.customer c
INNER JOIN si.servinv s
ON c.custname = s.custname
WHERE s.custname = userinput;
--removed group by as custname is part of where clause
END visitsandtotal;
But for whatever reason if you insists to keep the group by clause, you have to handle NO_DATA_FOUND exception explicitly in the procedure visitsandtotal
CREATE OR REPLACE PROCEDURE visitsandtotal
(
userinput IN VARCHAR2
,visits OUT NUMBER
,total OUT FLOAT
)
IS
BEGIN
SELECT COUNT(*) AS visits
,SUM(s.laborcost) AS totalcost
INTO visits
,total
FROM si.customer c
INNER JOIN si.servinv s
ON c.custname = s.custname
WHERE s.custname = userinput;
GROUP BY c.custname,s.custname;
-- you dont need to mention custname from both table as join is in place
EXCEPTION
WHEN no_data_found THEN
--HERE - write your exception code whatever you like to add
END visitsandtotal;
Related
I change my executable part as,
BEGIN
proc_ref_value(orders_id);
proc_display_firstname(custId);
DBMS_OUTPUT.PUT_LINE(orders_id||' '||status||' '||re_value||' '||cust_last_name );
END;
When I run It, I got output without customer full name.
Statement processed.
10101 completed 0 10102 cancelled 0 10103 completed 0
10104 completed 0 10105 refunded 740
But I want to get customer full name from first procedure.
This might be starting point based on what it seems you're trying to do with your code. You don't need procedures within your anonymous block, just cursors. Adjust variable types as you need.
DECLARE
orders_id CHAR(5);
status VARCHAR(10);
re_value NUMBER;
custId customer.customer_id%TYPE;
or_status orders.order_status%TYPE;
total orders.total_order%TYPE;
v_order_id orders.order_id%TYPE;
ref_value NUMBER;
customer_name varchar2(20);
CURSOR c1 IS
SELECT INITCAP(CONCAT(CONCAT(cust_first_name,' '),cust_last_name)) as cust_name
FROM customer
WHERE customer_id = cust_id;
CURSOR c2 IS
SELECT order_id
, order_status
, total_order
, CASE WHEN order_status='refunded' THEN total_order*0.25
WHEN order_status='completed' THEN total_order* 0
WHEN order_status='cancelled' THEN total_order*0
END order_value
FROM orders
where order_id = v_order_id;
BEGIN
v_order_id := 1; -- your value here
for x in c2 loop
or_status := x.order_status;
total := x.total_order;
ref_value := x.order_value;
for y in c1 loop
customer_name := y.cust_name;
end loop;
DBMS_OUTPUT.PUT_LINE(v_order_id||' '||or_status||' '||ref_value||' '||customer_name);
end loop;
END;
I want to enter the variables inside the table INFORME_VENTA.
Below is my code:
VARIABLE B_ANIO VARCHAR2(8);
EXECUTE :B_ANIO := '042018';
DECLARE
V_CLIENTE_ID CLIENTES.CLIENTE_ID%TYPE;
V_NOMBRE_CIA CLIENTES.NOMBRE_CIA%TYPE;
V_NRO_BOLETA BOLETAS.NRO_BOLETA%TYPE;
BEGIN
LOOP
SELECT C.CLIENTE_ID , C.NOMBRE_CIA , B.NRO_BOLETA
INTO V_CLIENTE_ID , V_NOMBRE_CIA , V_NRO_BOLETA
FROM BOLETAS B JOIN ORDENES O JOIN CLIENTES C
ON (C.CLIENTE_ID = O.ORDEN_ID)
ON (B.NRO_BOLETA = O.ORDEN_ID);
INSERT INTO INFORME_VENTA
VALUES(:B_ANIO , V_CLIENTE_ID , V_NOMBRE_CIA , V_NRO_BOLETA);
END LOOP;
END;
I want to put the variables inside the informe_venta table but I get the following error
Informe de error -
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 11
01422. 00000 - "exact fetch returns more than requested number of rows"
*Cause: The number specified in exact fetch is less than the rows returned.
*Action: Rewrite the query or change number of rows requested
Why do you use PL/SQL? A simple INSERT is capable of doing the whole job:
insert into informe_venta
select :b_anio, c.cliente_id , c.nombre_cia , b.nro_boleta
from boletas b join ordenes o
on b.nro_boleta = o.orden_id
join clientes c
on c.cliente_id = o.orden_id;
(In SQL*Plus you'd reference the B_ANIO variable with ampersand, &b_anio).
If it has to be PL/SQL, enclose the above INSERT into BEGIN-END and run it. Once again: you don't need a loop.
One more remark: I'd suggest you to name columns you're inserting into, such as
insert into informe_venta (anio, cliente_id, nombre_cia, nro_boleta)
select ...
Instead of SELECT INTO statement use CURSOR against ORA-01422 error, as in the following block :
VARIABLE B_ANIO VARCHAR2(8);
EXECUTE :B_ANIO := '042018';
DECLARE
V_CLIENTE_ID CLIENTES.CLIENTE_ID%TYPE;
V_NOMBRE_CIA CLIENTES.NOMBRE_CIA%TYPE;
V_NRO_BOLETA BOLETAS.NRO_BOLETA%TYPE;
BEGIN
FOR R IN
(
SELECT C.CLIENTE_ID , C.NOMBRE_CIA , B.NRO_BOLETA
FROM BOLETAS B JOIN ORDENES O JOIN CLIENTES C
ON (C.CLIENTE_ID = O.ORDEN_ID)
ON (B.NRO_BOLETA = O.ORDEN_ID);
)
LOOP
V_CLIENTE_ID := R.CLIENTE_ID;
V_NOMBRE_CIA := R.NOMBRE_CIA;
V_NRO_BOLETA := R.NRO_BOLETA;
INSERT INTO INFORME_VENTA
VALUES(:B_ANIO , V_CLIENTE_ID , V_NOMBRE_CIA , V_NRO_BOLETA);
END LOOP;
END;
My procedure will take in an subject code as a parameter and the proceed to display the subject title and lecturers’ names who have taught the subject before 2016. My procedure should also prompt input a subject code and display the results. I tried my code as following without putting it into a procedure and it returns the correct results but when I include my code as part of a PL/SQL procedure, I get an error that says
Warning: Procedure created with compilation errors.
I am quite new to PL/SQL so any advices would be helpful! The procedure code that I had been trying for hours as follow:
CREATE OR REPLACE PROCEDURE WHO(CODE CHAR)
IS
FNAME VARCHAR(256)
LNAME VARCHAR(256)
TITLE VARCHAR(256)
CURSOR C1
IS
SELECT FIRST_NAME, LAST_NAME, SUBJECT.NAME
FROM ACADEMIC INNER JOIN TEACHES
ON STAFF# = LECTURER
INNER JOIN SUBJECT
ON TEACHES.CODE=SUBJECT.CODE
WHERE YEAR<2016 AND TEACHES.CODE=CODE
GROUP BY FIRST_NAME, LAST_NAME, SUBJECT.NAME;
BEGIN
OPEN C1;
LOOP
IF C1%NOTFOUND THEN EXIT;
END IF;
FNAME=FIRST_NAME;
LNAME=LAST_NAME;
DBMS.OUTPUT.PUT_LINE(FNAME||' '||LNAME);
TITLE=SUBJECT.NAME;
DBMS.OUTPUT.PUT_LINE(TITLE);
END LOOP;
CLOSE C1;
END WHO;
/
You have few issues with your code. Some missing semilcons at decalration. See below:
CREATE OR REPLACE PROCEDURE WHO( CODE CHAR)
IS
FNAME VARCHAR(256); --<Missing Semicolon
LNAME VARCHAR(256); --<Missing Semicolon
TITLE VARCHAR(256); --<Missing Semicolon
CURSOR C1
IS
SELECT FIRST_NAME,
LAST_NAME,
SUBJECT.NAME
FROM ACADEMIC
INNER JOIN TEACHES
ON STAFF# = LECTURER
INNER JOIN SUBJECT
ON TEACHES.CODE =SUBJECT.CODE
WHERE YEAR <2016
AND TEACHES.CODE=CODE
GROUP BY FIRST_NAME,
LAST_NAME,
SUBJECT.NAME;
BEGIN
OPEN C1;
LOOP
--this is how you put the value of cursor to variable
FETCH C1 into FNAME ,LNAME ,TITLE ;
--No need for IF to exit loop. You can use as shown below
EXIT when C1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(FNAME||' '||LNAME);
DBMS_OUTPUT.PUT_LINT(TITLE);
END LOOP;
CLOSE C1;
END WHO;
/
Demo:
CREATE OR REPLACE PROCEDURE WHO( CODE CHAR)
IS
FNAME number;
CURSOR C1
IS
SELECT contract_id FROM TX;
BEGIN
OPEN C1;
LOOP
fetch c1 into FNAME;
EXIT when C1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(FNAME);
END LOOP;
CLOSE C1;
END WHO;
/
Output:
1
1
1
2
2
2
3
3
Tools like SQL Developer, PL/SQL Developer etc will highlight compilation errors, or on the SQL*Plus command line you can enter
show errors
immediately after compiling, or else
show errors procedure who
Or you can query the user_errors table. Whatever tool you are using, you need to be able to work with compilation errors.
Start by tidying up the code so it is easier to read and follow and generally looks more professional. Also although they are not actually causing any errors, you should really change those char and varchar types to the standard varchar2 (ideally everything should be anchored as academic.first_name%type etc, but one thing at a time).
Here is the fixed version:
create or replace procedure who
( code subject.code%type ) -- CHAR is a disastrous datatype you should never need
is
fname varchar2(50); -- VARCHAR2 is the standard string type, VARCHAR is reserved
lname varchar2(50); -- Also PL/SQL requires terminating semicolons here
title varchar2(256);
cursor c1 is
select first_name
, last_name
, subject.name
from academic
join teaches
on staff# = lecturer
join subject
on teaches.code = subject.code
where year < 2016
and teaches.code = who.code -- "code" on its own is ambiguous
group by first_name, last_name, subject.name;
begin
open c1;
loop
fetch c1 into fname, lname, title; -- Added 'fetch into'
if c1%notfound then
exit;
end if;
dbms_output.put_line(fname || ' ' || lname); -- It's dbms_output, with a '_'
dbms_output.put_line(title);
end loop;
close c1;
end who;
This compiles.
But you can still simplify this by using a Cursor FOR Loop instead of the elaborate, verbose way:
create or replace procedure who
( code subject.code%type )
is
begin
for r in (
select first_name
, last_name
, subject.name as title
from academic
join teaches
on staff# = lecturer
join subject
on teaches.code = subject.code
where year < 2016
and teaches.code = who.code
group by first_name, last_name, subject.name
)
loop
dbms_output.put_line(r.first_name || ' ' || r.last_name);
dbms_output.put_line(r.title);
end loop;
end who;
This implicitly declares a cursor and a record named r with the three fields based on the cursor, and it handles opening, fetching and closing for you.
I don't know your tables, but I am guessing they are something like this:
create table subject
( code varchar2(20) primary key
, name varchar2(30) not null );
create table academic
( staff# integer primary key
, first_name varchar2(20) not null
, last_name varchar2(20) not null );
create table teaches
( lecturer references academic(staff#) not null
, code references subject not null
, year number(4,0) not null
, constraint teaches_pk primary key (lecturer, code, year) );
If so, I would use table aliases in the query as below to avoid ambiguity:
select a.first_name
, a.last_name
, s.name as title
from academic a
join teaches t
on t.lecturer = a.staff#
join subject s
on s.code = t.code
where t.year < 2016
and t.code = who.code
group by a.first_name, a.last_name, s.name
Your going the correct direction, but there are a bunch of typos..
But to your main-question:
You have to FETCH the rows into you variables. Here an example.
DECLARE
FNAME VARCHAR (256); -- you need some semicolons here ;)
LNAME VARCHAR (256);
NAME VARCHAR (256);
TITLE VARCHAR (256);
CURSOR C1 -- replaced you query with a dummy-query. Yours should also work
IS
SELECT 'Hans' FIRST_NAME, 'Schnitzel' LAST_NAME, 'The Schnitzel' NAME
FROM DUAL
UNION ALL
SELECT 'Heinrich' FIRST_NAME, 'Wurst' LAST_NAME, 'The Sausage' NAME
FROM DUAL;
BEGIN
OPEN C1; -- open cursor
LOOP
FETCH C1 INTO FNAME, LNAME, NAME; -- try to read the row and put the values into our variables
IF C1%NOTFOUND -- check if we got a row
THEN
EXIT;
END IF;
DBMS_OUTPUT.put_line ('Name: ' || FNAME || ' ' || LNAME); -- work with is.
TITLE := NAME;
DBMS_OUTPUT.put_line ('Title: ' || TITLE);
END LOOP;
CLOSE C1;
END;
I have tried to execute code the instruction below. But somehow I cant get it working. I am new to PL/SQl. Any hint will be valuable thanks
The rank table has:
rankID Number
name Varchar2(255 BYTE)
/*
Write PL/SQL program (anonymous block) that prints out a list of all ranks (ID
and name) for all rank ID from 100 to 110. If a rank ID (xxx) does not appear
in the rank table the program should print out: NO RANK AVAILABLE for ID:
xxx
*/
--set serveroutput on
DECLARE
rank_id NUMBER;
rank_name VARCHAR2(255);
loopcount NUMBER;
BEGIN
loopcount :=100;
FOR k IN 100..110
LOOP
SELECT rankID, name
INTO rank_id, rank_name
FROM rank
WHERE rankID=loopcount;
DBMS_OUTPUT.PUT_LINE(rank_id||' '|| rank_name);
loopcount := loopcount + 1;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO RANK AVAILABLE for ID: '||rank_id);
END;
/
Below is what I am getting. Its not working the way it should
Server output:
100 Chefpilot
NO RANK AVAILABLE for ID: 100
DECLARE executed successfully
Execution time: 0.26s
No real need for a loop in this case, you can do this with an outer join, a case statement and a numbers table:
WITH CTE (RankId) AS (
SELECT 100 RankId
FROM DUAL
UNION ALL
SELECT RankId + 1
FROM CTE
WHERE RankId < 110
)
SELECT t.RankId, COALESCE(r.Name, 'Does not exist') Name,
CASE
WHEN r.RankId IS NULL THEN 'No rank available for: ' || t.RankId
ELSE r.RankId || ' ' || r.Name
END Description
FROM CTE t
LEFT JOIN rank r ON t.RankId = r.RankId
ORDER BY t.RankId
SQL Fiddle Demo
#sgeddes's approach is better, but to explain what you are seeing, if you did want to use your mechamism then you'd need to catch the exception in an inner block. At the moment the exception handler is outside the loop, so the first error you see terminates the loop. With an inner block:
DECLARE
rank_id NUMBER;
rank_name VARCHAR2(255);
BEGIN
FOR loopID IN 100..110
LOOP
BEGIN -- inner block
SELECT rankID, name
INTO rank_id, rank_name
FROM rank
WHERE rankID=loopID;
DBMS_OUTPUT.PUT_LINE(rank_id||' '|| rank_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO RANK AVAILABLE for ID: '||loopID);
END; -- inner block
END LOOP;
END;
/
Now if the ID doesn't exist the exception is caught, the message is printed, and the inner block exits; which means the loop continues at the next iteration. (I've also removed the extra loopcount and consistently used a loopID for the for and both references).
i want to create a trigger that will
select staffid where dateread = this months date
to count the number of a staffID
if the count for that staff during that month is more than 5
a stop will be issued.
here is what i did that screws me up
I would like to know is this logically correct?
and here are my compiler log errors
This is my required outcome:
Meter readers can only read a maximum of 5 meters in any given calendar month
My Reading Table has
StaffID
MeterID
DateRead
ReadinID (PK)
Here is the error text:
Error(5,7): PL/SQL: SQL Statement ignored Error(5,27):
PL/SQL:ORA-00923: FROM keyword not found where expected
C:\Users\ChrisPin\AppData\Roaming\SQL Developer\assgn2 sat4.sql Error(5,7):
PL/SQL: SQL Statement ignored Error(5,27):
PL/SQL: ORA-00923: FROM keyword not found where expected
Here is the trigger code:
CREATE OR REPLACE TRIGGER LIMIT_5_REDINGS
BEFORE UPDATE OR INSERT ON reading
FOR EACH ROW
DECLARE
ReadingCount INTEGER; --# of depts for this employee
max_read INTEGER := 5; --max number of depts per employee.
BEGIN
select Reading COUNT(*) into ReadingCount
from (select *
from Reading
where to_char(DateRead, 'YYYY-MM') = to_char(sysdate, 'YYYY-MM'))
WHERE STAFFID = :NEW.STAFFID;
IF :OLD.STAFFID = :NEW.STAFFID THEN
RETURN;
ELSE
IF ReadingCount >= max_read THEN
RAISE_APPLICATION_ERROR (-20000,'Employees are limited to a max of two departments.');
END IF;
END IF;
END;
It's in this line
select Reading COUNT(*) into ReadingCount
should be
select COUNT(*) into ReadingCount