getting most rented movie per category - oracle

I have movie rental database that I'm working with here and I am trying to find the most rented movie per category. I want to print out the title and the category. Here is my block of code:
declare
type listOfCat is varray(10) of varchar2(10);
categories listOfCat;
movCount number(2);
title varchar2(50);
begin
select distinct category bulk collect into categories from movie;
for i in 1.. categories.count loop
select max(count) into movCount from
(select count(r.movie_id) as count, m.title as mov, m.CATEGORY as cat from rental r
join movie m on r.movie_id = m.movie_id
where m.category = categories(i)
group by m.title, m.CATEGORY
);
dbms_output.put_line(movCount || ' ' || categories(i));
end loop;
end;
Now the problem is I don't know how to get the movie title in this case. I tried giving max(count) an alias and doing alias.mov but when adding a group by clause at the end, it gives a an invalid identifier error. Also grouping by the result I get at max(count) will just separate the result again if I have several movies per category. Finally, I also tried putting movCount, mov and cat into arrays and display them in parallel but that does not guarantee me the order would be right.
Movie table has movie_id, title, category, qty_available.
Rental table has movie_id, customer_id, due_date, returned
Customer table has customer_id, name

One way to get a max or min of one attribute and also bring along another field associated with that max or min is to use KEEP. KEEP functions to sort the data by one field, then take the record(s) with the extreme value in that attribute and resolve ties by max or min.
Here's an example with some tables that should be compatible with the query and tables you provided:
Create the test tables:
CREATE TABLE RENTAL(
MOVIE_ID NUMBER
);
CREATE TABLE MOVIE(
MOVIE_ID NUMBER,
TITLE VARCHAR2(64),
CATEGORY VARCHAR2(64)
);
And load some test data;
INSERT INTO MOVIE VALUES(1,'The Fugitive','Thriller');
INSERT INTO MOVIE VALUES(2,'No Country for Old Men','Thriller');
INSERT INTO MOVIE VALUES(3,'The Martian','Sci-Fi');
INSERT INTO MOVIE VALUES(4,'Back To The Future','Sci-Fi');
INSERT INTO MOVIE VALUES(5,'Alien','Sci-Fi');
INSERT INTO RENTAL VALUES (1);
INSERT INTO RENTAL VALUES (2);
INSERT INTO RENTAL VALUES (3);
INSERT INTO RENTAL VALUES (3);
INSERT INTO RENTAL VALUES (5);
INSERT INTO RENTAL VALUES (1);
INSERT INTO RENTAL VALUES (3);
INSERT INTO RENTAL VALUES (4);
INSERT INTO RENTAL VALUES (5);
INSERT INTO RENTAL VALUES (1);
INSERT INTO RENTAL VALUES (4);
INSERT INTO RENTAL VALUES (2);
INSERT INTO RENTAL VALUES (1);
INSERT INTO RENTAL VALUES (3);
INSERT INTO RENTAL VALUES (2);
INSERT INTO RENTAL VALUES (2);
Now if we query to get some initial expectation for the pl/sql block, we can see we have a tie in the Thriller category:
SELECT MOVIE.TITLE, MOVIE.CATEGORY, COUNT(*) AS RENTAL_COUNT FROM MOVIE
INNER JOIN RENTAL
ON MOVIE.MOVIE_ID = RENTAL.MOVIE_ID
GROUP BY MOVIE.TITLE, MOVIE.CATEGORY
ORDER BY 2 ASC, 3 DESC;
TITLE CATEGORY RENTAL_COUNT
The Martian Sci-Fi 4
Alien Sci-Fi 2
Back To The Future Sci-Fi 2
No Country for Old Men Thriller 4
The Fugitive Thriller 4
So we should end up with The Martian in Sci-Fi, but we'll need to resolve that tie in Thriller with KEEP.
Now run the pl/sql block. I modified the query and added the movie title to the printed statement here but it is largely the same. max-count gets the higest number of rentals, and KEEP gets the movie with that number of rentals.
declare
type listOfCat is varray(10) of varchar2(10);
categories listOfCat;
movCount number(2);
movieTitle varchar2(50);
begin
select distinct category bulk collect into categories from movie;
for i in 1.. categories.count loop
SELECT MIN(TITLE) KEEP (DENSE_RANK FIRST ORDER BY COUNT(*) DESC),
MAX(COUNT(*))
INTO movieTitle, movCount
FROM MOVIE
INNER JOIN RENTAL
ON MOVIE.MOVIE_ID = RENTAL.MOVIE_ID
WHERE MOVIE.CATEGORY = categories(i)
GROUP BY MOVIE.TITLE;
dbms_output.put_line(utl_lms.format_message('Category: %s, Most-Rented-Movie:%s, Rental-Count:%s',categories(i),movieTitle,to_char(movCount)));
end loop;
end;
/
Result:
Category: Sci-Fi, Most-Rented-Movie:The Martian, Rental-Count:4
Category: Thriller, Most-Rented-Movie:No Country for Old Men, Rental-Count:4
In this case, MIN(TITLE) resolved the tie between The Fugitive and No Country for Old Men.

Related

I want to update table emp_appointment with data from other table data

I have two tables named emp_appointment and employees. Employees have 2 column named EMPLOYEE_ID1 and SUBSID_ACCOUNT_IDX and table emp_appointment have also 2 column named dep_id and EMPLOYEE_ID1 I want to update table emp_appointment column dep_id with data of employees table from column SUBSID_ACCOUNT_IDX both have same id's so I write this code but not worked
DECLARE
EMP_id EMPLOYEES.EMPLOYEE_ID1%TYPE;
SUBSID_ID EMPLOYEES.SUBSID_ACCOUNT_IDX%TYPE;
CURSOR C_EMP is
SELECT EMPLOYEE_ID1, SUBSID_ACCOUNT_IDX FROM EMPLOYEES WHERE SUBSID_ACCOUNT_IDX = EMPLOYEE_ID1 ;
BEGIN
OPEN C_EMP;
LOOP
FETCH C_EMP into EMP_id,SUBSID_ID ;
UPDATE EMP_APPOINTMENT
SET DEP_ID = SUBSID_ID
WHERE EMPLOYEE_ID1= SUBSID_ID;
EXIT WHEN C_EMP%notfound;
END LOOP;
CLOSE C_EMP;
END;
help needed from Masters Please
First off you should not do this, unless dep_id id necessary as part of a foreign key. What you are doing is just copying data, which is just a bad idea. It exposes you to data inconsistency, what happens when dep_id contains a value that does not exist in employees. Instead you should just Join the tables and select subsid_account_idx from employees. But if you insistant then just use a single update.
update emp_appointment ea
set dep_id = (select subsid_account_idx
from employees e
where e.employee_id1 = ea.employee_id1
);
You can use this :
MERGE INTO emp_appointment e
USING employees. Employees h
ON (h.EMPLOYEE_ID1= e.SUBSID_ID)
WHEN MATCHED THEN
UPDATE SET e.DEP_ID = h.SUBSID_ACCOUNT_IDX ;
and this merge into syntax:
MERGE INTO table1
USING table_reference h
ON (conditions)
WHEN MATCHED THEN
UPDATE SET table1.column1 = h.value1, table1.column2 = h.value2

creating a trigger that updates a table when a column in a different table is updated

For some reason I'm having a hard time fully understanding triggers. For my homework assignment I need to create a table that holds product id, total sales, and total quantity sold for each product (these columns are already in two different tables). Then I create a trigger that updates this table when the orderplaced column from a different table is updated to 1. Not exactly sure where to start. Since the table I created is empty would I do an UPDATE table as the assignment suggests or an INSERT since the columns are empty? If anyone can put me in the right direction I would really appreciate it..
CREATE TABLE bb_sales_sum (
idProduct number(2) NOT NULL,
total number(6,2),
quantity number);
CREATE OR REPLACE TRIGGER BB_SALESUM_TRG
AFTER UPDATE OF orderplaced on bb_basket
FOR EACH ROW
WHEN (NEW.orderplaced = 1)
DECLARE
lv_count Number;
BEGIN
if :new.orderplaced = 1 then
for item in
(select idproduct, (quantity * price) AS total, quantity
from bb_basketitem
where idbasket = :old.idbasket)
loop
select count(*)
into lv_count
from bb_sales_sum where idProduct = item.idproduct;
if lv_count = NULL then
INSERT INTO bb_sales_sum
VALUES (item.idproduct, item.total, item.quantity);
else
update bb_sales_sum
set quantity = item.quantity where
idProduct = item.idproduct;
end if;
end loop;
end if;
END;
/
You may use a MERGE in place of update, which will create a new row if there isn't one already for a given idproduct and updates the quantity and total for those rows which are already available.
CREATE OR REPLACE TRIGGER bb_salesum_trg
AFTER UPDATE OF orderplaced on bb_basket
FOR EACH ROW
WHEN (NEW.orderplaced = 1)
BEGIN
MERGE INTO bb_sales_sum t USING
( select :new.idproduct as idproduct ,
:new.quantity as quantity,
:new.total as total
from dual ) s
ON (s.idproduct = t.idproduct )
WHEN MATCHED THEN UPDATE
SET quantity = s.quantity,
total = s.total
WHEN NOT MATCHED THEN
INSERT (
idproduct,quantity,total)
VALUES
( :new.idproduct,:new.quantity,:new.total );
END;
/
DEMO
It goes basically like this:
You have a table that is recording an individual order. It may have a Product Id, Quantity and a Total or similar columns.
You put your trigger code on this table.
When someone inserts a new record here, you would take the quantity and or total and update the main products table. You will add the new quantity and total to the existing summarized values in the main table where the product id matches.

Fetch data from a table in oracle sql

I've two table in the database, the first one is Person and the second is Pilot. as following:
Person Table:
CREATE TABLE person(
person_id NUMBER PRIMARY KEY,
last_name VARCHAR2(30) NOT NULL,
first_name VARCHAR2(30) NOT NULL,
hire_date VARCHAR2(30) NOT NULL,
job_type CHAR NOT NULL,
job_status CHAR NOT NULL
);
/
INSERT INTO person VALUES (1000, 'Smith', 'Ryan', '04-MAY-90','F', 'I');
INSERT INTO person VALUES (1170, 'Brown', 'Dean', '01-DEC-92','P', 'A');
INSERT INTO person VALUES (2010, 'Fisher', 'Jane', '12-FEB-95','F', 'I');
INSERT INTO person VALUES (2080, 'Brewster', 'Andre', '28-JUL-98', 'F', 'A');
INSERT INTO person VALUES (3190, 'Clark', 'Dan', '04-APR-01','P', 'A');
INSERT INTO person VALUES (3500, 'Jackson', 'Tyler', '01-NOV-05', 'F', 'A');
INSERT INTO person VALUES (4000, 'Miller', 'Mary', '11-JAN-08', 'F', 'A');
INSERT INTO person VALUES (4100, 'Jackson', 'Peter', '08-AUG-11', 'P','I');
INSERT INTO person VALUES (4200, 'Smith', 'Ryan', '08-DEC-12', 'F','A');
COMMIT;
/
Pilot Table:
CREATE TABLE pilot(
person_id NUMBER PRIMARY KEY,
pilot_type VARCHAR2(100) NOT NULL,
CONSTRAINT fk_person_pilot FOREIGN KEY (person_id)
REFERENCES person(person_id)
);
/
INSERT INTO pilot VALUES (1170, 'Commercial pilot');
INSERT INTO pilot VALUES (2010, 'Airline transport pilot');
INSERT INTO pilot VALUES (3500, 'Airline transport pilot');
COMMIT;
/
I'm asked to write a pl/sql block of code that accepts the last name from the user and return the result as following:
1) if the last name is not in the table, it returns all the rows in the table.
2) if the last name is in the table, it shows all of the employee's information.
So far I'm doing well with the code, but I got stuck in the case that there are two employees with the last name. here is the cursor that I wrote:
cursor person_info is
select last_name, first_name, hire_date, job_type, job_status, nvl(pilot_type, '-----------')
from person
full outer join pilot
on person.person_id = pilot.person_id
where upper(last_name) = upper(v_last_name)
group by last_name, first_name, hire_date, job_type, job_status, pilot_type
order by last_name, first_name, hire_date asc;
Logically, there are three cases to be covered:
the first case, when the entered last name is in the table, I return all the rows in the table and that's done.
The second case when there is only one employee with the entered last name, and this case is done as well. The last case when there are more than one employee having the same last name like for example 'Jackson' or 'Smith' in this case, my program crashes and give me the error that my select into statement returns more than one row.
select person_id
into v_n
from person
where upper(last_name) = upper(v_last_name);
if v_n = 1 then
open person_info;
fetch person_info into v_last_name, v_first_name, v_hire_date, v_job_type, v_job_status, v_pilot_type;
Can someone help me in guiding me how to fetch the data correctly? I'm not allowed to create any temporary tables or views.
I'm so sorry for making the problem longer than it should, but I was trying to be as clear as possible in explaining the problem.
Thank you in advance.
if the error is
"ORA-01422 exact fetch returns more than requested number of rows" then I think your answer is here https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:981494932508
If you EXPECT the query to return more than one row, you would code:
for x in ( select * from t where ... )
loop
-- process the X record here
end loop;
Your immediate issue is that you're selecting the matching person_id into a variable, and then seeing if that specific ID is 1. You don't have an actual ID 1 anyway so that check would never match; but it is that querying matching multiple rows that gets the error, as you can't put two matching IDs into a single scalar variable.
The way you've structured it looks like you are trying to count how many matching rows there are, rather than looking for a specific ID:
select count(person_id)
into v_n
from person
where upper(last_name) = upper(v_last_name);
if v_n = 1 then
....
When you do have multiple matches then you will need to use the same mechanism to return all of those as you do when there are no matches and you return all employees. You may find the logic should be in the cursor query rather then in PL/SQL logic. It depends on the details of the assignment though, and how it expects you to return the data in both (or all three) scenarios.
It's also possible you just aren't expected to hit this problem - it isn't clear if the assignment is finding all employees, or only those that are pilots. The issue still exists in general, but with the data you show there aren't any duplicate pilot last names. If you haven't learned about this kind of error yet perhaps you're getting a bit ahead of what your tutor expects.

Counting records in a table and updating a table using a cursor

I have the following tables in Oracle:
Teacher
id_teacher (pk)
number_courses
Course
id_course (pk)
id_teacher (fk)
I would like to create a cursor that updates the number_courses field from the Teacher table by counting the courses a teacher has been assigned to. For what I know I should first declare a cursor like this:
cursor c_teacher IS
select id_teacher from teacher;
And then do a for loop iterating over the results from this cursor and counting the assigned courses, a draft of my solution is:
declare
countC number(2);
cursor c_teacher IS
select id_teacher from teacher;
begin
for data in c_teacher
loop
select count(id_teacher) into countC from Course where id_teacher=data;
--I can output here with a DMBS_OUTPUT only to see if its working, but
--I need to use an UPDATE instruction
end loop;
end;
Don't use a cursor. (Unless this is a dramatic simplification of the actual problem you are facing. Or this is a poorly thought out homework problem. In which case the secret is update where current of)
A single update statement with correlated subquery will do the job:
update Teachers T
set number_courses = (select count(*)
from Courses C
where C.id_teacher = T.id_teacher);
What's likely to be even better, because the values can't get out of synch, is to not store the number of courses in the teachers table and calculate the correct value when needed:
alter table Teachers drop column number_courses;
create view Teachers_VW as
select T.id_teacher
, count(*) as number_courses
from Teachers T
left outer join Courses C on C.id_teacher = T.id_teacher
group by T.id_teacher;

Oracle Inserting or Updating a row through a procedure

I have a table
CREATE TABLE STUDENT
(
ID INTEGER PRIMARY KEY,
FIRSTNAME VARCHAR2(1024 CHAR),
LASTNAME VARCHAR2(1024 CHAR),
MODIFIEDDATE DATE DEFAULT sysdate
)
I am inserting a row of data
insert into STUDENT (ID, FIRSTNAME, LASTNAME, MODIFIEDDATE) values (1,'Scott', 'Tiger', sysdate);
When I have to insert a record of data, I need to write a procedure or function which does the following:
if there is no record for the same id insert the row.
if there is a record for the same id and data matches then do nothing.
if there is a record for the same id but data does not match then update the data.
I am new to oracle. From the java end, It is possible to select the record by id and then update that record, but that would make 2 database calls. just to avoid that I am trying update the table using a procedure. If the same can be done in a single database call please mention.
For a single SQL statement solution, you can try to use the MERGE statement, as described in this answer https://stackoverflow.com/a/237328/176569
e.g.
create or replace procedure insert_or_update_student(
p_id number, p_firstname varchar2, p_lastname varchar2
) as
begin
merge into student st using dual on (id = p_id)
when not matched then insert (id, firstname, lastname)
values (p_id, p_firstname, p_lastname)
when matched then update set
firstname = p_firstname, lastname = p_lastname, modifiedate = SYSDATE
end insert_or_update_student;
instead of procedure try using merge in oracle .
If Values is matched it will update the table and if values is not found it will insert the values
MERGE INTO bonuses b
USING (
SELECT employee_id, salary, dept_no
FROM employee
WHERE dept_no =20) e
ON (b.employee_id = e.employee_id)
WHEN MATCHED THEN
UPDATE SET b.bonus = e.salary * 0.1
DELETE WHERE (e.salary < 40000)
WHEN NOT MATCHED THEN
INSERT (b.employee_id, b.bonus)
VALUES (e.employee_id, e.salary * 0.05)
WHERE (e.salary > 40000)
Try this
To solve the second task - "if there is a record for the same id and data matches then do nothing." - starting with 10g we have additional "where" clause in update and insert sections of merge operator.
To do the task we can add some checks for data changes:
when matched then update
set student.last_name = query.last_name
where student.last_name <> query.last_name
This will update only matched rows, and only for rows where data were changed

Resources