Triggers in PL-SQL - sql

I have two tables:
create table speciality(
major varchar2(30),
total_credits number,
total_students number);
create table students(
id number(5) primary key,
first_name varchar2(20),
last_name varchar2(20),
major varchar2(30),
current_credits number(3));
I want to create a trigger that carries the name UpdateSpeciality that deletes, updates and inserts into speciality table right when the same operation happens on students table.
This is how the speciality table should look after the following:
SQL> INSERT INTO STUDENTS(id, first_name, last_name, major, current_credits) values(10001, 'sam', 'ali', 'computer science', 11);
SQL>INSERT INTO STUDENTS(id, first_name, last_name, major, current_credits) values(10002, 'kevin', 'mark', 'MIS', 4);
SQL>INSERT INTO STUDENTS(id, first_name, last_name, major, current_credits) values(10003, 'robert', 'jack', 'computer science', 8);
How can I solve this? I don't know how to connect the two tables.
Should I use stored procedures?
CREATE OR REPLACE TRIGGER UpdateSpeciality
after insert or delete or update on students
for each row
begin
if inserting /* this is how far i got */

Something like this should do it. With some assumtions :)
CREATE OR REPLACE TRIGGER UpdateSpeciality
after insert or delete or update on students
for each row
declare
cursor c_spec(sMajor speciality.major%type) is
select * from speciality
where major = sMajor
for update;
r_spec c_spec%ROWTYPE;
begin
if inserting then
open c_spec(:new.major);
fetch c_spec into r_spec;
if c_spec%FOUND then
update speciality set
total_credits = total_credits + :new.current_credits,
total_students = total_students + 1
where current of c_spec;
else
insert into speciality(major, total_credits, total_students) values (:new.major, :new.current_credits, 1);
end if;
close c_spec;
elsif updating then
open c_spec(:new.major);
fetch c_spec into r_spec;
if c_spec%FOUND then
update speciality set
total_credits = total_credits + :new.current_credits - :old.current_credits
where current of c_spec;
else
insert into speciality(major, total_credits, total_students) values (:new.major, :new.current_credits, 1);
end if;
close c_spec;
elsif deleting then
open c_spec(:old.major);
fetch c_spec into r_spec;
if c_spec%FOUND then
update speciality set
total_credits = total_credits - :old.current_credits ,
total_students = total_students - 1
where current of c_spec;
if r_spec.total_students = 1 then
delete from speciality where major = :old.major;
end if;
end if;
close c_spec;
end if;
end;

Related

How to prevent Varchar Appending Duplicate Information

I'm learning PL/SQL stored functions in Oracle SQL Developer and I'm having trouble trying to create a function that returns a row that does not contain duplicate information.
Schema information:
APPLICANT which has an ANUMBER (applicant number)
SPOSSESSED (skill possessed) which has an SNAME (skill name) and ANUMBER (to link applicants to skills)
POSITION which has a PNUMBER
SNEEDED which has SNAME and PNUMBER to link required skills to a position
The task essentially is to have a function that takes an anumber and returns a string with the positions available that the applicant has the skills for.
My current code:
CREATE OR REPLACE FUNCTION applicant_position_titles(anum NUMBER) RETURN VARCHAR IS
course_list VARCHAR(300);
first_position POSITION.TITLE%TYPE;
current_position POSITION.TITLE%TYPE;
BEGIN
course_list := '';
FOR spossessed_cursor IN (SELECT sname FROM SPOSSESSED WHERE anumber = anum)
LOOP
FOR sneeded_cursor IN (SELECT PNUMBER FROM SNEEDED WHERE spossessed_cursor.sname = sname)
LOOP
FOR position_cursor IN (SELECT TITLE FROM POSITION WHERE sneeded_cursor.PNUMBER = PNUMBER)
LOOP
course_list := course_list || ' ' || position_cursor.title;
END LOOP;
END LOOP;
END LOOP;
RETURN course_list;
END applicant_position_titles;
/
My Select Statement:
SELECT anumber, applicant_position_titles(anumber)
FROM APPLICANT
WHERE applicant_position_titles(anumber) IS NOT NULL;
The results:
ANUMBER
APPLICANT_POSITION_TITLES(ANUMBER)
1
lecturer lecturer lecturer senior lecturer lecturer professor professor professor
I would like to know how I can optimise this code function to prevent from duplicating positions.
For example for the first row I would like column 2 to have:
ANUMBER
APPLICANT_POSITION_TITLES(ANUMBER)
1
lecture senior lecturer professor
I know that it is happening because each skill can be applied to multiple positions but I don't know what the best way of fixing this issue would be. I've tried a few things such as storing and comparing VARCHARS but nothing seems to be working.
I'm still learning SQL, please go easy on my disgusting code. Thankyou :)
#OldProgrammer is right u can do it in one select statement.
Here is my sample table and data:
create table SPOSSESSED (sname varchar2(30), anumber number);
create table sneeded (sname varchar2(30), pnumber number);
create table "position" (title varchar2(30), pnumber number);
-------------------------------------
insert into SPOSSESSED values('name',1);
insert into SPOSSESSED values('name2',1);
insert into SPOSSESSED values('name3',1);
--------------------------------------
insert into sneeded values ('name',111);
insert into sneeded values ('name2',222);
insert into sneeded values ('name3',222);
--------------------------------------------
insert into "position" values ('lecturer',111);
insert into "position" values ('professor',222);
And here is that one select statement:
select sp.anumber, LISTAGG(p.title,' ') WITHIN GROUP (ORDER BY sp.anumber) AS title
from spossessed sp,sneeded sn,"position" p
where
sp.sname=sn.sname and
p.pnumber=sn.pnumber
group by sp.anumber
Result:
ANUMBER | TITLE
1 lecturer professor
Edit that removes same position title:
select anumber, LISTAGG(title,' ')
WITHIN GROUP (ORDER BY anumber) AS TITLE
from (
select distinct sp.anumber, p.title
from spossessed sp,sneeded sn,"position" p
where sp.sname=sn.sname and p.pnumber=sn.pnumber
)
group by anumber;

REPLACE procedure on oracle 12c

When selecting the employee name and salary of 1500 if there is 1 employee, insert into tMessage the employee's name and the salary amount. If there are 2 or more employees, insert multiple lines. If there are no employees then insert into tMessage without any staff.
create or REPLACE procedure temployees_i(
v_aempname in tregions.aregid%type,
v_aempsal number(3):=1500),
)
is
BEGIN
select lastname,salary from employees ;
EXCEPTION
WHEN SALARY=v_aempsal THEN
insert into tMessage values ('have qualified staff'|| lastname ||'of' salary);
WHEN SALARY!=v_aempsal THEN
insert into tMessage values ('There are no employees
');
END;
I think that's you want.
CREATE OR REPLACE PROCEDURE temployees_i( v_aempsal number := 1500 )
IS
TYPE emprec IS RECORD (empname VARCHAR2(50), salary NUMBER(9,2));
TYPE emp_tab IS TABLE OF emprec;
empv emp_tab;
BEGIN
SELECT lastname, salary bulk COLLECT INTO empv from employees WHERE
SALARY = v_aempsal ;
IF empv.count != 0 then
FORALL i in 1..empv.COUNT
insert into tMessage values ('have qualified staff '||
empv(i).empname ||'of ' ||empv(i).salary);
ELSE
INSERT INTO tMessage VALUES ('There are no employee');
END IF;
COMMIT;
END;

How to insert foreign key from pre-generated table?

I have 3 tables:
create table customer
(
customer_id integer primary key,
customer_first_name varchar2(50) not null,
customer_surrname varchar2(50) not null,
phone_number varchar2(15) not null,
customer_details varchar2(200) default 'There is no special notes'
);
create table place
(
table_number integer primary key,
table_details varchar2(200) default 'There is no details'
);
create table booking
(
booking_id integer primary key,
date_of_booking date,
number_of_persons number(2) not null,
customer_id integer not null,
foreign key(customer_id) references customer(customer_id),
table_number integer not null,
foreign key(table_number) references place(table_number)
);
I have to generate customer table using this kind of generator:
set SERVEROUTPUT on format wrapped;
set define off;
drop sequence customer_seq;
drop sequence place_seq;
--CUSTOMER TABLE INSERT ROW GENERATOR
create sequence customer_seq START WITH 1 INCREMENT BY 1 NOMAXVALUE;
CREATE OR REPLACE TRIGGER customer_id_trigger
BEFORE INSERT ON customer
FOR EACH ROW
BEGIN
SELECT customer_seq.nextval INTO :new.customer_id FROM dual;
END;
/
DELETE FROM customer;
DECLARE
TYPE TABSTR IS TABLE OF VARCHAR2(250);
first_name TABSTR;
surrname TABSTR;
qname number(5);
phonenum number(15);
details TABSTR;
BEGIN
first_name := TABSTR ('Jhon','Poul','Jesica','Arnold','Max','Teemo','Tim','Mikel','Michael',
'Kristian','Adela','Mari','Anastasia','Robert','Jim','Juana','Adward',
'Jana','Ola','Kristine','Natali','Corey','Chester','Naomi','Chin-Chou');
surrname := TABSTR ('Grey','Brown','Robins','Chiho','Lee','Das','Edwins','Porter','Potter',
'Dali','Jordan','Jordison','Fox','Washington','Bal','Pitney','Komarowski',
'Banks','Albra','Shwiger');
details := TABSTR ('Exellent Customer','Good Customer','Always drunked','Left big tips',
'Bad Customer','Did not pay last bill','New Customer','VIP client');
qname := 100; — CHANGE THIS TO MANAGE HOW MANY ROWS YOU WANT TO BE ADDED
FOR i IN 1..qname LOOP
phonenum := dbms_random.value(111111111,999999999);
INSERT INTO customer VALUES (NULL, first_name(dbms_random.value(1,25)),
surrname(dbms_random.value(1,20)), phonenum, details(dbms_random.value(1,8)));
END LOOP;
DBMS_OUTPUT.put_line('Customers done!');
END;
/
--TABLE INSERT
DELETE FROM place;
create sequence place_seq start with 1 increment by 1;
insert into place values (place_seq.nextval, 'Near the window');
insert into place values (place_seq.nextval, default);
insert into place values (place_seq.nextval, 'Near the door');
insert into place values (place_seq.nextval, 'Near the window');
insert into place values (place_seq.nextval, 'Near the window');
insert into place values (place_seq.nextval, default);
insert into place values (place_seq.nextval, 'Near the door');
insert into place values (place_seq.nextval, 'Big table');
insert into place values (place_seq.nextval, default);
insert into place values (place_seq.nextval, 'Big table');
So the question is how can I insert client_id in "booking" table which have one of the numbers in "customers" table? Because every time I regenerate data in "Customers" table numbers are changing so I should somehow select numbers in an array and then randomly choose one of them from this array. The thing is I don't really know how to select from table to array. Can anybody help?
For PL/SQL version you can use BULK COLLECT and standard sys.odcinumberlist array.
create sequence booking_seq start with 1 increment by 1;
declare
customerIds sys.odcinumberlist;
placeIds sys.odcinumberlist;
number_of_generated_records number := 150; -- number of records to be generated
begin
-- fill the array of customer_id values
select customer_id
bulk collect into customerIds
from customer;
-- fill the array of place numbers
select table_number
bulk collect into placeIds
from place;
for i in 1..number_of_generated_records loop
insert into booking(booking_id,date_of_booking,number_of_persons,customer_id,table_number)
values(
booking_seq.nextval, -- booking_id
trunc(sysdate) + round(dbms_random.value(1,365)), -- date_of_booking
round(dbms_random.value(1,99)), -- number_of_persons
customerIds(round(dbms_random.value(1,customerIds.count))), -- customer_id
placeIds(round(dbms_random.value(1,placeIds.count))) -- table_number
);
end loop;
end;
But, for your case I would prefer pure sql:
insert into booking(booking_id,date_of_booking,number_of_persons,customer_id,table_number)
with
customer_subq as (
select customer_id, row_number() over (order by customer_id) rn from customer
),
place_subq as (
select table_number, row_number() over (order by table_number) rn from place
),
params as (
select 1500 number_of_generated_records,
(select count(1) from customer) customer_count,
(select count(1) from place) place_count
from dual
),
random_numbers as (
select round(dbms_random.value(1,1000)) random_number1,
round(dbms_random.value(1,1000)) random_number2,
round(dbms_random.value(1,1000)) random_number3,
round(dbms_random.value(1,1000)) random_number4
from dual,params
connect by level <= number_of_generated_records
)
select booking_seq.nextval booking_id,
trunc(sysdate) + mod(random_number1,365) date_of_booking,
mod(random_number1,100) number_of_persons,
customer_id,
table_number
from random_numbers,
params,
customer_subq,
place_subq
where mod(random_number1,customer_count) + 1 = customer_subq.rn
and mod(random_number2,place_count) + 1 = place_subq.rn

PL/SQL procedure to insert id to rows having same information

I have contact details data in Oracle table something like below...
I want to insert new column to assign same ID to the contacts having matching information , i.e. based on group of lastname, firstname and (phone and/or email)
Output should look like below
I'm new to this forum, so having formatting issues while posting question, please see attached images for easy understanding of my requirement
Looking for PL/SQL procedure to get this done in our huge database
Due to the nature of the data you presented I see no possible way to do it in one SQL or for it to be better tuned.
Script to create the table and populate with your example:
create table TAB_TEST(LAST_NAME VARCHAR2(200),
FIRST_NAME VARCHAR2(200),
PHONE NUMBER,
EMAIL VARCHAR2(200))
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 2058371579, 'ABC#GMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 4479940000, 'ABC#GMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7195739945, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7473430336, null)
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7195739945, 'XYZ#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7475393956, '123#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', null, '123#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, '456#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, '456#HOTMAIL.COM')
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 1168548666, null)
/
INSERT INTO TAB_TEST VALUES
('Ho','Kim', 7473430336, null)
/
ALTER TABLE TAB_TEST ADD ID NUMBER
/
And the script to update
DECLARE
lncount NUMBER := 1;
-- had to create this record in order to add the rowid
TYPE lttab_test IS RECORD(
ROWID VARCHAR2(200),
last_name VARCHAR2(200),
first_name VARCHAR2(200),
phone NUMBER,
email VARCHAR2(200),
id NUMBER);
TYPE lttype
IS TABLE OF LTTAB_TEST;
larecords LTTYPE;
BEGIN
SELECT ROWID,
last_name,
first_name,
phone,
email,
id
bulk collect INTO larecords
FROM tab_test a;
FOR i IN 1..larecords.count LOOP
IF Larecords(i).id IS NULL THEN
FOR j IN 1..i-1 LOOP
IF Larecords(j).phone = Larecords(i).phone
AND Larecords(i).id IS NULL THEN
Larecords(i).id := Larecords(j).id;
exit;
END IF;
END LOOP;
FOR j IN 1..i-1 LOOP
IF Larecords(j).email = Larecords(i).email
AND Larecords(i).id IS NULL THEN
Larecords(i).id := Larecords(j).id;
exit;
END IF;
END LOOP;
IF Larecords(i).id IS NULL THEN
Larecords(i).id := lncount;
lncount := lncount + 1;
END IF;
END IF;
END LOOP;
forall i IN 1..larecords.count
UPDATE tab_test
SET id = Larecords(i).id
WHERE ROWID = Larecords(i).ROWID;
COMMIT:
END;
It isn't the most efficient / pretty code but it works in case you just want sequential numbers
DECLARE
I NUMBER := 0;
BEGIN
ALTER TABLE TABLE
ADD ID NUMBER;
COMMIT;
FOR REC IN (SELECT * FROM TABLE)
LOOP
IF(REC.ID IS NULL) THEN
UPDATE TABLE T SET ID = I
WHERE (T.EMAIL = REC.EMAIL OR T.PHONE = REC.PHONE)
AND ID IS NULL;
COMMIT;
I := I + 1;
END IF;
END LOOP;
END;

Check if there is a location_id and manager_id with the same name (they are from different tables)

So on that code I checked the uniqueness of department_id and department_name. Now I would like to do the same with manager_id and locations_id. Please keep in mind that manager_id is from table EMPLOYEES and locations_id is from table LOCATIONS.
I wonder if I can just continue that statement:
upper(s.department_id) = upper(d.department_id)
OR upper(s.department_name) = upper(d.department_name)
But I think that will not be enough, because it will check only in the DEPARTMENTS table. Not from LOCATIONS and EMPLOYEES like I want. Please advise.
CREATE OR REPLACE PROCEDURE add_dep(p_id NUMBER,
p_name VARCHAR2,
p_mgr NUMBER,
p_loc NUMBER) IS
BEGIN
MERGE INTO departments d
USING (
SELECT
p_id department_id,
p_name department_name,
p_mgr manager_id,
p_loc location_id
FROM dual) s
ON ( upper(s.department_id) = upper(d.department_id)
OR upper(s.department_name) = upper(d.department_name))
WHEN NOT MATCHED THEN
INSERT VALUES (s.department_id, s.department_name, s.manager_id, s.location_id);
IF SQL%ROWCOUNT = 0 THEN
INSERT INTO error_depa VALUES (p_id, p_name, p_mgr, p_loc);
END IF;
END;
EDIT - additional information
I got an error that there are not enough values for LOCATIONS and EMPLOYEES table. But I also tried to make this with exception and no_data_found. Can you modify this code and help me with that please? I would like to check the same for locations_id and manager_id.
create or replace procedure add_de(
p_id NUMBER,
p_name VARCHAR2,
p_mgr NUMBER,
p_loc NUMBER
)
is
v_dummy number;
begin
select 1
into v_dummy
from departments
where department_name = p_name OR DEPARTMENT_ID = p_id ;
insert
into error_depa
values(
p_id,
p_name,
p_mgr,
p_loc
);
exception
when no_data_found
then
insert
into departments
values(
p_id,
upper(p_name),
p_mgr,
p_loc
);
end;
I think that you want something like this:
CREATE OR REPLACE PROCEDURE add_dep(p_id NUMBER,
p_name VARCHAR2,
p_mgr NUMBER,
p_loc NUMBER) IS
BEGIN
MERGE INTO departments d
USING (
SELECT
p_id department_id,
p_name department_name,
p_mgr manager_id,
p_loc location_id
FROM dual) s
ON ( upper(s.department_id) = upper(d.department_id)
OR upper(s.department_name) = upper(d.department_name))
WHEN NOT MATCHED THEN
***INSERT(department_id, department_name,manager_id,location_id)*** VALUES (s.department_id, s.department_name, s.manager_id, s.location_id);
COMMIT;
MERGE INTO employees trg
USING (
SELECT
*
FROM employees
where
manager_id = p_mgr) src
ON ( upper(trg.manager_id) = upper(src.manager_id))
WHEN NOT MATCHED THEN
***INSERT(department_id, ...)*** VALUES (src.department_id, ....);
COMMIT;
MERGE INTO locations trg
USING (
SELECT
*
FROM locations
where
locations_id = p_loc) src
ON ( upper(trg.locations_id) = upper(src.locations_id))
WHEN NOT MATCHED THEN
***INSERT(locations_id, ...)*** VALUES (src.locations_id, ....);
COMMIT;
IF SQL%ROWCOUNT = 0 THEN
INSERT INTO error_depa VALUES (p_id, p_name, p_mgr, p_loc);
END IF;
END;
/