How to insert in a table in which trigger is also present? - oracle

For the past few days, I am working on my SQL project 'Supermarket Billing Management System', here I am getting a lot of obstacles while creating this project. Since I am a beginner I am not able to solve my all issues, So please help me!!
Here is my doubt:
I have created a table called 'Payments' and inside it, I have even created a trigger and a procedure, Now I don't know how to insert the values in the Payments table because of trigger and procedure. And I want such a procedure that can add the total of the product's price of a single person and it will store into Final Total, I am not sure that my procedure code is right or not, but it was created successfully. So if there is any problem with my Procedure code then please let me know and please also tell me how can I insert it by giving an example
Paymnets table :
create table Paymnets
(
PayId int primary key, PayDate date,
ProdTotal int,
FinalTotal int,
PayOrdId int references orders,
PayProdId int references Products,
PayCustId int references Customers
);
Products table:
create table Products (
ProdId number primary key,
ProdNum number not null unique,
ProdName varchar2(15),
ProdPrice int,
ProdQuantity int,
ProdCustId int references Customers,
ProdOrdId int references Orders,
ProdStoreId int references Stores
);
Procedure :
create procedure FINAL_TOTAL(C IN NUMBER, T OUT NUMBER) IS BEGIN
UPDATE
Payments
SET
FinalTotal = FinalTotal + ProdTotal
WHERE
PayCustId = C;
Commit;
SELECT
FinalTotal into T
FROM
Payments
WHERE
PayCustId = C;
END;
/
Trigger:
create trigger PROD_TOTAL
AFTER INSERT ON Products
BEGIN
UPDATE Paymnets
SET ProdTotal = (SELECT Products.ProdPrice * Products.ProdQuantity FROM Products);
END;
/
insert statement:
insert into Payments values(1,'2020-10-07',1,1,1);
Well, after inserting this many values I knew that I'll get an error and so I got:
error:not enough values
Here I want to know, how can I insert the values in the Payment table and If my procedure code is wrong then what should I write? So please help me in solving these issues!!

Related

Can you use Object Types in Procedures in Oracle PL/SQL?

Hello fellow programmers. Im currently working on a webshop database for my studying program. Currently im trying to make a procedure which creates an order in the orders table for a customer when he/she/it is beeing created. I am also thinking of putting this into a constructor but since i want to use this functionality twice once when the order reaches a certain status and after creation i want to bundle this functionality in a procedure. I have spend nearly 8 hours of research and testing on this but since the feedback from oracle db on my code is 0 to nothing i cant figure out what is wrong. When i create the procedure it is not flagged as valid and i cant even see the parameters in the parameters tab when i click on the procedure. I hope the code formatting works this is my first post..
This are the types order and Customer which hold a REF to each other
CREATE TYPE ORDER_TYPE AS OBJECT(
Order_Id NUMBER,
Date_of_Creation DATE,
Items ITEM_LIST,
Status REF STATUS_TYPE,
Customer REF CUSTOMER_TYPE
);
CREATE TYPE CUSTOMER_TYPE AS OBJECT(
Customer_Id NUMBER,
Email VARCHAR2(254),
User_Name VARCHAR2(50),
Password VARCHAR2(20),
First_Name VARCHAR2(50),
Last_Name VARCHAR2(50),
Address ADDRESS_TYPE,
Shopping_Cart REF ORDER_TYPE
);
CREATE TABLE Orders OF ORDER_TYPE(Status SCOPE IS Order_Status NOT NULL, Customer NOT NULL)
NESTED TABLE Items STORE AS ORDER_ITEMS_NT_TAB;
ALTER TABLE Orders ADD CONSTRAINT PK_Orders PRIMARY KEY(Order_Id);
CREATE TABLE Customers OF CUSTOMER_TYPE(Customer_Id PRIMARY KEY,
Email NOT NULL,
User_Name NOT NULL,
Password NOT NULL,
First_Name NOT NULL,
Last_Name NOT NULL,
Address NOT NULL);
This is the procedure code. The input should be the customer created or updated. Then i want to insert a new order, i still have to change the id field to guid or uuid so every order will be unique but for testing purpose i just used 1. The item list should be empty at first and the status of the order should be status 1 which stands for "Shopping_Cart" this means that the order is still beeing created and should be displayed as shopping cart in the browser later on. After the insert i want to return the inserted row with the returning into statement so i cant update the customer and set the ref of his shopping cart to the new inserted order. I cant figure out whats wrong im still working on it but i would be greatful for any help.
CREATE PROCEDURE create_customer_order(customer IN CUSTOMER_TYPE) AS
DECLARE
shopping_c NUMBER;
BEGIN
INSERT INTO ORDERS
VALUES(1,CURRENT_DATE ,NEW ITEM_LIST(),(SELECT REF(os) FROM ORDER_STATUS os WHERE VALUE(os).STATUS_ID = 1),REF(customer))
RETURNING Order_Id INTO shopping_c;
UPDATE CUSTOMERS c
SET c.SHOPPING_CART = (SELECT REF(o) FROM ORDERS o WHERE o.ORDER_ID = shopping_c)
WHERE c.CUSTOMER_ID = customer.CUSTOMER_ID;
END;
Feel free to ask questions if something is not clear. Cheers!
I will include a working example.
But first, you did not provide all types. So i assumed them.
I see you have types that refer to eachother. This is basically not a good idea.
For storing data, you could use object types, but you could also use normal data types like number/varchar2. You would need tables orders/order_items/customers. If you want to make changes to the customer_type and your table is already populated with data, changing the type is difficult (what to do with the old data?).
But to come back at your question, here is a working example.
drop type customer_type force;
drop type order_type force;
drop type address_Type force;
drop type status_type force;
drop type item_list force;
create type STATUS_TYPE as object (
status number
);
create type ADDRESS_TYPE as object (
street varchar2(100)
);
create type ITEM_LIST as object (
itemname varchar2(100)
);
CREATE TYPE ORDER_TYPE AS OBJECT(
Order_Id NUMBER,
Date_of_Creation DATE,
Items ITEM_LIST,
Status REF STATUS_TYPE,
Customer REF CUSTOMER_TYPE
);
CREATE TYPE CUSTOMER_TYPE AS OBJECT(
Customer_Id NUMBER,
Email VARCHAR2(254),
User_Name VARCHAR2(50),
Password VARCHAR2(20),
First_Name VARCHAR2(50),
Last_Name VARCHAR2(50),
Address ADDRESS_TYPE,
Shopping_Cart REF ORDER_TYPE
);
alter type order_type compile;
drop table orders;
CREATE TABLE Orders (id number, Status status_type , Customer CUSTOMER_TYPE);
CREATE OR REPLACE PROCEDURE create_customer_order(p_customer IN CUSTOMER_TYPE) AS
shopping_c NUMBER;
BEGIN
dbms_output.enable(null);
INSERT INTO ORDERS (id, status, customer)
VALUES (1, null, p_customer)
RETURNING id INTO shopping_c;
commit;
dbms_output.put_line('id='||shopping_c);
END;
/
--Test
declare
l_customer customer_type;
begin
l_customer := customer_type (Customer_Id => 1
, email=>'a#b.org'
, user_name=>'test'
, password=>'DoyouReallyWantThis'
, first_name=>'first'
, last_name =>'last'
, address=>null --for simplicity
, shopping_cart=>null --for simplicity
);
create_customer_order(p_customer => l_customer);
end;
/
thanks for your answer.. It did not work for me and you also missed the update part in the procedure but still thank you for the effort. i managed to get the procedure working by running every code piece step by step and watching if it fails. Feels like javascript to me lol. I discovered for some reason i cant explain that you cant declare variables under the declare statement and you cant use defined object types as parameters. i could not find anything about it in the documentation. to everyone still interested how i solved the problem this is the code.
CREATE OR REPLACE PROCEDURE create_customer_order(p_customer_id IN NUMBER)
AS
BEGIN
INSERT INTO ORDERS
VALUES(1, CURRENT_DATE, NEW ITEM_LIST(),(SELECT REF(os) FROM ORDER_STATUS os WHERE os.STATUS_ID = 1),(SELECT REF(c) FROM CUSTOMERS c WHERE c.CUSTOMER_ID = p_customer_id));
UPDATE CUSTOMERS c
SET c.SHOPPING_CART = (SELECT REF(o) FROM ORDERS o WHERE DEREF(o.Customer).Customer_Id = p_customer_id AND DEREF(o.STATUS).Status_Id = 1 )
WHERE c.CUSTOMER_ID = p_customer_id;
END;

SQL error - trigger/function may not see it

I was working on my project, here I got this error while inserting some values in a row:
ERROR at line 1: ORA-04091: table SYSTEM.PRODUCTS is mutating,
trigger/function may not see it ORA-06512: at "SYSTEM.PROD_TOTAL",
line 2 ORA-04088: error during execution of trigger
'SYSTEM.PROD_TOTAL'
This is my insert statement:
insert into products
values (1, 1001, 'Medical', 20, 4, 1, 1, 1);
Products table :
create table Products
(
ProdId number primary key,
ProdNum number not null unique,
ProdType varchar2(15),
ProdPrice int,
ProdQuantity int,
ProdCustId int references Customers,
ProdOrdId int references Orders,
ProdStoreId int references Stores
);
Trigger code:
create trigger PROD_TOTAL
after insert ON Products
for each row
begin
update Payments
set ProdTotal = (select Products.ProdPrice * Products.ProdQuantity from Products);
end;
/
And finally my Payment table:
create table Payments
(
PayId int primary key,
PayDate date,
ProdTotal int,
FinalTotal int,
PayOrdId int references orders,
PayProdId int references Products,
PayCustId int references Customers
);
I don't know why I am getting this error, please help me in solving this issue...
A statement level trigger (i.e. without FOR EACH ROW clause) will update always all records in Payments table, I don't think that's needed. For an update of only related products, use this trigger:
create trigger PROD_TOTAL
after insert ON Products
for each row
begin
update Payments
set ProdTotal = :new.ProdPrice * :new.ProdQuantity
WHERE PayProdId = :new.ProdId ;
end;

Oracle PLS-00049: bad bind variable

I'm getting this error when I try to create this trigger. I tried everything but I don't know what seems to be the problem.
Here is the code:
CREATE OR REPLACE TRIGGER after_price_update
AFTER UPDATE
ON Item
FOR EACH ROW
DECLARE new_totalprice INT;
BEGIN
IF :OLD.price <> :new.price THEN
new_totalprice := :old.Quantity * :new.price;
INSERT INTO OrderRecord(OrderRecord_Id, Item_Id, Employee_Id, Reservation_Id, Order_Time, Quantity, TotalPrice)
VALUES(old.OrderRecord_Id, old.Item_Id, old.Employee_Id, old.Reservation_Id, old.Order_Time, old.Quantity, new_totalprice);
END IF;
END;
And the error is:
4/22 PLS-00049: bad bind variable 'OLD.QUANTITY'
The tables look like this:
CREATE TABLE Item (
Item_Id int PRIMARY KEY,
Menu_Id int,
Name varchar2(20),
Description varchar2(120),
Price int,
FOREIGN KEY(Menu_Id) REFERENCES Menu(Menu_Id)
);
CREATE TABLE OrderRecord (
OrderRecord_Id int PRIMARY KEY,
Item_Id int,
Employee_Id int,
Reservation_Id int,
Order_Time date,
Quantity int,
TotalPrice int,
FOREIGN KEY(Item_Id)References Item(Item_Id),
FOREIGN KEY(Employee_Id)References Employee(Employee_Id),
FOREIGN KEY(Reservation_Id)References Reservation(Reservation_Id)
);
Table OrderRecord is not the table being updated: you cannot reference an old value for this table; you need to read this value with some SELECT statement.
IF :OLD.price <> :new.price THEN
new_totalprice := <Quantity> * :new.price;
There is no column Quantity in the table ITEM on which you are applying trigger.
If I understand what you are wanting to do correctly, it looks like you want to update the calculated total price of all existing OrderRecord entries when an Item entry has a price change.
CREATE OR REPLACE TRIGGER after_price_update
AFTER UPDATE
ON Item
FOR EACH ROW
BEGIN
IF NVL(:OLD.Price, 0) <> NVL(:new.Price, 0) THEN
-- Update the child table "OrderRecord" for this item using new price
UPDATE OrderRecord SET TotalPrice = Quantity * :new.Price WHERE Item_Id = :new.Item_id;
END IF;
END;
Note that this trigger is on the parent table, Item, and references two values from the modified record: price and Item_Id, to update the child OrderRecord table.
Also note the use of NVL to watch for nulls because the comparison will not succeed if a null is on either side. Using zero for null is debatable; one could use a value that will never occur such as a negative number.

PL/SQL Stored Procedure to Populate Fact Table

I need to populate this fact table using a PL/SQL stored procedure:
CREATE TABLE SALES_FACTS
(saleDay DATE,
vehicleCode INT,
planID INT,
dealerID INT,
vehiclesSold INT,
grossSalesAmt NUMBER(10),
CONSTRAINT SALE_DAY_FK FOREIGN KEY (saleDay) REFERENCES TIMES(saleDay) ON DELETE CASCADE,
CONSTRAINT VEHICLE_CODE_FK FOREIGN KEY (vehicleCode) REFERENCES VEHICLES(vehicleCode) ON DELETE CASCADE,
CONSTRAINT PLAN_ID_FK FOREIGN KEY (planID) REFERENCES FINANCING_PLANS(planID) ON DELETE CASCADE,
CONSTRAINT DEALER_FK FOREIGN KEY (dealerID) REFERENCES DEALERSHIPS(dealerID) ON DELETE CASCADE,
CONSTRAINT SALES_FACTS_PK PRIMARY KEY (saleDay, vehicleCode, planID, dealerID));
I have been asked to do this by using four nested cursor loops to get every possible combination of the dimension tables' primary keys, along with the total vehicles sold and gross sales amount for each combination.
Also, if the values for vehiclesSold and grossSalesAmount are zero, then a row SHOULD NOT be inserted into the SALES_FACTS table.
Only rows for combinations of the four foreign key columns where there were some vehicles sold should be inserted.
I have created the following code that I hoped would accomplish this:
CURSOR factData IS
SELECT vehicleVin,saleDate,sf.planID,sp.dealerID
COUNT (*) AS vehiclesSold
SUM (s.grossSalePrice) AS grossSalesAmount
FROM SALES s, SALES_FINANCINGS sf, SALESPERSONS sp
WHERE s.saleID = sf.saleID
AND s.salespersonID = sp.salespersonID
GROUP BY vehicleVIN, saleDate, sf.planID, sp.dealerID
HAVING COUNT(*) > 0;
BEGIN
FOR record IN factData
LOOP
INSERT INTO SALES_FACTS (saleDay,vehicleCode,planID,dealerID,vehiclesSold, grossSalesAmount
VALUES (record.saleDate,record.vehicleVin,record.planID,record.dealerID,record.vehiclesSold,record.grossSalesAmount);
END LOOP;
END;
/
However the code executes fine, but I do not get any results when I run a
SELECT COUNT(*) FROM SALES_FACTS;
I have created an SQL Fiddle link here http://sqlfiddle.com/#!4/9708d6/1 since the code for the tables and table population was too much to post on this question. Keep in mind that I only INSERTed about 2-3 rows of data for each table to keep the code somewhat short, however the data that has been inserted should suffice to get this working.
Please let me know where I'm going wrong and what the best way to fix it is! Thanks in advance!
This Ended up doing the trick. Thanks for all of the help to those who commented.
DECLARE
CURSOR sales_data
IS
SELECT vehicleVIN, saleDate, SF.planID, SP.dealerID,
COUNT(*) AS vehiclesSold, SUM(S.grossSalePrice) AS grossSalesAmt
FROM SALES S, SALES_FINANCINGS SF, SALESPERSONS SP, VEHICLES V
WHERE S.saleID = SF.saleID AND S.vehicleVIN = V.vehicleCode AND S.salespersonID = SP.salespersonID
GROUP BY vehicleVIN, saleDate, SF.planID, SP.dealerID
HAVING COUNT(*) > 0;
BEGIN
FOR record IN sales_data
LOOP
INSERT INTO SALES_FACTS (saleDay,vehicleCode,planID,dealerID,vehiclesSold, grossSalesAmt)
VALUES (record.saleDate,record.vehicleVIN,record.planID,record.dealerID,record.vehiclesSold,record.grossSalesAmt);
END LOOP;
END;
/

Oracle trigger that update record after insert

I want update book ant set amount-1, when after insert record to sell table.
create table book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
constraint book_pk primary key(id)
);
create table sell (
id number(3) not null,
date varchar(20),
book_id number(3),
constraint sell_pk primary key(id)
);
I want after insert to table sell record update book table amount-1;
CREATE OR REPLACE TRIGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = book_id
END;
I not know how to get inserted record book id, to update this record in book table.
Try like this,
CREATE OR REPLACE TRIGGER changes_amount_trigger
AFTER INSERT ON sell
FOR EACH ROW
BEGIN
UPDATE BOOK SET amount = amount-1 WHERE id = :new.book_id;
END;
/
Data Model Assumptions:
I am assuming you will register transactions by changing the data in the SELL table through INSERT DML SQL operations. This is also supported by your set up of a DML trigger on SELL to pass its changes as SALES information to the BOOK table. This is workable.
By accident, I tried setting up the trigger a little differently and I'd like to suggest a different approach:
Consider possibly working in the opposite direction: Change book quantities directly on the BOOK table, so a single purchase of book_id = 5 would handle queries that could:
UPDATE book SET amount = amount -1
WHERE id = 5; COMMIT;
Restocking would mean increasing the quantity of available books
by incrementing the AMOUNT value instead.
There are a few additional changes that might tighten up this two-table design and protect the integrity of the data within them for the longer term:
CREATE TABLE book (
id number(3) not null,
name varchar(20),
author varchar(12),
amount number(3) not null,
CONSTRAINT book_pk PRIMARY KEY(id)
);
ALTER TABLE book
ADD CONSTRAINT book_amt_ck CHECK (amount > 0);
ALTER TABLE book
ENABLE CONSTRAINT book_amt_ck;
To prevent negative book amount (quantity) values, a TABLE CHECK CONSTRAINT would prevent the entry of values by means of arithmetic errors in DML operations such as:
UPDATE book SET amount := amount - 1
In the example above, there is no control over decrementing the book inventory even if the quantity on hand has reached 0. Check out a few references on TABLE CHECK CONSTRAINTS to get a better understanding of what it can do for specific design situations.
Here are some design suggestions for the trigger:
Changes in book quantities should be the only triggering data element that affects the SELL table.
The trigger should account for changes in book quantities > 1.
CREATE OR REPLACE TRIGGER orders_after_update
AFTER UPDATE
ON book
FOR EACH ROW
DECLARE
v_amount number;
BEGIN
IF (:new.amount < :old.amount ) THEN
FOR v_amount in 1 .. (:old.amount - :new.amount)
LOOP
INSERT INTO sell (id, date, book_id)
VALUES (sell_seq.nextval, sysdate, :new.id);
COMMIT;
END LOOP;
END IF;
END;
For more information on triggers and their design, check a few instances to get a better understanding of how they are designed and set up.
CREATE SEQUENCE sell_seq
MINVALUE 1
START WITH 1
INCREMENT BY 1
CACHE 20;
We needed a sequence to populate the primary key/index of the SELL table. Oracle Sequences are useful for this purpose.
By watching the table changes with a trigger on the BOOK table, you can use the built in references which already exist when a table trigger fires. For example, BOOK.ID does not require an additional query because a trigger automatically is made aware of the beginning and ending value of each trigger monitored record.
Some useful discussions on triggers are discussed in more detail through an Internet search.
Setting Up a Foreign Key Relationship
Although the trigger will probably keep this relation clean, a Foreign Key relation between elements BOOK.ID and SELL.BOOK_ID would be good, otherwise queries on Sales transactions may yield book sales without any descriptive production information. The following is a reference on Foreign Keys and their use.
CREATE TABLE sell (
id number(3) not null,
date varchar(20),
book_id number(3)
);
ALTER TABLE table_name
ADD CONSTRAINT sell_fk
FOREIGN KEY (book_id)
REFERENCES book(id);
Do you already have records in sell table and want to update the amount in book table ?
if this is your case you can update your book.amount as following:
update book b
set b.amount = b.amount - (select count(*) from sell s where b.id = s.book_id);

Resources