Reputation: 16002
I'm trying to make a trigger that adds the value of input_qty
to the value of shelf_qty
, then sets input_qty
= 0.
This is my attempt:
DELIMITER $$
CREATE TRIGGER inventory_update
AFTER UPDATE ON `products`
FOR EACH ROW
IF OLD.`input_qty` > 0 THEN
BEGIN
DECLARE new_shelf_qty INT(11);
SET new_shelf_qty := OLD.`input_qty` + OLD.`shelf_qty`;
UPDATE `products` SET `input_qty` = 0, `shelf_qty` = new_shelf_qty;
END $$
DELIMITER;
I managed to get this to "work" by modifying Gordon's answer, but the trigger creates an infinite loop and doesn't update anything.
DELIMITER $$
CREATE TRIGGER inventory_update
BEFORE UPDATE ON `products`
FOR EACH ROW
BEGIN
DECLARE new_shelf_qty INT(11);
IF OLD.`input_qty` > 0 THEN
SET new_shelf_qty = OLD.input_qty + OLD.shelf_qty;
SET new.input_qty = 0;
END IF;
END $$
I would use something like this:
UPDATE product t
SET t.shelf_qty = t.shelf_qty + 1
WHERE t.id = 1 ;
But the problem is that the assembled sql query on my server looks like this:
UPDATE `products`
SET `qty` = CASE
WHEN `sku` = 'foo' THEN `qty` + qty1
WHEN `sku` = 'bar' THEN `qty` + qty2
...
END;
The data for the query is collected from a form like this:
<input id="sku1" type="number">
<input id="sku2" type="number">
<input id="sku3" type="number">
...
<input type="submit" value="Save">
print(POST body) //[{sku1:qty1}, {sku2:qty2}, {sku3:qty3}...]
the form only submits a list of item/qty objects when a change was made. The qty field is the amount to increment the inventory by, not the actual qty amount.
As far as I know, operations after THEN
aren't allowed and the only way I can think of to implement this with 1 query is with the trigger I attempted (which clearly doesn't work). Any suggestions would really be appreciated :)
This worked for me:
UPDATE products t
SET t.qty = t.qty
+ CASE t.sku
WHEN 'foo' THEN 1
WHEN 'bar' THEN 2
ELSE 0
END
WHERE t.sku IN ('foo','bar')
Upvotes: 0
Views: 101
Reputation: 108370
A TRIGGER cannot perform DML operations on tables that are referenced in the triggering statement. This restriction is documented in the MySQL Reference Manual.
To put that another way: the body of an UPDATE ON product
trigger cannot issue an UPDATE
statement against the product
table.
This is one of the the things wrong with the trigger definition.
Beyond that, there's some syntax issues. The FOR EACH ROW
should be followed by BEGIN
keyword (the exception to that is a trigger that is a single statement.)
An IF
statement should be closed with END IF
(not just END
)
But we have to re-think the whole approach here, not just fix the syntax.
Let's understand what we're trying to achieve, maybe by way of example.
Let's say we have table product
id mfr input_qty shelf_qty
-- --- --------- ---------
1 fee 3 39
2 fi 0 7
What would be the expected state of the table after we issue these statements:
UPDATE product SET mfr = 'fo' WHERE id = 1 ;
UPDATE product SET input_qty = 4 WHERE id = 2 ;
That is, we can predict the outcome of these statements if no triggers are fired. But how are triggers supposed to influence the behavior, modify the results of these statements? What we are needing the trigger(s) to accomplish?
UPDATE product SET input_qty = 5 , shelf_quantity = 11 WHERE id = 1;
We can't write code to do something if we don't have a specification; we need to have some tests that we can use to verify that the code we write is doing what it's supposed to be doing. Otherwise, we're just flinging SQL syntax hoping that things will somehow work out.
What are we trying to accomplish?
If we are wanting to "increment" shelf_qty
by some provided value, the normative pattern would be something like this (without any trigger):
UPDATE product t
SET t.shelf_qty = t.shelf_qty + 1
WHERE t.id = 1 ;
We reference the current value of shelf_qty
column, and add 1 to it, and then assign that new value back to the shelf_qty
column.
Update 1
An expression can be used following the THEN
keyword in a CASE
expression. An addition operation in an expression is allowed.
The syntax shown for the "assembled sql query" (is valid; we'd hope that there's an ELSE qty before the END
, in that it's a bit odd (not illegal, just unusual) to perform an UPDATE
without a WHERE
clause (to update every row in the table).
The syntax looks valid, but I can't verify the semantics, e.g. whether sku
and qty
are valid column references, et al.)
Personally, I'd do the UPDATE operation (added to the question) like this:
UPDATE product t
SET t.qty = t.qty
+ CASE t.sku
WHEN 'fee' THEN 1
WHEN 'fi' THEN 2
ELSE 0
END
But I'm not exactly sure what we're supposed to be assigning to qty
when sku
isn't listed. My assumption is that we would leave the qty
values on those rows unchanged. I'm just not understanding the benefit of a trigger for this use case,
Update 2
"As far as I know, operations after THEN aren't allowed" [in a CASE expression]
That depends what is meant by operations. Syntax for a CASE expression is:
CASE WHEN expr1 THEN expr2 WHEN expr3 THEN expr4 ... ELSE expr5 END
or:
CASE expr1 WHEN expr2 THEN expr3 WHEN expr4 THEN expr5 ... ELSE expr6 END
Where exprN
are expressions. An addition operation can be used in an expression.
We could write the update like this:
UPDATE products t
SET t.qty = CASE
WHEN t.sku = 'foo' THEN t.qty + 1
WHEN t.sku = 'bar' THEN t.qty + 2
ELSE t.qty
END
WHERE t.sku IN ('foo','bar')
But we would make it easier for a future reader to discern our intent by expressing it like this
UPDATE products t
SET t.qty = t.qty
+ CASE t.sku
WHEN 'foo' THEN 1
WHEN 'bar' THEN 2
ELSE 0
END
WHERE t.sku IN ('foo','bar')
Generalizing that is fairly straightforward. The SQL text generated by the application using named placeholders would be something like this:
UPDATE products t
SET t.qty = t.qty
+ CASE t.sku
WHEN :sku1 THEN :qty1
WHEN :sku2 THEN :qty2
WHEN :sku3 THEN :qty3
ELSE 0
END
WHERE t.sku IN ( :wsku1 , :wsku2 , :wsku3 )
or using positional placeholders, like this:
UPDATE products t
SET t.qty = t.qty
+ CASE t.sku
WHEN ? THEN ?
WHEN ? THEN ?
WHEN ? THEN ?
ELSE 0
END
WHERE t.sku IN ( ? , ? , ? )
We can see how the statement would be extended dynamically for a variable number of {sku:qty}
combinations
followup
This all disrecommends using a TRIGGER. It's not the best way to handle the requirement. But, to answer the question that was asked...
If we have to use a trigger, given:
product
id sku input_qty shelf_qty
-- --- --------- ---------
3 fo 0 41
4 fum 0 11
and
UPDATE product t
SET t.input_qty = CASE t.sku
WHEN 'fo' THEN 1
WHEN 'fum' THEN 2
ELSE 0
END
WHERE t.sku IN ('fo','fum')
then with this trigger defined:
DELIMITER $$
CREATE TRIGGER product_bu
BEFORE UPDATE ON product
FOR EACH ROW
BEGIN
IF NEW.input_qty > 0 THEN
-- add provided value of input_qty to shelf_qty
SET NEW.shelf_qty = HEW.shelf_qty + NEW.input_qty;
-- set input_qty to zero
SET NEW.input_qty = 0;
END IF;
END$$
The expected result would be:
product
id sku input_qty shelf_qty
-- --- --------- ---------
3 fo 0 42
4 fum 0 13
But it doesn't make sense to me to do this with a trigger. I'm not seeing the benefit. It just seems to unnecessarily and confusingly modify the normal behavior of an UPDATE
.
Upvotes: 1
Reputation: 1269445
That sounds really weird -- you have a column that will always be 0?. But if you want the set the value in the current row to 0
, then use a BEFORE UPDATE
trigger:
DELIMITER $$
CREATE TRIGGER inventory_update
BEFORE UPDATE ON `products`
FOR EACH ROW
BEGIN
IF OLD.`input_qty` > 0 THEN
DECLARE new_shelf_qty INT(11);
SET new_shelf_qty = OLD.input_qty + OLD.shelf_qty;
SET new.input_qty = 0;
END IF;
END $$
DELIMITER;
Upvotes: 1