Using PL/SQL to develop relationship among two tables? - sql

I am new to the PL/SQL language and I am working on a problem and was looking for advice to the right direction to go. Any help would be greatly appreciated!
I have an Employee table and a Building table.
The Employee table consists of Employee_ID, Last_Name, First_Name, and Job(F/P) full or part time.
The Building table consists of Employee_ID and Building_location.(This is the location where the employee works from) either a home or office.
I want the user to type in the last_name from user input. As a result I am looking for the information from the employee: Employee Name, Job (full or part employee) and the building_location. For example, I want my output to look something like this if the user typed in Johnson:
Employee Name Job Position Building Location
====================================================
Andre Johnson Part Time Home Office
Nick Johnson Full Time Downtown Office
If a user types in any string I want to list all Employees who work for the company. But if the name is on the Employee table like 'Johnson' is then it only displays Johnson. Would I need to add in another SELECT clause?
At this point I put together some code that checks if the employee works for the company or not. I am looking to build from this.
ACCEPT p_1 PROMPT 'Please enter the last name of the employee:'
DECLARE
v_name employee.last_name%TYPE := '&p_1';
v_lname employee.last_name%TYPE;
v_fname employee.first_name%TYPE;
v_jobpos employee.job%TYPE;
v_buildtype building.building_type%TYPE;
v_count NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM employee
WHERE UPPER(last_name) = UPPER(v_name);
IF v_count = 0 THEN
DBMS_OUTPUT.PUT_LINE('EMPLOYEE NAME Job Position Building Location');
DBMS_OUTPUT.PUT_LINE('----------------------------------------------------' );
for i in (select * from employee order by last_name) loop
DBMS_OUTPUT.PUT_LINE(i.last_name || ', ' || i.first_name)
end loop;
ELSE
for i in (select * from employee order by last_name) loop
DBMS_OUTPUT.PUT_LINE(i.last_name || ', ' || i.first_name);
end loop;
END IF;
END;

You can do this. The simplest way might be like this:
...
IF v_count = 0 THEN
DBMS_OUTPUT.PUT_LINE(v_name || ' Does not work here.');
/* New code starts here! */
DBMS_OUTPUT.PUT_LINE('Employees are:');
for r in (select * from employees) loop
dbms_output.put_line(r.last_name||', '||r.first_name);
end loop;
/* New code ends here! */
ELSE
...
end if;
end;

Related

How to update a table data if a data from another table is more than a value

I have three tables:
EMPLOYEE (ssn, salary).
WORKS_ON (essn, pid, hours).
PROJECT (pid, pname).
Example data for EMPLOYEE:
insert into EMPLOYEE values('1011', 1000)
insert into EMPLOYEE values('1012', 1200).
Example data for WORKS_ON
insert into WORKS_ON values('1011',80, 60)
insert into WORKS_ON values('1012',90, 40).
Example data for PROJECT
insert into PROJECT values(80, A)
insert into PROJECT values(90, B).
I need to create a stored procedure and if the works hour exceeds 50 then set his/her salary by 10%.
This is what I have done, I had a problem with UPDATE(maybe my whole code is wrong) and I have tried many times but still getting issues, please help me
CREATE OR REPLACE PROCEDURE employee_details(p_ssn IN CHAR) AS
v_ssn employee.ssn%TYPE;
v_sal employee.salary%TYPE;
w_hours works_on.hours%TYPE;
BEGIN
SELECT ssn, salary, hours
INTO v_ssn, v_sal, w_hours
FROM employee NATURAL JOIN works_on
WHERE ssn = p_ssn
AND ssn = essn;
DBMS_OUTPUT.PUT_LINE('Employee_ssn :' || v_ssn);
DBMS_OUTPUT.PUT_LINE('Employee_sal :' || v_sal);
DBMS_OUTPUT.PUT_LINE('Work_hours :' || w_hours);
IF w_hours > 60.0 THEN
v_sal := v_sal + (v_sal * .1);
END IF;
UPDATE employee
SET salary = v_sal
WHERE ssn = essn
AND ssn = p_ssn;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No data found.');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Many rows fetched.');
END;
Error:
17/1 PL/SQL: SQL Statement ignored
19/13 PL/SQL: ORA-00904: "ESSN": invalid identifier
My expected result should be
Employee_ssn : 1011
Employee_sal : 1100
Work_hours : 60
THANK YOU!
You have 3 structural issues preventing with your code from functioning:
The select statement has a predicate essn=ssn but your table
description for work_hours contains column name emp_ssn. So the column essn does not exist. This is the direct cause of you error: invalid identifier basically means a column does not exist.
Still on your select, you use the predicate employee natural join works_on. This will cause the query to never return rows. NATURAL JOIN matches on all columns with the same name in the referenced tables. However, there are no common columns names in the referenced tables.
The update statement uses the predicate ssn=essn. This would
result in the same error you current get as the column/variable essn does not exist.
Furthere, even correcting NATURAL JOIN to the needed INNER JOIN (or renaming a emp_ssn to essn) your sample data would not return any rows as you have no matching data value in the referenced tables.
Other than using dbms_output to display values(for debug, purposes I presume or part of the homework) there is no reason for the "select, test and set, update" sequence you have. As it stands you will update the row even if salary is NOT recalculates. But the entire thing is unnecessary. It can be accomplished with a single update statement. If necessary do that the calling routine.
You also have a consistency problem between code and expected results. Your code looks for w_hours > 60.0 but your expenced results and sample data indicate w_hours >= 60.0. Even where your description says 50. So reduce the procedure to it bare requirements. (Note: to provide the Exception Message tested the number of rows processed and raised the appropriate error to be handled by the calling routine.
Finally the procedure name itself says nothing about what your procedure actually does. This in (IMHO) a very poor practice.
So try:
create or replace
procedure increase_salary_for_excessive_hours(p_ssn in employee.ssn%type) as
begin
update employee e
set salary = salary * 1.1
where e.ssn = p_ssn
and exists (select null
from works_hour w
where w.emp_ssn = e.ssn
and w.hours >= 60
);
if sql%rowcount < 1 then
raise no_data_found;
elsif sql%rowcount > 1 then
raise too_many_rows;
end if;
end increase_salary_for_excessive_hours;
/
NOTE: The exception too_many_rows will only be raised when the employee.ssn is duplicated. Will never happen if there is a proper unique (or PK) constraint on that column. The routine will not raise too_many_rows for multiple qualifying work_hours but only update the employee salary once (but heed the warning by #astentx ).
See Demo here. Demo includes a test driver that generates the DBMS_OUTPUT. DBMS_OUTPUT is fine for test/debug but not in a production environment.
Which brings up a final point. If either exception is raised and handled as currently written, it will write the message, but the calling routine would never know about it and think everything is successful. I suggest you take some time to understand whet the EXCEPTION section actually does and means.
Procedure you wrote uses tables and columns that don't exist (according to what you posted).
table works_on should be works_hour
you used essn; what is it?
sample data mismatch; if SSN = 1011, then you can't expect it to be joined to 101, so it doesn't raise ORA-01422 (tag you used) (too_many_rows) but no_data_found.
I tried to fix what I thought that should be fixed. Then the procedure compiles.
SQL> CREATE OR REPLACE PROCEDURE employee_details(
2 p_ssn IN CHAR
3 )AS
4
5 v_ssn employee.ssn%TYPE;
6 v_sal employee.salary%TYPE;
7 w_hours works_hour.hours%TYPE; -- works_hour, not works_on
8 BEGIN
9 SELECT ssn,
10 salary,
11 hours
12 INTO
13 v_ssn,
14 v_sal,
15 w_hours
16 FROM employee
17 JOIN works_hour on emp_ssn = ssn
18 WHERE ssn = p_ssn;
19 --AND ssn = v_ssn; -- essn;
20
21 dbms_output.put_line('Employee_ssn :' || v_ssn);
22 dbms_output.put_line('Employee_sal :' || v_sal);
23 dbms_output.put_line('Work_hours :' || w_hours);
24 IF w_hours > 60.0 THEN
25 v_sal := v_sal +(v_sal *.1);
26 END IF;
27
28 UPDATE employee
29 SET
30 salary = v_sal
31 WHERE ssn = v_ssn -- essn
32 AND ssn = p_ssn;
33
34 EXCEPTION
35 WHEN no_data_found THEN
36 dbms_output.put_line('No data found.');
37 WHEN too_many_rows THEN
38 dbms_output.put_line('Many rows fetched.');
39 END;
40 /
Procedure created.
SQL>
It even works now:
SQL> set serveroutput on
SQL>
SQL> exec employee_details('1011');
Employee_ssn :1011
Employee_sal :1000
Work_hours :60
PL/SQL procedure successfully completed.
SQL> select * From employee;
SSN SALARY
---- ----------
1011 1000
1012 1200
SQL>
I don't know whether that's what you wanted, though.

how to print the each column separated by dot in pl/sql

I am a beginner to PL/SQL. Consider I have three tables: emp, organization, emp_detail. Refer the image to know the table Schema, and result format.
I can get the result by joining these three tables based on the emp_id but I don't know how to print the dots (....) in the result.
Here's how; I don't have your tables (and don't feel like creating ones, as you didn't feel like providing test case yourself) so I used Scott's EMP.
If you don't care about nice alignment, omit RPAD function call and just concatenate desired number of dots.
SQL> set serveroutput on
SQL> begin
2 for cur_r in (select empno, ename, job from emp where deptno = 10) loop
3 dbms_output.put_line(cur_r.empno ||'.....'||
4 rpad(cur_r.ename, 15, '.') ||
5 cur_r.job
6 );
7 end loop;
8 end;
9 /
7782.....CLARK..........MANAGER
7839.....KING...........PRESIDENT
7934.....MILLER.........CLERK
PL/SQL procedure successfully completed.
SQL>
select cast(emp_id as varchar2(5)) || '....' || emp_name || '....' || organisation || '....'
|| cast(salary as varchar2(10))
from emp
join organisation on emp.emp_id=organisation.emp_id
join emp_details on emp.emp_id=emp_details.emp_id

Oracle Block Statement DBMS output syntax

I made this account just for this question because I'm really not sure how to word it properly for google to pick up on it. So here goes...
I was working on a query for a group project that I was assigned in my database II course. I made 2 and was working on a third when I wanted to see if I could do something like the following.
declare
Emp_ID := 03;
Salary_Increment := 2.0 --constants do not change
Salary_ID := 03;
Current_Salary := 47500
Updated_Salary := Current_Salary * 2.0
BEGIN
dbms_output.put_line('The Employee with the ID of' +Emp_ID 'will receive a 2% bonus.');
dbms_output.put_line('This Employees new salary will be: ' +Updated_Salary ');
end;
I attempted to do this previously but with a more simple code snippet. Figured I would see if I could do this just to simplify how much I have to type out.
TL;DR - Can I use a reference like +Emp_ID in a Oracle SQL dbms output?
In oracle, There are two ways of concating the string.
using || operator.
Like this.
dbms_output.put_line('The Employee with the ID of' || Emp_ID || ' will receive a 2% bonus.');
using CONCAT method.
Like this:
dbms_output.put_line(CONCAT(CONCAT('The Employee with the ID of', Emp_ID), ' will receive a 2% bonus.'));
Note that CONCAT uses only two parameter as input. So you need to use it multiple times to concat more than 2 strings.
Cheers!!
The string concatenation operator is || in PL/SQL, so you'd want to write
dbms_output.put_line('The Employee with the ID of' || Emp_ID || ' will receive a 2% bonus.');

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 fetch the deleted records?

How to fetch the deleted records. I am using Oracle 10g.
Do you have a backup before the deletion? ...or you could try using flashback (SELECT AS OF)
http://docs.oracle.com/cd/E11882_01/appdev.112/e41502/adfns_flashback.htm#ADFNS01003
Hope I've understood your question correctly.
Do some research into the RETURNING clause: https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/returninginto_clause.htm
https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/collections.htm#BABHDGIG
The link has a fully-functional example with an UPDATE statement. I'm sure if you spent some time on it you can figure out how to make it work with a DELETE statement as well.
DECLARE
TYPE EmpRec IS RECORD (last_name employees.last_name%TYPE,
salary employees.salary%TYPE);
emp_info EmpRec;
emp_id NUMBER := 100;
BEGIN
UPDATE employees SET salary = salary * 1.1 WHERE employee_id = emp_id
RETURNING last_name, salary INTO emp_info;
DBMS_OUTPUT.PUT_LINE('Just gave a raise to ' || emp_info.last_name ||
', who now makes ' || emp_info.salary);
ROLLBACK;
END;
/