I want to create a trigger after there's an insert in 'order_items' table. The trigger is to minus the quantity of that product id in 'inventories' table by the quantity being insert to 'order_items' table. If the result of quantity after being subtracted is negative, then the quantity of 'inventories' will be changed back to the old quantity and the data in 'order_items' will be deleted (the insertion is failed). If the product id is in many warehouses, then just minus the quantity with the smallest warehouse_id
Example #1:
Product ID '101' is available in Warehouse ID '1','2','3'
So choose the product id '101' in warehouse id '1' (smallest)
Product ID '101' Quantity in Inventories = 10
Product ID '101' Quantity Order_Items = 15
So 10-15=-5 (negative)
So...
Product ID '101' Quantity in Inventories = 10 (back to old data)
Delete '101' datas from Order_Items
Example #2:
Product ID '102' Quantity in Inventories = 20
Product ID '102' Quantity Order_Items = 8
So 20-8=12
So...
Product ID '102' Quantity in Inventories = 12
This is my code so far, any ideas? Thanks!
CREATE OR REPLACE TRIGGER update_qty
AFTER INSERT
ON order_items
FOR EACH ROW
DECLARE
qty_diff number;
qty_in number;
total number;
BEGIN
select quantity,count(product_id) into qty_in,total
from inventories
where product_id=:new.product_id;
if(total>1) then
select product_id from inventories where warehouse_id=(select min(warehouse_id) from inventories);
else
qty_diff:=qty_in-:new.quantity;
if(qty_diff<0) then
i.quantity:=:old.quantity;
delete from order_items where product_id=:new.product_id;
else
update inventories
set quantity=qty_diff;
end if;
end if;
END;
/
BEGIN
insert into order_items(order_id,item_id,product_id,quantity,unit_price) values(12345,12345,12345,100,200);
END;
/
It is better to apply this logic at the time of insertion(using procedure in DB or application-level logic). but if you are doing it for learning purposes and if I followed you correctly, You can use the following simple update in the trigger.
CREATE OR REPLACE TRIGGER UPDATE_QTY AFTER
INSERT ON ORDER_ITEMS
FOR EACH ROW
BEGIN
-- check and update the INVENTORIES table
-- lowest warehouse id with the product will be selected
-- new quantity or more must be available in the INVENTORIES table
UPDATE INVENTORIES I
SET
QUANTITY = QUANTITY - :NEW.QUANTITY
WHERE QUANTITY >= :NEW.QUANTITY
AND PRODUCT_ID = :NEW.PRODUCT_ID
AND NOT EXISTS (
SELECT 1
FROM INVENTORIES II
WHERE II.PRODUCT_ID = :NEW.PRODUCT_ID
AND II.WAREHOUSE_ID < I.WAREHOUSE_ID
);
-- if no record is selected means product is not available in any of the lowest warehouse
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(
-20000,
'product not available in inventory'
);
END IF;
END;
/
Related
I have an orders table and an order_products_items table.
The order_products_items has these fields:
order_id
product_id
quantity
price
I am trying to create a calculated_field: calculated_total_products_price in the orders table through a before insert trigger function that would calculate the total order price by looping through all the order_products_items related to the order and multiplying the quantity with the price for every order item.
This is my failed attempt in doing so:
CREATE OR REPLACE FUNCTION public.fn_trigger_total_order_price()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare total float := 0.0;
product record;
BEGIN
FOR product IN
SELECT itm.price, itm.quantity
FROM order_products_items itm
INNER JOIN orders ord
ON itm.order_id = ord.id
WHERE ord.id = NEW.id
LOOP
total = total + (product.price * product.quantity);
END LOOP;
NEW.calculated_total_products_price := total;
RETURN NEW;
END;
$function$
;
The trigger looks like this:
CREATE TRIGGER fn_trigger_total_order_price BEFORE INSERT ON public.orders
FOR EACH ROW EXECUTE PROCEDURE fn_trigger_total_order_price();
Somehow, I do not get any errors, but always get 0 as a result.
Am I missing something? or is there a better/more efficient way of approaching this?
Many thanks.
You don't need a loop in postgres to achieve this.
A simple AGGREGATION query would do.
Something like
SELECT SUM(quantity * price)
FROM order_products_items
WHERE order_id = NEW.id
You also don't really need to mess with a trigger if you don't want to.
Just make a view out of the query below and relate it to the orders table as something like order_totals
SELECT order_id, SUM(quantity * price) order_total
FROM order_products_items
GROUP BY order_id
I have a procedure that adds data
add_price (cust_id customers.id%type,
items_id items.id%type,
price number);
and I want to create a function that for each combination of customers and items to create an additional one at random entry in the table price.
How can I do that?
UPD: Please note, I believe the idea from MT0 is better because you'll need only one insert statement. My solution is for the case when using add_price function is required
So, "each combination of customers and items" means you need a cartesian product:
select cust_id, item_id
from customers
cross join items;
For example if you had following data in "customers" and "items" table:
cust_id
cust_name
1
A
2
B
item_id
item_name
1
a
2
b
the query above would return:
cust_id
item_id
1
1
1
2
2
1
2
2
Thus, all is left is to get random value. Use dbms_random.value for that
begin
for q in (select cust_id, item_id from customers cross join items) loop
add_price(q.cust_id, q.item_id, round(dbms_random.value(10, 1000), 2));
end loop;
end;
The parameters for value are lowes_value and highest_value so the result will be between those numbers. You probably will need to set them somehow. And rounding will be needed too
Don't use a function, create a procedure and use INSERT ... SELECT with the CROSS JOIN of the customers and the items tables:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
INSERT INTO prices (customer_id, item_id, price)
SELECT c.customer_id,
i.item_id,
ROUND(DBMS_RANDOM.VALUE(0,100),2)
FROM customers c
CROSS JOIN items i;
END generate_random_prices;
/
Which, if you have the sample data:
CREATE TABLE customers (customer_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,5,42));
CREATE TABLE items (item_id PRIMARY KEY) AS
SELECT COLUMN_VALUE FROM TABLE(SYS.ODCINUMBERLIST(1,3,61));
CREATE TABLE prices (
customer_id REFERENCES customers(customer_id),
item_id REFERENCES items(item_id),
price NUMBER(4,2)
);
Then after:
BEGIN
generate_random_prices();
END;
/
The prices table may (randomly) contain:
CUSTOMER_ID
ITEM_ID
PRICE
1
1
38.91
1
3
39.74
1
61
67.28
5
1
13.92
5
3
48.17
5
61
70.21
42
1
90.33
42
3
5.7
42
61
40.37
If you want to call your ADD_PRICE procedure then just take the same CROSS JOIN query and use a cursor loop:
CREATE PROCEDURE generate_random_prices
IS
BEGIN
FOR rw IN (SELECT c.customer_id,
i.item_id
FROM customers c
CROSS JOIN items i)
LOOP
ADD_PRICE(rw.customer_id, rw.item_id, ROUND(DBMS_RANDOM.VALUE(0,100),2));
END LOOP;
END generate_random_prices;
/
(But it will be more efficient to just use a single INSERT ... SELECT statement.)
db<.fiddle here
I have the following tables in my database:
The first table is named Amount, second Product, third Purchase.
And I should to create the trigger on insert to amount table. For example, I'll insert the following values: 4, 1, 10, where 4 is id_purchase, 1 is id_product and 4 is amount of this products. And trigger should subtract this amount from Amount_On_Stock. In my example, it should be: was 48, became 38.
Here's the code of my trigger:
CREATE TRIGGER AmountInsert ON Amount
AFTER INSERT
AS
BEGIN
UPDATE Product
SET Amount_On_Stock = (
SELECT
Amount_On_Stock
FROM Product
WHERE ID_Product = (
SELECT
MAX(ID_Product)
FROM Purchase
WHERE ID_Purchase = (
SELECT
MAX(ID_Purchase)
FROM Purchase
)
)
)-(
SELECT
Amount
FROM AMOUNT
WHERE ID_Product = (
SELECT
MAX(ID_Product)
FROM Purchase
WHERE ID_Purchase = (
SELECT
MAX(ID_Purchase)
FROM Purchase
)
)
)
END
But when I try to create this trigger I have the following error:
The aggregate expression cannot be used in the WHERE clause unless it
is contained in a subquery of the HAVING clause or in the select list,
and the column being aggregated is not an external reference.
So, how can I solve this problem?
Your trigger looks nothing like a SQL Server trigger. I would expect your trigger to look more like this:
CREATE TRIGGER AmountInsert ON Amount AFTER INSERT
AS
BEGIN
UPDATE p
SET Amount_On_Stock = p.Amount_On_Stock - i.amount
FROM Product p JOIN
inserted i
ON p.ID_Product = i.ID_Product;
END;
However, this will not do the right thing if you have multiple inserts on the same product at the same time. To handle that you need aggregation:
CREATE TRIGGER AmountInsert ON Amount AFTER INSERT
AS
BEGIN
UPDATE p
SET Amount_On_Stock = p.Amount_On_Stock - i.amount
FROM Product p JOIN
(SELECT i.ID_Product, SUM(i.amount) as amount
FROM inserted i
GROUP BY i.ID_Product
) i
ON p.ID_Product = i.ID_Product;
END;
I have three table related in a trigger
1) Order_Item -- Recipe_ID, Order_ID, Quantity
2) Ingredient_Recipe -- Stock_ID, Recipe_ID, Quantity
3) Stock -- Stock_ID,Balance
Another table related,
4) Recipe -- Recipe_ID, Name
Trying to make a trigger that Stock will decrease after Order_Item insert, also based on the quantity in Ingredient_Recipe.
The basic formula is
Balance (from Stock) - ( Quantity (from Ingredient_recipe ) * Quantity (from Order_Item) )
I created the trigger
CREATE TRIGGER Stck_Up
AFTER INSERT ON Order_Item
REFERENCING NEW AS N
FOR EACH ROW mode db2sql
UPDATE Stock
SET Balance = (SELECT sum (Stock.Balance - (Quantity *( SELECT Quantity FROM Ingredient_Recipe
where Ingredient_Recipe.Recipe_ID = Order_Item.Recipe_ID)))
FROM Stock
WHERE Stock_ID = N.Stock_ID )
WHERE Stock_ID = N.Stock_ID;
but the error is N.Stock_ID is not valid
I am trying to create an update trigger that checks the quantity of a product in stock over how much someone is ordering and displays a message if not enough of that product is in stock.
It's letting me create the trigger but when testing, it displays an error "Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=,...."
I'm not really understanding how it's returning more than one value as I have it searching for the particular row with the product ID that matches the inserted value first. I then have it comparing the UnitsInStock from the inserted value.
Here's what I have so far:
CREATE TRIGGER tr_check_qty
ON OrderDetails
FOR UPDATE
AS
DECLARE #ProductID int,
#Quantity int
SELECT #ProductID = ProductID,
#Quantity = Quantity
FROM inserted
WHERE #ProductID = ( SELECT ProductID FROM Products )
IF
#Quantity > ( SELECT UnitsInStock FROM Products )
BEGIN
PRINT 'Not enough product in stock'
ROLLBACK TRANSACTION
END
I think you want something like:
CREATE TRIGGER tr_check_qty
ON OrderDetails
FOR UPDATE
AS
IF EXISTS (
SELECT *
FROM
Products p
inner join
inserted i
on p.ProductID = i.ProductID
WHERE i.Quantity > p.UnitsInStock)
BEGIN
PRINT 'Not enough product in stock'
ROLLBACK TRANSACTION
END
However, I'm a bit mystified on why this is inside an update trigger, as compared to an insert trigger.
SELECT UnitsInStock FROM Products as well as SELECT ProductID FROM Products may return a whole column, not a single value. You should specify some restriction there, like WHERE id = #someId.
You have more the one products right?
Then this line:
WHERE #ProductID = ( SELECT ProductID FROM Products )
And this line:
#Quantity > ( SELECT UnitsInStock FROM Products )
will return many rows.
Note as well that if you insert more then one row. The inserted table will have more then one row as well
You might want have to do something like this:
IF EXISTS
(
SELECT
NULL
FROM
inserted
WHERE EXISTS
(
SELECT
NULL
FROM
Product
WHERE
Product.Quantity>inserted.Quantity
)
)
BEGIN
PRINT 'Not enough product in stock'
ROLLBACK TRANSACTION
END