'ORA-00934: group function is not allowed here' while creating a Procedure - oracle

I have created a stored procedure, I was expecting to add the total column which is also a trigger, into FinalTotal.
Code:
Procedure FINAL_TOTAL:
create procedure FINAL_TOTAL(C IN NUMBER,T OUT NUMBER)
IS
BEGIN
UPDATE Products
SET FinalTotal = SUM(Total),
HAVING ProdCustId = C;
Commit;
SELECT SUM(FinalTotal) into T FROM Products WHERE ProdCustId = C;
END;
/
Here, if I write WHERE instead of HAVING then, still it's giving me the same error.
Trigger Total:
CREATE TRIGGER PROD_TOTAL
AFTER INSERT ON Products
BEGIN
UPDATE Products
SET Total = ProdPrice * ProdQuantity;
END;
/
Table Products;
create table Products
( ProdId number primary key,
ProdNum number not null unique,
ProdName varchar2(15),
ProdPrice int,
ProdQuantity int,
Total int,
FinalTotal int,
ProdCustId int references Customers,
ProdOrdId int references Orders,
ProdStoreId int references Stores
);
Error:
5/18 PL/SQL: ORA-00934: group function is not allowed here.
Since I am a beginner, I don't have that much idea, So If anyone knows why this is happening, then please let me know!!

Please note first that you example should be questioned. You typically do not wan't to store a customer Id in a Product table as you often sell one product Id to lot of customers.
I'm therefore illustrating the concepts on a ProductSales table, while leaving only the most relevant attributes.
My main point is do not use triggers and procedures if you may use only plain SQL.
The first concept is the calculation of the Total Price - you may use the virtual column to calculate it
create table ProductSales
( ProdId number primary key,
ProdPrice int,
ProdQuantity int,
Total int generated always as (ProdPrice * ProdQuantity) virtual,
ProdCustId int
);
insert into ProductSales (ProdId, ProdPrice, ProdQuantity,ProdCustId) values (1, 100, 5, 1001);
insert into ProductSales (ProdId, ProdPrice, ProdQuantity,ProdCustId) values (2, 50, 1, 1001);
insert into ProductSales (ProdId, ProdPrice, ProdQuantity,ProdCustId) values (3, 100, 10,1002);
After adding some sample data you see, the Totalcolumn is calculated as expected and defined with the formula in the CREATE TABLE.
select * from ProductSales;
PRODID PRODPRICE PRODQUANTITY TOTAL PRODCUSTID
---------- ---------- ------------ ---------- ----------
1 100 5 500 1001
2 50 1 50 1001
3 100 10 1000 1002
The next feature ist the calculation of the total price per customer. Here I'd recommend to use an analytic function in a view as follows
create view V_ProductSales as
select a.*,
sum(TOTAL) over (partition by ProdCustId) as CustTotal
from ProductSales a;
select * from V_ProductSales;
PRODID PRODPRICE PRODQUANTITY TOTAL PRODCUSTID CUSTTOTAL
---------- ---------- ------------ ---------- ---------- ----------
1 100 5 500 1001 550
2 50 1 50 1001 550
3 100 10 1000 1002 1000
So you get the customer total value from the view wihtout storing it in the table (which would violate the normal forms)
This is my final recommendation - get an introductory SQL book learn basic concepts (such as normalization, ER models, CamelCase Identifiers in Oracle, that price is not always Int datatype etc.) and than start your profesional career.

Related

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;

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

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!!

How do I create the update statement that checks if a vlue is lower than the reorder_point attribute for a product

Utilize the Outdoor Clubs & Product database to create a trigger “product_reorder_au” that is associated with an update operation on the product table. The trigger checks to see whether during the update of the quantity_in_stock attribute, if its value gets lower than the reorder_point attribute value for a product. When this situation occurs, the trigger automatically inserts a new purchase order in the purchase_order table. The new purchase order will use the existing supplier_no attribute value for the product in the product table, and the quantity attribute value will be same as the reorder_qty value for the product in the product table. Save the trigger source as a script file.
This is what the product table looks like
So far this is what I have but I am just generally confused on how to update the table and add in what I am guessing would be an if then statement to only update when the reorderpoint is lower than the quantitiy_in_stock.
CREATE TRIGGER product_reorder_au
AFTER UPDATE OF product
for each row
begin
IF Quantity_In_Stock < Reorder_Point
THEN
INSERT INTO Purchase_Order (Po_No, Po_Date, Product_Id, Quantity, Supplier_id)
VALUES ( );
END IF;
END;
--Test Script
update product
set quantity_in_stock = 5
where product_id = 10012;
Attached in the other tables
You need a BEFORE trigger not AFTER trigger. See below:
--Table Preparation
CREATE TABLE product_1 (
quantity_in_stock NUMBER,
reorder_point NUMBER,
product_id NUMBER,
supplier_id NUMBER,
reorder_qty NUMBER
);
INSERT INTO product_1 VALUES (
20,
5,
10010,
500,
25
)
CREATE TABLE purchase_order (
po_no NUMBER,
po_date DATE,
product_id NUMBER,
quantity NUMBER,
supplier_id NUMBER
);
SQL> Select * from product_1;
QYT_IN_STOCK REORDER_PNT PRDT_ID SUPP_ID RERDR_QTY
----------- ---- ----- ----- -----
4 5 10010 500 25
--Trigger
CREATE TRIGGER product_reorder_au BEFORE
UPDATE ON product_1
FOR EACH ROW
WHEN ( new.quantity_in_stock < old.reorder_point )
BEGIN
INSERT INTO purchase_order (
po_no,
po_date,
product_id,
quantity,
supplier_id
) VALUES (
1, --Populate this coulmn by a sequence or by some logic
SYSDATE,
:old.product_id,
:old.reorder_qty,
:old.supplier_id
);
END;
Execution:
SQL> UPDATE product_1
SET
quantity_in_stock = 4
WHERE product_id = 10010;
SQL> SELECT * FROM purchase_order;
PO_NO PO_DATE PRODUCT_ID QTY SUPP_ID
----- ------- -------- ---- -------
1 25-10-18 10010 25 500

Parent key not found (oracle)

I want to insert 400000 random lines into a table with an oracle procedure.
I got an 'violated - parent key not found' Error.
create or replace
PROCEDURE TESTDATA AS
X INTEGER;
BEGIN
FOR X IN 1..400000 LOOP
INSERT INTO SALES
SELECT CAST(DBMS_RANDOM.VALUE(1, 10) as INTEGER) as "CUSTOMER_ID",
CAST(DBMS_RANDOM.VALUE(1, 2) as INTEGER) as "PRODUCT_ID",
TO_DATE(TRUNC(DBMS_RANDOM.VALUE(TO_CHAR(TO_DATE('1-jan-2000'),'J'),TO_CHAR(TO_DATE('21-dec-2012'),'J'))),'J') as "SALES_DATE",
CAST(DBMS_RANDOM.VALUE(1, 100) as INTEGER) as "PIECES"
FROM DUAL;
END LOOP;
END TESTDATA;
-- -----------------------------------------------------
-- Table SALES
-- -----------------------------------------------------
CREATE TABLE SALES (
CUSTOMER_ID INTEGER,
PRODUCT_ID INTEGER,
SALES_DATE DATE,
PIECES INTEGER,
PRIMARY KEY (CUSTOMER_ID, PRODUCT_ID, SALES_DATE),
FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMER (CUSTOMER_ID),
FOREIGN KEY (PRODUCT_ID) REFERENCES PRODUCT (PRODUCT_ID)
);
I hope that anyone could help me.
Roman
Your create table statement includes foreign keys on the CUSTOMER and PRODUCT tables. Your insert statement uses random values to populate CUSTOMER_ID and PRODUCT_ID. It is highly unlikely that random values will match existing keys in the referenced tables, so it's unsurprising that you get foreign key violations.
As to how you fix it, well that depends on what you actually want to achieve. As you're populating your table with random numbers you clearly don't care about the data, so you might as well drop the foreign keys. Alternatively you can use primary key values from the referenced tables in the insert statement.
"How can I use the primary key values from the referenced tables?"
You have twenty permutations of PRODUCT and CUSTOMER. So you will need to thrash through them 20000 times to generate 400000 records.
"In the customer table I have inserted 10 rows (1-10) and in the product table 2 rows (1-2)"
Here's a version of your procedure which loops through Products and Customers to generate random combinations of them. There's a third outer loop which allows you to produce as many sets of twenty combos as your heart desires.
create or replace procedure testdata
( p_iters in pls_integer := 1)
as
type sales_nt is table of sales%rowtype;
recs sales_nt;
rec sales%rowtype;
tot_ins simple_integer := 0;
begin
recs := new sales_nt();
dbms_random.seed(val=>to_number(to_char(sysdate,'sssss')));
<< iterz >>
for idx in 1..p_iters loop
<< prodz >>
for c_rec in ( select customer_id from customer
order by dbms_random.value )
loop
<< custz >>
for p_rec in ( select product_id from product
order by dbms_random.value )
loop
rec.customer_id := c_rec.customer_id;
rec.product_id := p_rec.product_id;
rec.sales_date := date '2000-01-01' + dbms_random.value(1, 6000);
rec.pieces := dbms_random.value(1, 100);
recs.extend();
recs(recs.count()) := rec;
end loop custz;
end loop prodz;
forall idx in 1..recs.count()
insert into sales
values recs(idx);
tot_ins := tot_ins + sql%rowcount;
recs.delete();
end loop iterz;
dbms_output.put_line('test records generated = '||to_char(tot_ins));
end testdata;
/
Proof of the pudding:
SQL> set serveroutput on
SQL> exec testdata(2)
test records generated = 40
PL/SQL procedure successfully completed.
SQL> select * from sales;
CUSTOMER_ID PRODUCT_ID SALES_DAT PIECES
----------- ---------- --------- ----------
9 2 21-FEB-02 42
9 1 10-AUG-05 63
7 1 23-FEB-12 54
7 2 21-NOV-12 80
1 2 06-NOV-15 56
1 1 08-DEC-09 47
4 2 08-JUN-10 58
4 1 19-FEB-09 43
2 2 04-SEP-02 64
2 1 09-SEP-15 69
6 2 20-FEB-08 60
...
2 1 11-JAN-16 19
3 2 03-FEB-10 58
3 1 25-JUL-10 66
9 2 26-FEB-16 70
9 1 15-MAY-14 90
6 2 03-APR-05 60
6 1 21-MAY-15 19
40 rows selected.
SQL>
note
As #mathguy points out, the OP's code constrains the range of random values to fit the range of primary keys in the referenced tables. However, I will leave this answer in place because its general approach is both safer (guaranteed to always match a referenced primary key) and less brittle (can cope with inserting or deleting PRODUCT or CUSTOMER records.

Creating a constraint in Oracle that depends on other columns

Please consider the following table structure on Oracle:
create table DOCS
(
DOC_NO NUMBER not null,
DOC_TYPE VARCHAR2(5) not null,
PMT_NO NUMBER not null
);
In this table, the PMT_NO column has to be unique except when DOC_NO is the same and DOC_TYPE is different:
DOC_NO DOC_TYPE PMT_NO
---------- -------- ----------
1 A 10 <-- good
1 B 10 <-- good, DOC_NO is the same
2 C 10 <-- NOT good, DOC_NO is different
PMT_NO cannot repeat and cannot have "holes" (i.e. 1, 2, 3, 5), so a sequence would not work. And there are many users inserting data at the same time.
Is there a way to create a unique key / unique index / function-based index for that condition?
Thanks!
Maybe this is a normalization problem.
You could pull out the relevant tuple into another table such that the row would be unique.
In this case link doc_no to pmt_no, once (not repeated as you have shown).
Then you can make a unique index on the pmt_no column of this link table.

Resources