How to update a rows with same prodID on a UPDATE? - mysql

I get the following error:
"Can't update table 'Product' in stored function/trigger because it is already used by statement which invoked this stored function/trigger."
CREATE TRIGGER `SalesDB`.`updateSum` AFTER UPDATE ON SalesDB.Product FOR EACH ROW
BEGIN
DECLARE totalStock INT;
SELECT SUM(stock)
INTO totalStock
FROM Product
WHERE Product.prodID= NEW.prodID;
UPDATE Product SET StockSum = totalStock
WHERE prodID = NEW.prodID;
END;
I understand that there is a recursive issue, but how do I do something like this? Is there a way to use triggers to do this kind of thing? Should this be done in a Stored Procedure instead, and how do I run the stroed procedure on a Update Product event?
I need to update the stock of a product and then go thru all rows that have the same prodID calculate the SUM and Update the SumTotal field of all the rows.

UPDATED Unfortunately you can't do this in MySql due to the limitations of triggers implementation.
Possible options are:
Create stored procedure and call it after each update on Product table
Store stockSum in a separate table (e.g. ProductStock), use triggers (INSERT, UPDATE) to update stockSum values, create a view on joined Product and ProductStock
Use MySql events or a cron job to update stockSum in Product table if some delay is acceptable
IMHO best approach is 2 separating Product and ProductStock tables:
ProductStock might look like this
CREATE TABLE ProductStock
(
prodID int NOT NULL PRIMARY KEY,
stockSum decimal(12, 3)
);
Now triggers
CREATE TRIGGER tg_product_update
AFTER UPDATE ON Product
FOR EACH ROW
CALL sp_updateProductStock(NEW.prodID);
CREATE TRIGGER tg_product_insert
AFTER INSERT ON Product
FOR EACH ROW
CALL sp_updateProductStock(NEW.prodID);
and a stored procedure
CREATE PROCEDURE sp_updateProductStock(IN ProductID INT)
REPLACE INTO ProductStock (prodID, stockSum)
SELECT ProductID,
(SELECT SUM(COALESCE(stock, 0))
FROM Product
WHERE prodID = ProductID);
And finally a view
CREATE VIEW vw_Product AS
SELECT p.id, p.prodID, ... , p.stock, s.stockSum
FROM Product p JOIN ProductStock s
ON p.prodID = s.prodID
Here is SQLFiddle example

Related

SQL Trigger issue : Can't update table in stored function/trigger because it is already used by statement which invoked this stored function/trigger

Hi guys, I'm facing an issue trying to auto update a table with triggers.
I'm developing a real estate management app and I'd like to generate, for the property manager side, a table (bill) with all charges and payments received per year and per landlord for a same shared building.
The main tables that I use for this part are:
building
charge (which contains building_id)
charge_payment(which contains building_id and landlord_id as user_id)
To generate this table (bill), for exemple, I made a trigger on table charge which will generate a table on each new charge insert that I insert into bill table. I check in table charge and table charge_payment for that.
Here is how it looks like:
CREATE DEFINER=`root`#`localhost` TRIGGER `charge_AFTER_INSERT` AFTER INSERT ON `charge` FOR EACH ROW BEGIN
INSERT INTO bill(building_id, user_id, year, amount, payment)
SELECT
p.building_id,
ll.user_id user_id,
bcharge_annual.year year,
ROUND(sum(p.building_slice) * (bcharge_annual.amount), 2) amount,
IFNULL(total_payments.payments, 0) payment
FROM landlord ll
LEFT JOIN property p ON ll.property_id=p.id
LEFT JOIN (
SELECT
c.building_id building_id,
c.year,
SUM(c.amount) amount
FROM charge c
WHERE c.building_id=new.building_id and c.property_id IS NULL
GROUP BY c.year
) bcharge_annual ON p.building_id=bcharge_annual.building_id
LEFT JOIN (
SELECT
cp.user_id user_id,
cp.corresponding_year year,
IFNULL(sum(cp.amount), 0) payments
FROM charge_payment cp
WHERE cp.corresponding_year=new.year
GROUP BY user_id,corresponding_year
) total_payments ON ll.user_id=total_payments.user_id
WHERE p.building_id=new.building_id AND (ll.ownership_end IS NULL OR ll.ownership_end > (SELECT NOW())) AND bcharge_annual.year=new.year
GROUP BY ll.user_id,year;
END
My problem is that if I add an other charge on the same building and same year, I had an error. Until now, I solved the problem by deleting all raws in table bill with same year before each new charge insert, but it's quite dirty :)
CREATE DEFINER=`root`#`localhost` TRIGGER `charge_AFTER_INSERT` AFTER INSERT ON `charge` FOR EACH ROW BEGIN
DECLARE matchbillyear INT;
SELECT COUNT(*)
INTO matchbillyear
FROM bill b
WHERE b.building_id=new.building_id AND b.year=new.year;
IF matchbillyear > 0 THEN
DELETE FROM bill bil
WHERE bil.year=new.year;
...
So I've been thinking, and I tried to remove this deletion trigger on table charge and add a trigger on table bill to check before insert on each row if it should be an insert or just an amount update:
CREATE DEFINER=`root`#`localhost` TRIGGER `bill_BEFORE_INSERT` BEFORE INSERT ON `bill` FOR EACH ROW BEGIN
DECLARE matchBillId INT;
SELECT b.id
INTO matchBillId
FROM bill b
WHERE b.building_id=new.building_id AND b.year=new.year AND b.user_id=new.user_id;
IF EXISTS (
SELECT b.id
FROM bill b
WHERE b.building_id=new.building_id AND b.year=new.year AND b.user_id=new.user_id) THEN
UPDATE bill
SET NEW.amount = amount + NEW.amount
WHERE id = matchBillId;
END IF;
END
Was looking good to me, but definitely not for MySql which throws me this error:
ERROR 1442: 1442: Can't update table 'bill' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
SQL Statement:
INSERT INTO `erem_pro`.`charge` (`building_id`, `year`, `type`, `amount`) VALUES ('1', '2021', 'Entretien', '1300.00')
If anyone has any clue on how to make it work, would be great. Otherwise I think I'll have to rethink my app... :/
Thanks a lot in advance and congratulation if you read all that :)

Trigger AFTER UPDATE for effected rows only

My Prestashop table ps_stock available has a column called quantity where the stock of the product is stored. This table is updated by an app, changing the values of the quantity column.
I need a trigger in that table. Every time the quantity of a product is changed I need to extract which products are effected on that table to insert them in another table
I have tried a trigger like this:
IF (OLD.quantity <> NEW.quantity)
THEN
INSERT IGNORE INTO ps_ebay_product_modified (id_ebay_product_modified,id_ebay_profile,id_product)
SELECT ps_ebay_product.id_ebay_product,id_ebay_profile,id_product
FROM ps_ebay_product INNER JOIN ps_stock_available
WHERE OLD.quantity <> NEW.quantity;
END IF
What I need is a selection of the products that have been changed.
This should work
CREATE TRIGGER mytrigger AFTER UPDATE ON ps_stock
FOR EACH ROW
BEGIN
-- do some stuff here
END;

Update mysql rows with formula having different values for each row

I have two tables:
Table 1 : parts
id (primary key)
code
title
quantity
Table 2 : bill_items
id (primary key)
bill_id
parts_id: refers to primary key of table parts
qty
I would like to update parts table - qty every time I create a row in table bill_items. The qty in parts table is to be decremented by qty of bill_items. There may be N number of updates to table bill_items in one go. Would like to use a single INSERT....ON DUPLICATE or UPDATE statement.
Thank you for your time.
I think for this case better using trigger :
DELIMITER $$
CREATE
TRIGGER `bill_items_after_insert` AFTER INSERT
ON `bill_items`
FOR EACH ROW BEGIN
UPDATE parts set quantity = quantity - NEW.qty WHERE id = NEW.parts_id;
END$$
DELIMITER ;
I suggest you also make trigger for UPDATE and DELETE also for data consistency.
UPDATED
Based on your comment, it is possible to use normal insert and update using transaction for consistency data :
START TRANSACTION;
INSERT INTO bill_items (bill_id, parts_id, qty) VALUES (your_bill_id,your_parts_id,your_qty);
UPDATE parts SET quantity = quantity - your_qty WHERE id = your_parts_id;
COMMIT;

Set max value as default value

I'm trying to store the order of the products in a database, and I want every new product added to be at the last position. Basically, I want the "rank" field to be set to MAX + 1, and MAX should be the highest value between the entries having the same "type".
I tried to do with a trigger, but since MySQL won't let me do that since the trigger would be executed on the same table :
**#1442 - Can't update table 'products' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. **
Here is what my trigger was like :
CREATE TRIGGER `productInsert`
AFTER INSERT ON `products`
FOR EACH ROW
BEGIN
UPDATE products
SET rank = (SELECT MAX(rank)
from products
where type_id = NEW.type_id)
where id = NEW.id
Is there any other way to do that?
Thanks
You want a before insert trigger:
CREATE TRIGGER `productInsert`
BEFORE INSERT ON `products`
FOR EACH ROW
BEGIN
set NEW.rank = (select max(rank) + 1
from products p
where p.type_id = NEW.type_id
);
END

Add a row if there are less than 5, otherwise replace the lowest number in MySQL

I'm pretty stuck on a Mysql query.
I have a table with three columns;
user_id | person_id | score.
The table is going to be used to store top 5 highscores for each person.
I need at query that checks if there is less than five rows for a specific person.
Is there is less, insert new row. But if there is five rows I have to replace the lowest score with the new one.
It is for a webservice written in PHP and the data about the new score is posted to the method as params.
Been stuck for some hours now — is it even possible to make this happen in one query ?
You can use stored procedure in mysql. I dont know the names of the tables but if you look closer you will understand how it works.
DELIMITER $$
DROP PROCEDURE IF EXISTS test $$
CREATE PROCEDURE test( IN testparam VARCHAR(22) )
BEGIN
DECLARE count INT(11);
SET count = (SELECT COUNT(*) FROM persons );
IF count < 5 THEN
insert into table_needed_for_insert values(testparam);
ELSE
update table_needed_for_insert where score=(select min(score) from table_needed_for_insert);
END IF;
select * from table_needed_for_insert
END $$
DELIMITER;
And how to execute this thing CALL test(1); 1 is the parameter, you can create as many as you need.
And from php you can call directly as like
$result = mysql_query("call test(".$param.")");
And here you can check a tutorial on mysql stored procedures:
http://www.mysqltutorial.org/mysql-stored-procedure-tutorial.aspx
It might be possible if you have a unique key which identifies the lowest score. Then you could use the
INSERT ... ON DUPLICATE KEY construct. But you would have to install a trigger which keeps explicit track of the lowest score.
I would propose this scenario (I have not tried it, it is just an idea):
as I understand, you only need 5 ids. you can run a subqueries like these
SELECT MAX(id) AS last_id FROM table
SELECT MIN(score), id AS lowest_id FROM table
then
insert or replace into table (id, ...) values ( MIN(last_id+1, lowest_id), ... )
there are possible mistakes and also only one subquery is possible, but I hope you get the main idea
The simplest way imo is to insert data,
INSERT INTO top_scores (user_id, person_id, score_id) VALUES (1,2,3)
then delete inappropriate rows
DELETE top_scores FROM top_scores
INNER JOIN
(SELECT * FROM top_scores WHERE person_id = 2 ORDER BY score ASC LIMIT 5, 1000000) AS inappropriate_rows
USING (user_id, person_id, score)