Failed to fill new columns in table from existing ones - oracle

I have cars table. It has a structure like this:
CREATE TABLE cars (
ID NUMBER(38) PRIMARY KEY,
Manufacturer VARCHAR2(26),
Model VARCHAR2(26),
Year NUMBER(38),
Category VARCHAR2(26),
Mileage NUMBER(38),
FuelType VARCHAR2(26),
EngineVolume VARCHAR2(26),
DriveWheels VARCHAR2(26),
GearBox VARCHAR2(26),
Doors VARCHAR2(26),
Wheel VARCHAR2(26),
Color VARCHAR2(26),
InteriorColor VARCHAR2(26),
VIN VARCHAR2(26),
LeatherInterior VARCHAR2(26),
Price NUMBER(38),
Clearance VARCHAR2(26),
Age NUMBER(38),
PriceInTg NUMBER(38)
)
I want to fill age and 'PriceInTg' columns that are filled from other columns. So I have created filler functions for it:
CREATE OR REPLACE FUNCTION age_converter(Year NUMBER)
RETURN NUMBER
IS
v_age Number;
BEGIN
v_age := EXTRACT(YEAR FROM SYSDATE) - Year;
RETURN v_age;
END;
CREATE OR REPLACE FUNCTION price_converter(Price NUMBER, currency_in_tg NUMBER)
RETURN NUMBER
IS
price_in_tg NUMBER;
BEGIN
price_in_tg := Price * currency_in_tg;
RETURN price_in_tg;
END;
To fill these columns automatically I have created triggers:
CREATE OR REPLACE TRIGGER age_filler_trigger
AFTER INSERT ON cars FOR EACH ROW
BEGIN
INSERT INTO cars (age)
VALUES (age_converter(:OLD.year));
END;
CREATE OR REPLACE TRIGGER priceTg_filler
AFTER INSERT ON cars FOR EACH ROW
BEGIN
INSERT INTO cars (age)
VALUES (price_converter(:OLD.price, 430));
END;
But when I am inserting value to the table like this:
INSERT INTO CARS (ID, MANUFACTURER, MODEL, YEAR, CATEGORY, MILEAGE, FUELTYPE, ENGINEVOLUME, DRIVEWHEELS, GEARBOX, DOORS, WHEEL, COLOR, INTERIORCOLOR, VIN, LEATHERINTERIOR, PRICE, CLEARANCE)
VALUES (0,'BMW','M6',2014,'Coupe',33500,'Petrol','4.4','Rear','Tiptronic','4/5','Left wheel','White','Black','','true',39000,'false');
I got this kind of error:
Error starting at line : 6 in command -
INSERT INTO CARS (ID, MANUFACTURER, MODEL, YEAR, CATEGORY, MILEAGE, FUELTYPE, ENGINEVOLUME, DRIVEWHEELS, GEARBOX, DOORS, WHEEL, COLOR, INTERIORCOLOR, VIN, LEATHERINTERIOR, PRICE, CLEARANCE) VALUES (0,'BMW','M6',2014,'Coupe',33500,'Petrol','4.4','Rear','Tiptronic','4/5','Left wheel','White','Black','','true',39000,'false')
Error report -
ORA-04091: table PROJECT.CARS is mutating, trigger/function may not see it
ORA-06512: at "PROJECT.PRICETG_FILLER", line 2
ORA-04088: error during execution of trigger 'PROJECT.PRICETG_FILLER'
How I can handle the problem?

Your insert triggers shouldn't be trying to insert new rows. Instead, they should set the :new values, e.g.
:NEW.year := age_converter(:OLD.year);
I was going to suggest making both AGE and PRICE virtual columns, but these can't use sysdate so calculating age on the fly is a problem. It's a problem anyway though, because any value you set will only reflect the age as of the last time the row was updated, which could be any time in the past and so will never be reliable. It might be better to create a view over the cars table, as then you could include an AGE column that would reflect the time of the query.
The "price in tg" column could be virtual:
priceintg number(38) generated always as (price * 430)

Related

could anyone help me to write a code to this question?

An HR system has an Employee table that holds a row for each employee within the company. Each record in the table has a employee id, employee name and manager column, that holds the id for the employee's manager. Write a trigger so that when an employee record is deleted, the record details need to be inserted into an table called Employee_archive along with the deleted date.
EMPLOYEE:
EMPID NUMBER PRIMARY KEY
EMPNAME VARCHAR2(25)
MANAGERID NUMBER
EMPLOYEE_ARCHIVE:
EMPID NUMBER PRIMARY KEY
EMPNAME VARCHAR2(25)
MANAGERID NUMBER
DELETED_DATE DATE
(Hint: Data is case sensitive. Use '/' to terminate the PLSQL block)
Seems to be pretty simple:
create or replace trigger trg_bd_emp
before delete on employee
for each row
begin
insert into employee_archive (empid, empname, managerid, deleted_date)
values (:old.empid, :old.empname, :old.managerid, sysdate);
end trg_bd_emp;
/
Though, what is that hint supposed to mean? What does letter case have to do with the problem?

What is the right PL/SQL for updating rows without a need to reinsert it?

I new at using PL/SQL and I want the following:
I have this table on Oracle SQLcl
create table Child (
id varchar not null,
name varchar not null,
gender varchar not null,
YearOfBirth number(4) not null,
YearsOfAge number(4) null,
CONSTRAINT Pk primary key (id)
);
And I want a PL/SQL (preferred anonymous) that update field of "YearsOfAge" by minusing 2020 from the "YearOfBirth" field. I could do that but my problem is that the table won't be updated until I insert the PL/SQL block again. So whenever I insert a new row, I have to insert my PL/SQL block again. I want to get the table updated whenever I insert/update a row, without a need to insert this block following a new row.
To be clearer, I just want to insert SL/SQL block one time after creating the table, then get the table's "YearsOfAge" updated whenever I insert/update/delete a row. So when I write "select * from Child;" I need to see the "YearsOfAge" with the new value that computed from subtracting 2020 from "YearOf Birth".
My current PL/SQL is below:
begin
IF INSERTING THEN
update Child set YearsOfAge = 2020 - YearOfBirth;
ELSIF DELETEING THEN
update Child set YearsOfAge = 2020 - YearOfBirth;
ELSE
update Child set YearsOfAge = 2020 - YearOfBirth;
END IF;
END;
/
If you really need to store the age this way, some options are virtual columns, views, and triggers.
Virtual Column
With a virtual column, Oracle will automatically perform the calculation on the fly.
SQL> create table Child
2 (
3 id number not null,
4 name varchar2(10) not null,
5 gender varchar2(10) not null,
6 YearOfBirth number(4) not null,
7 YearsOfAge number generated always as (2020 - yearOfBirth) null,
8 constraint pk_child primary key (id)
9 );
Table created.
SQL> insert into child(id, name, gender, yearOfBirth) values(1, 'A', 'female' , 1990);
1 row created.
SQL> insert into child(id, name, gender, yearOfBirth) values(2, 'B', 'male' , 2000);
1 row created.
SQL> insert into child(id, name, gender, yearOfBirth) values(3, 'C', 'non-binary', 2010);
1 row created.
SQL> select * from child;
ID NAME GENDER YEAROFBIRTH YEARSOFAGE
---------- ---------- ---------- ----------- ----------
1 A female 1990 30
2 B male 2000 20
3 C non-binary 2010 10
View
One downside of virtual columns is that they cannot use functions like SYSDATE, so the year has to be hard-coded. With a view, the expression can reference SYSDATE and will always be up-to-date:
create or replace view child_view as
select id, name, gender, yearOfBirth, extract(year from sysdate) - yearOfBirth yearsOfAge
from child;
Trigger (Warning)
You can also use a trigger to create the value when a row is inserted or updated:
create or replace trigger child_trg
before update or insert on child
for each row
begin
if updating('YEAROFBIRTH') or inserting then
:new.yearsOfAge := extract(year from sysdate) - :new.yearOfBirth;
end if;
end;
/
But in practice, triggers are a pain to maintain. Which leads to the question: why do you want to store this information in the first place?
Good database design should minimize the amount of redundant data. There are always exceptions, but you should have a good reason for those exceptions, like an especially complicated calculation that you don't want others to get wrong, you can't create a PL/SQL function because of an unusual security constraint, etc. Calculating something as trivial as the age may cause more problems than it solves.

getting most rented movie per category

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.

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.

oracle triggers :new context variable

Question:
A Trigger which automatically stores in a separate table called ‘ExcellentSale’ the Sales Agent
name, car model and manufacturer name, each time the agreed price of a
SalesTransaction is more than 20% of the car’s asking price. (Note: You need to create
the ‘ExcellentSale’ table before implementing this trigger. To create the primary key, use a
sequence that starts at 1 and increments by 1).
I am using these tables
Manufacturer(manufacturerID, name, region)
Model(modelNo, name, type, previousModel, manufacturerID)
Car(VIN, dateAcquired, yearBuilt, purchasedPrice, askingPrice,
currentMileage, modelNo)
SalesAgent(agentID, name, DOB)
SalesTransaction(VIN, custID, agentID, dateOfSale, agreedPrice)
Here is my attempt
create sequence generateKey
start with 1
increment by 1;
CREATE TABLE ExcellentSale(
recordNo NUMBER,
agentName VARCHAR2(20) NOT NULL,
modelName VARCHAR2(20) NOT NULL,
manufacturerName VARCHAR2(20) NOT NULL,
PRIMARY KEY(recordNo));
create or replace trigger AutoStore
before insert on SalesTransaction
for each row
declare
SAname varchar2(50);
carModel varchar2(50);
manufacturerName varchar2(50);
askingprice number;
agreedprice number;
begin
select sa.name, mo.name, mu.name, c.askingprice, st.agreedprice
into SAname, CarModel, manufacturerName, askingprice, agreedprice
from manufacturer MU, Model MO, Car C, SalesAgent SA, SalesTransaction ST
where mu.manufacturerid = mo.manufacturerid
and st.vin = c.vin
AND c.vin = :new.vin
AND sa.agentID = :new.agentID;
IF :new.agreedPrice > (1.2 * askingPrice) THEN
INSERT INTO ExcellentSale
VALUES
(generateKey.nextval, agentName, modelName, manufacturerName);
END IF;
end AutoStore;
/
and the error I am getting is
PL/SQL: ORA -00984: Column not allowed here
please help, thank you in advanced.
In the VALUES clause of the insert statement, you have go use the variables that hold the values you need instead of the column names.
INSERT INTO ExcellentSale
VALUES
(generateKey.nextval, SAname, CarModel, manufacturerName);

Resources