How to retrieve the number of copies of a book PLSQL - sql

Hi I'm trying to write a procedure that retrieves the number of copies of a specific book from a table I have created.
create or replace PROCEDURE book_count(c_isbn IN book_copies.isbn%TYPE)
IS
total number;
BEGIN
SELECT COUNT (*)
FROM book_copies
WHERE isbn = c_isbn;
DBMS_OUTPUT.PUT_LINE('Book count: ' || total);
END book_count;
The error I'm having is that an INTO clause is expected in the SELECT statement, but I can't seem to get it done. Any help is appreciated!

create or replace PROCEDURE book_count(c_isbn IN book_copies.isbn%TYPE)
IS
total number;
BEGIN
SELECT COUNT(*) INTO TOTAL
FROM book_copies
WHERE isbn = c_isbn;
DBMS_OUTPUT.PUT_LINE('Book count: ' || total);
END book_count;

you canĀ“t just use plain SQL in a begin end block. you need to insert as many variables in the into clause, immediatly after the select, that it matches the amount of columns you select.
You either need to do
SELECT COUNT (*)
into total
FROM book_copies
WHERE isbn = c_isbn;
or
for i in (
SELECT COUNT (*) count
FROM book_copies
WHERE isbn = c_isbn;
)
loop
total := i.count;
end loop;
keep in mind that the upper one can throw two exceptions, no_data_found and to_many_rows, while the loop has to notice this "mistake" manually.

Related

in the below code, if last query is not returning reject_count(as there are no rejects in the table), if statement below this query is not executing

1)In the below code, if the last query is not returning "reject_count"(as there are no rejects in the table) if the statement below this query is not executing.
Any alternative for this situation
2)if d_count value is empty, how do i check in the if condition, d_count=r_id
create or replace procedure editor30 (p_title in paper.title%type,round_num in integer)
is
cursor c2 is
select pv.decision,pv.rcomment from paper_review pv,paper p where p.pid=pv.pid and p.title=p_title and pv.round=round_num;
r_decision integer;
p_title1 paper.title%type;
c integer;
r_comment paper_review.rcomment%type;
r_id integer;
d_count integer;
r_num integer;
reject_count integer;
begin
select count(*) into c from paper where title=p_title;
if c=0 then
dbms_output.put_line('No such paper');
else
dbms_output.put_line(p_title||':');
select count(distinct rid) into r_id from paper p,paper_review pv where p.pid=pv.pid and p.title=p_title and pv.round=round_num;
dbms_output.put_line('total no of reviews in round '||round_num||' is :'||r_id);
open c2;
loop
fetch c2 into r_decision,r_comment;
exit when c2%notfound;
dbms_output.put_line(r_decision ||' ' ||r_comment);
end loop;
close c2;
select count(decision) into d_count from paper p,paper_review pv where p.pid=pv.pid and pv.round=round_num and p.title=p_title and pv.decision=1 group by p.title,pv.round;
dbms_output.put_line('total no of accepts :'||d_count);
select count(decision) into reject_count from paper p,paper_review pv where p.pid=pv.pid and pv.round=round_num and p.title=p_title and pv.decision=4 group by p.title,pv.round;
dbms_output.put_line('total no of rejects :'||reject_count);
if d_count=r_id then
dbms_output.put_line('suggestion or the paper is accept');
elsif reject_count>=2 then
dbms_output.put_line('suggestion for the paper is reject');
else
dbms_output.put_line('suggestion to be decided by editor');
end if;
end if;
exception
when no_data_found then
dbms_output.put_line('');
end;
From my point of view, it looks as if you're misinterpreting what's going on.
First of all, remove EXCEPTION handler as it doesn't handle anything; it displays an empty string so you have no idea whether NO_DATA_FOUND happened or not. Or, at least, display something meaningful.
Then, count function will return 0 even there are no rows to be returned:
SQL> select count(*) from emp, dept where 1 = 2;
COUNT(*)
----------
0
SQL>
Therefore, saying that "query is not returning reject_count" or "d_count value is empty" is most probably wrong.
However, those queries have group by clauses which might raise TOO_MANY_ROWS as you're fetching count into scalar variables, and that just won't work:
SQL> select count(*) from emp e, dept d group by e.deptno;
COUNT(*)
----------
24
20
12
There's no way to put 3 values (rows) into reject_count which is declared as integer.
how do i check in the if condition, d_count=r_id
One option is to use NVL function, e.g.
if nvl(d_count, -1) = nvl(r_id, -1) then ...
Finally, do you see anything on the screen? Any output at all? If not, did you set serveroutput on?
For us, it is difficult to test code you wrote as we don't have your tables nor data. Consider creating test case for us (CREATE TABLE and INSERT INTO statements), explaining what result you expect out of it.

How to fix the code for trigger that prevent the insert based on some conditions

I am trying to create a trigger that I have problems with.
The triggers work is to stop the item from inserting.
Here there are 2 tables of student and subjects.
I have to check and prevent inserting when the student has not enrolled in that subject and if he has he will start it from the next semester so currently he is not enrolled there.
Please help me with this.
CREATE OR REPLACE TRIGGER check_student
BEFORE INSERT ON subject
FOR EACH ROW
BEGIN
IF :new.student_no
AND :new.student_name NOT IN (
SELECT DISTINCT student_no,student_name
FROM student
)
OR :new.student_enrollment_date < (
select min(enrolment_date)
from student
where
student.student_name = :new.student_name
and student.student_no = :new.guest_no
group by student.student_name , student.student_no
)
AND :new.student_name = (
select distinct student_name
from student
)
AND :new.student_no = (
select distinct student_no
from student
)
THEN
raise_application_error(-20000, 'Person must have lived there');
END IF;
END;
/
I have to check and prevent inserting when the student has not enrolled in that subject and if he has he will start it from the next semester so currently he is not enrolled there.
Please help me with this.
You probably have a logical prescedence issues in your conditions, since it contains ANDs and ORs without any parentheses. Also, you are checking scalar values against subqueries that return more than one row or more than one column, this will generate runtime errors.
But overall, I think that your code can be simplified by using a unique NOT EXISTS condition with a subquery that checks if all functional conditions are satisfied at once. This should be pretty close to what you want:
create or replace trigger check_student
before insert on subject
for each row
begin
if not exists (
select 1
from student
where
name = :new.student_name
and student_no = :new.student_no
and enrolment_date <= :new.student_enrollment_date
) then
raise_application_error(-20000, 'Person must have livd there');
end if;
end;
/
Note: it is unclear what is the purpose of colum :new.guest_no, so I left it apart from the time being (I assumed that you meant :new.student_no instead).
Your code is quite unclear so I am just using the same conditions as used by you in your question to let you know how to proceed.
CREATE OR REPLACE TRIGGER CHECK_STUDENT BEFORE
INSERT ON SUBJECT
FOR EACH ROW
DECLARE
LV_STU_COUNT NUMBER := 0; --declared the variables
LV_STU_ENROLLED_FUTURE NUMBER := 0;
BEGIN
SELECT
COUNT(1)
INTO LV_STU_COUNT -- assigning value to the variable
FROM
STUDENT
WHERE
STUDENT_NO = :NEW.STUDENT_NO
AND STUDENT_NAME = :NEW.STUDENT_NAME;
-- ADDED BEGIN EXCEPTION BLOCK HERE
BEGIN
SELECT
COUNT(1)
INTO LV_STU_ENROLLED_FUTURE -- assigning value to the variable
FROM
STUDENT
WHERE
STUDENT.STUDENT_NAME = :NEW.STUDENT_NAME
AND STUDENT.STUDENT_NO = :NEW.GUEST_NO
GROUP BY
STUDENT.STUDENT_NAME,
STUDENT.STUDENT_NO
HAVING
MIN(ENROLMENT_DATE) > :NEW.STUDENT_ENROLLMENT_DATE;
EXCEPTION WHEN NO_DATA_FOUND THEN
LV_STU_ENROLLED_FUTURE := 0;
END;
-- OTHER TWO CONDITIONS SAME LIKE ABOVE, IF NEEDED
-- using variables to raise the error
IF LV_STU_COUNT = 0 OR ( LV_STU_ENROLLED_FUTURE >= 1 /* AND OTHER CONDITIONS*/ ) THEN
RAISE_APPLICATION_ERROR(-20000, 'Person must have livd there');
END IF;
END;
/
Cheers!!

How to write a procedure to display the contents of a table in sql

I have a created a procedure as
create or replace procedure availability(num in number) as
begin
delete from vehicle_count;
insert into vehicle_count from select engine_no,count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
end;
/
The procedure was created successfully but now i have to write a separate query to view the contents of vehicle_count as
select * from vehicle_count;
I tried inserting the select statement into the procedure after insertion but it showed a error stating "an INTO clause is expected in the select statement".
How can I create procedure to select the required contents and display it in a single execute statement?
Table schema
vehicle(vehicle_no,engine_no,offence_count,license_status,owner_id);
vehicle_count(engine_no,engine_count);
Check this (MS SQL SERVER)-
create or alter procedure availability(#num as int) as
begin
delete from vehicle_count;
insert into vehicle_count
output inserted.engine_no,inserted.count_engine_no
select engine_no,count(engine_no) as count_engine_no
from vehicle
where engine_no=#num
group by engine_no;
end;
If you want to use a SELECT into a PL/SQL block you should use either a SELECT INTO or a loop (if you want to print more rows).
You could use something like this:
BEGIN
SELECT engine_no, engine_count
INTO v_engine, v_count
FROM vehicle_count
WHERE engine_no = num;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_engine := NULL;
v_count := NULL;
END;
v_engine and v_count are two variables. You can declare them in your procedure, and they will contain the values you want to print.
You said that the procedure you wrote (actually, you posted here) compiled successfully. Well, sorry to inform you - that's not true. This is not a valid syntax:
insert into vehicle_count from select engine_no,count(engine_no)
----
from? Here?
Consider posting true information.
As of your question (if we suppose that that INSERT actually inserted something into a table):
at the beginning, you delete everything from the table
as SELECT counts number of rows that share the same ENGINE_NO (which is equal to the parameter NUM value), INSERT inserts none (if there's no such NUM value in the table) or maximum 1 row (because of aggregation)
therefore, if you want to display what's in the table, all you need is a single SELECT ... INTO statement whose result is displayed with a simple DBMS_OUTPUT.PUT_LINE which will be OK if you're doing it interactively (in SQL*Plus, SQL Developer, TOAD and smilar tools). Regarding table description, I'd say that ENGINE_NO should be a primary key (i.e. that not more than a single row with that ENGINE_NO value can exist in a table).
create or replace procedure availability (num in number) as
l_engine_no vehicle_count.engine_no%type;
l_engine_count vehicle_count.engine_count%type;
begin
delete from vehicle_count;
insert into vehicle_count (engine_no, engine_count)
select engine_no, count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
-- This query shouldn't return TOO-MANY-ROWS if ENGINE_NO is a primary key.
-- However, it might return NO-DATA-FOUND if there's no such NUM there, so you should handle it
select engine_no, engine_count
into l_engine_no, l_engine_count
from vehicle_count
where engine_no = num;
dbms_output.put_line(l_engine_no ||': '|| l_engine_count);
exception
when no_data_found then
dbms_output.put_line('Nothing found for ENGINE_NO = ' || num);
end;
/
There are numerous alternatives to that (people who posted their answers/comments before this one mentioned some of those), and the final result you'd be satisfied with depends on where you want to display that information.

Stored Procedure Functions

I have two tables
Equipment(equipmentid, equipname, equipstatus, maxrentdurationdays)EquipmentID
Equipment_Hire(equipmentID, pickupdate, dropoffdate,clientNo)
I need to create a stored function that takes clientNo as input and adds up 20$per day if the equipment_hire table's dropoffdate-pickupdate > MaxRentDurationdays
I was getting errors while doing this in oracle 11g.
CREATE OR REPLACE FUNCTION Fines
(P_ClientNo Equipment_Hire.ClientNo%TYPE)
RETURN VARCHAR2 IS
V_ClientNo INTEGER;
V_DURATIONDEFD INTEGER;
V_TOTALFINE INTEGER;
BEGIN SELECT ClientNo, MAXDURATIONDAYS
INTO V_FName, V_LName, V_TotalSalary
FROM Equipment, Equipment_hire
WHERE P_CLientNo = Equipment_Hire.ClientNo;
IF Equipment_hire.dropoffdate-equipment_hire.pickupdate >
equipment.maxdurationdays THEN
Equipment_hire.dropoffdate-equipment_hire.pickupdate*20
RETURN Equipment_hire.dropoffdate-equipment_hire.pickupdate*20; ELSE
RETURN 'Date UNSPECIFIED';
END IF; END Fines;
Data: https://www.mediafire.com/?chl6fdcl9cs817w
You can only use two tables equipment, equipment_hire.
Formatting the code helps:
CREATE OR REPLACE FUNCTION Fines(P_ClientNo Equipment_Hire.ClientNo%TYPE)
RETURN VARCHAR2 IS
V_ClientNo INTEGER;
V_DURATIONDEFD INTEGER;
V_TOTALFINE INTEGER;
BEGIN
SELECT ClientNo, MAXDURATIONDAYS
INTO V_FName, V_LName, V_TotalSalary
FROM Equipment, Equipment_hire
WHERE P_CLientNo = Equipment_Hire.ClientNo;
IF Equipment_hire.dropoffdate - equipment_hire.pickupdate > equipment.maxdurationdays THEN
Equipment_hire.dropoffdate - equipment_hire.pickupdate * 20
RETURN Equipment_hire.dropoffdate - equipment_hire.pickupdate * 20;
ELSE
RETURN 'Date UNSPECIFIED';
END IF;
END Fines;
On a quick glance:
You select two columns, but your INTO clause contains three variables.
You are cross joining the tables. Who ever told you to use this out-dated join syntax anyway?
The cross join leads to multiple result rows that you cannot read into simple variables.
What is that first line after the IF supposed to do?
You are using table names and columns outside the query.
Multiplication has precedence over subtraction (or should have). So you are taking a date, multiply it by 20 and subtract the result from another date.
You seem to think that there is just one equipment hire you are dealing with, but can't a user have several hires?

Proper way of checking if row exists in table in PL/SQL block

I was writing some tasks yesterday and it struck me that I don't really know THE PROPER and ACCEPTED way of checking if row exists in table when I'm using PL/SQL.
For examples sake let's use table:
PERSON (ID, Name);
Obviously I can't do (unless there's some secret method) something like:
BEGIN
IF EXISTS SELECT id FROM person WHERE ID = 10;
-- do things when exists
ELSE
-- do things when doesn't exist
END IF;
END;
So my standard way of solving it was:
DECLARE
tmp NUMBER;
BEGIN
SELECT id INTO tmp FROM person WHERE id = 10;
--do things when record exists
EXCEPTION
WHEN no_data_found THEN
--do things when record doesn't exist
END;
However I don't know if it's accepted way of doing it, or if there's any better way of checking, I would really apprieciate if someone could share their wisdom with me.
I wouldn't push regular code into an exception block. Just check whether any rows exist that meet your condition, and proceed from there:
declare
any_rows_found number;
begin
select count(*)
into any_rows_found
from my_table
where rownum = 1 and
... other conditions ...
if any_rows_found = 1 then
...
else
...
end if;
IMO code with a stand-alone SELECT used to check to see if a row exists in a table is not taking proper advantage of the database. In your example you've got a hard-coded ID value but that's not how apps work in "the real world" (at least not in my world - yours may be different :-). In a typical app you're going to use a cursor to find data - so let's say you've got an app that's looking at invoice data, and needs to know if the customer exists. The main body of the app might be something like
FOR aRow IN (SELECT * FROM INVOICES WHERE DUE_DATE < TRUNC(SYSDATE)-60)
LOOP
-- do something here
END LOOP;
and in the -- do something here you want to find if the customer exists, and if not print an error message.
One way to do this would be to put in some kind of singleton SELECT, as in
-- Check to see if the customer exists in PERSON
BEGIN
SELECT 'TRUE'
INTO strCustomer_exists
FROM PERSON
WHERE PERSON_ID = aRow.CUSTOMER_ID;
EXCEPTION
WHEN NO_DATA_FOUND THEN
strCustomer_exists := 'FALSE';
END;
IF strCustomer_exists = 'FALSE' THEN
DBMS_OUTPUT.PUT_LINE('Customer does not exist!');
END IF;
but IMO this is relatively slow and error-prone. IMO a Better Way (tm) to do this is to incorporate it in the main cursor:
FOR aRow IN (SELECT i.*, p.ID AS PERSON_ID
FROM INVOICES i
LEFT OUTER JOIN PERSON p
ON (p.ID = i.CUSTOMER_PERSON_ID)
WHERE DUE_DATA < TRUNC(SYSDATE)-60)
LOOP
-- Check to see if the customer exists in PERSON
IF aRow.PERSON_ID IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Customer does not exist!');
END IF;
END LOOP;
This code counts on PERSON.ID being declared as the PRIMARY KEY on PERSON (or at least as being NOT NULL); the logic is that if the PERSON table is outer-joined to the query, and the PERSON_ID comes up as NULL, it means no row was found in PERSON for the given CUSTOMER_ID because PERSON.ID must have a value (i.e. is at least NOT NULL).
Share and enjoy.
Many ways to skin this cat. I put a simple function in each table's package...
function exists( id_in in yourTable.id%type ) return boolean is
res boolean := false;
begin
for c1 in ( select 1 from yourTable where id = id_in and rownum = 1 ) loop
res := true;
exit; -- only care about one record, so exit.
end loop;
return( res );
end exists;
Makes your checks really clean...
IF pkg.exists(someId) THEN
...
ELSE
...
END IF;
select nvl(max(1), 0) from mytable;
This statement yields 0 if there are no rows, 1 if you have at least one row in that table. It's way faster than doing a select count(*). The optimizer "sees" that only a single row needs to be fetched to answer the question.
Here's a (verbose) little example:
declare
YES constant signtype := 1;
NO constant signtype := 0;
v_table_has_rows signtype;
begin
select nvl(max(YES), NO)
into v_table_has_rows
from mytable -- where ...
;
if v_table_has_rows = YES then
DBMS_OUTPUT.PUT_LINE ('mytable has at least one row');
end if;
end;
If you are using an explicit cursor, It should be as follows.
DECLARE
CURSOR get_id IS
SELECT id
FROM person
WHERE id = 10;
id_value_ person.id%ROWTYPE;
BEGIN
OPEN get_id;
FETCH get_id INTO id_value_;
IF (get_id%FOUND) THEN
DBMS_OUTPUT.PUT_LINE('Record Found.');
ELSE
DBMS_OUTPUT.PUT_LINE('Record Not Found.');
END IF;
CLOSE get_id;
EXCEPTION
WHEN no_data_found THEN
--do things when record doesn't exist
END;
You can do EXISTS in Oracle PL/SQL.
You can do the following:
DECLARE
n_rowExist NUMBER := 0;
BEGIN
SELECT CASE WHEN EXISTS (
SELECT 1
FROM person
WHERE ID = 10
) THEN 1 ELSE 0 INTO n_rowExist END FROM DUAL;
IF n_rowExist = 1 THEN
-- do things when it exists
ELSE
-- do things when it doesn't exist
END IF;
END;
/
Explanation:
In the query nested where it starts with SELECT CASE WHEN EXISTS and after the parenthesis (SELECT 1 FROM person WHERE ID = 10) it will return a result if it finds a person of ID of 10. If the there's a result on the query then it will assign the value of 1 otherwise it will assign the value of 0 to n_rowExist variable. Afterwards, the if statement checks if the value returned equals to 1 then is true otherwise it will be 0 = 1 and that is false.
Select 'YOU WILL SEE ME' as ANSWER from dual
where exists (select 1 from dual where 1 = 1);
Select 'YOU CAN NOT SEE ME' as ANSWER from dual
where exists (select 1 from dual where 1 = 0);
Select 'YOU WILL SEE ME, TOO' as ANSWER from dual
where not exists (select 1 from dual where 1 = 0);
select max( 1 )
into my_if_has_data
from MY_TABLE X
where X.my_field = my_condition
and rownum = 1;
Not iterating through all records.
If MY_TABLE has no data, then my_if_has_data sets to null.