如何锁定行以进行更新,然后使用 MySQL 中的变量更新 table
How to lock rows for update and then update a table using a variable in MySQL
我有一个采购 table 存储商店收到的所有订单。在某些情况下,我必须以不同的成本重新订购一件商品。当我进行销售交易时,我需要能够使用根据购买日期确定的 FIFO(先进先出)系统确定所售商品的总成本。
这是我的 table 的样子
CREATE TABLE `purchases` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`item_id` int(11) unsigned NOT NULL,
`purchased_on` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`cost` decimal(10,2) unsigned NOT NULL,
`availableQty` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `item_id` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `purchases` (`id`, `item_id`, `purchased_on`, `cost`, `availableQty`) VALUES
(1, 1, '2015-01-13 23:50:09', '1.00', 10),
(2, 1, '2015-01-13 23:50:10', '0.90', 20),
(3, 1, '2015-01-13 23:50:11', '0.80', 50),
(4, 2, '2015-01-13 23:50:11', '10.80', 18),
(5, 2, '2015-01-13 23:50:11', '20.25', 235);
使用上面的数据,如果有人想购买 40 件 item_id = 1,那么成本是这样计算的 (10 X 1.00) + (20 X 0.90) + (10 X 0.80 ) = 36 。 item_id = 1 的这笔交易的总费用为 36 美元。
但是现在,我需要用新的可用值更新 availableQty
列。
根据 availableQty
列中的值,第 1 行和第 2 行在 availableQty
列中的值应为 0,同一列的第 3 行应保留 40。
为了计算交易成本,我能够编写此查询来进行成本计算。
SET @orgMyQty = 40;
SET @myQty = @orgMyQty;
SELECT SUM(cost) AS total_cost, SUM(availableQty) AS totalAvailable, SUM(availableQty) - @orgMyQty AS RemainingUnites
FROM (
SELECT
CASE WHEN availableQty >= @myQty THEN cost * @myQty
ELSE cost * availableQty END AS cost,
CASE WHEN @myQty > availableQty THEN @myQty := @myQty - availableQty END AS left_over,
availableQty
FROM purchases
WHERE item_id = 1 AND @myQty > 0
ORDER BY purchased_on ASC, item_id ASC
) AS t
但是如何使用新的总可用值更新每一行?
所以最终的记录需要像这样
(1, 1, '2015-01-13 23:50:09', '1.00', 0),
(2, 1, '2015-01-13 23:50:10', '0.90', 0),
(3, 1, '2015-01-13 23:50:11', '0.80', 40),
(4, 2, '2015-01-13 23:50:11', '10.80', 18),
(5, 2, '2015-01-13 23:50:11', '20.25', 235);
所以我的问题是
- 如何正确执行UPDATE?
- 如何使用查询查询锁定行以进行更新,以便在提交此事务之前没有其他事务可以更改值。请注意,查询 select 和更新都在同一个事务中。
- 有没有更好的方法来计算成本?
我尝试了以下查询来更新值
-- 这行给我一个语法错误 @myQty := CASE WHEN @myQty > availableQty THEN @myQty - availableQty ELSE @myQty END
SET @myQty = 40;
UPDATE data_import.purchases
SET qty = CASE WHEN @myQty = 0 THEN 0
WHEN @myQty > 0 AND availableQty >= @myQty THEN cost * @myQty
ELSE cost * availableQty END,
@myQty := CASE WHEN @myQty > availableQty THEN @myQty - availableQty ELSE @myQty END
WHERE item_id = 1
ORDER BY purchased_on ASC, item_id ASC;
我也试过这个查询
-- there is not syntax error here but only the last column was updated incorrectly.
UPDATE data_import.purchases
SET qty = CASE WHEN @myQty = 0 THEN 0
WHEN @myQty > 0 AND availableQty >= @myQty THEN cost * @myQty
ELSE cost * availableQty END
WHERE item_id = 1 AND @myQty := CASE WHEN @myQty > availableQty THEN @myQty - availableQty ELSE @myQty END
ORDER BY purchased_on ASC;
这里有一些不同的计算成本的方法:
select sum(cost *
least(
availableQty,
greatest(
0,
40-ifnull(
(select sum(availableQty) from purchases as b where b.id<a.id),
0
)
)
))
as totalCost
from purchases as a
where item_id = 1
一些说明它是如何工作的:
Return 我们还应该为当前行购买多少:
greatest(
0,
40 - ifnull((select sum(availableQty) from purchases as b where b.id<a.id),0)
)
对于每一行,我们计算我们从前几行购买了多少,即我们总结了所有前几行的可用数量,从 40(我们想要购买的数量)中减去这个数字,我们就剩下了。
这基本上是您已经拥有的相同循环,但是您每次迭代都在减少 "rest",我重新计算之前的总和并发现它与总需求之间的差异,所以我得到为没有循环的每一行休息。
但是我们可以有负数,这就是我们使用greatest
的原因。因此,对于正值(即前面的行没有满足需求),我们从其他行中得到我们还需要多少select,对于所有其他行 - 0,即所有需求都得到满足。
我们可以从当前行中 "buy" 多少:
least(
availableQty,
demandRest /* construction above */
)
我们只是 select 全部可用数量或未满足的需求,具体取决于哪个较小
因此,基本上对于每一行,我们要么获得全部可用数量,要么要求剩余数量,或者如果前几行满足了需求,则为 0
这里是更新的解决方案:
update purchases as u
inner join
(
select id,
least(
availableQty,
greatest(
0,
40-ifnull(
(select sum(availableQty) from purchases as b where b.id<a.id),
0
)
)
)
as usedQty
from purchases as a
where item_id = 1) as t
on u.id = t.id
set u.availableQty = u.availableQty-t.usedQty;
fiddle: http://sqlfiddle.com/#!9/0a756/1
我有一个采购 table 存储商店收到的所有订单。在某些情况下,我必须以不同的成本重新订购一件商品。当我进行销售交易时,我需要能够使用根据购买日期确定的 FIFO(先进先出)系统确定所售商品的总成本。
这是我的 table 的样子
CREATE TABLE `purchases` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`item_id` int(11) unsigned NOT NULL,
`purchased_on` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`cost` decimal(10,2) unsigned NOT NULL,
`availableQty` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `item_id` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `purchases` (`id`, `item_id`, `purchased_on`, `cost`, `availableQty`) VALUES
(1, 1, '2015-01-13 23:50:09', '1.00', 10),
(2, 1, '2015-01-13 23:50:10', '0.90', 20),
(3, 1, '2015-01-13 23:50:11', '0.80', 50),
(4, 2, '2015-01-13 23:50:11', '10.80', 18),
(5, 2, '2015-01-13 23:50:11', '20.25', 235);
使用上面的数据,如果有人想购买 40 件 item_id = 1,那么成本是这样计算的 (10 X 1.00) + (20 X 0.90) + (10 X 0.80 ) = 36 。 item_id = 1 的这笔交易的总费用为 36 美元。
但是现在,我需要用新的可用值更新 availableQty
列。
根据 availableQty
列中的值,第 1 行和第 2 行在 availableQty
列中的值应为 0,同一列的第 3 行应保留 40。
为了计算交易成本,我能够编写此查询来进行成本计算。
SET @orgMyQty = 40;
SET @myQty = @orgMyQty;
SELECT SUM(cost) AS total_cost, SUM(availableQty) AS totalAvailable, SUM(availableQty) - @orgMyQty AS RemainingUnites
FROM (
SELECT
CASE WHEN availableQty >= @myQty THEN cost * @myQty
ELSE cost * availableQty END AS cost,
CASE WHEN @myQty > availableQty THEN @myQty := @myQty - availableQty END AS left_over,
availableQty
FROM purchases
WHERE item_id = 1 AND @myQty > 0
ORDER BY purchased_on ASC, item_id ASC
) AS t
但是如何使用新的总可用值更新每一行?
所以最终的记录需要像这样
(1, 1, '2015-01-13 23:50:09', '1.00', 0),
(2, 1, '2015-01-13 23:50:10', '0.90', 0),
(3, 1, '2015-01-13 23:50:11', '0.80', 40),
(4, 2, '2015-01-13 23:50:11', '10.80', 18),
(5, 2, '2015-01-13 23:50:11', '20.25', 235);
所以我的问题是
- 如何正确执行UPDATE?
- 如何使用查询查询锁定行以进行更新,以便在提交此事务之前没有其他事务可以更改值。请注意,查询 select 和更新都在同一个事务中。
- 有没有更好的方法来计算成本?
我尝试了以下查询来更新值
-- 这行给我一个语法错误 @myQty := CASE WHEN @myQty > availableQty THEN @myQty - availableQty ELSE @myQty END
SET @myQty = 40;
UPDATE data_import.purchases
SET qty = CASE WHEN @myQty = 0 THEN 0
WHEN @myQty > 0 AND availableQty >= @myQty THEN cost * @myQty
ELSE cost * availableQty END,
@myQty := CASE WHEN @myQty > availableQty THEN @myQty - availableQty ELSE @myQty END
WHERE item_id = 1
ORDER BY purchased_on ASC, item_id ASC;
我也试过这个查询
-- there is not syntax error here but only the last column was updated incorrectly.
UPDATE data_import.purchases
SET qty = CASE WHEN @myQty = 0 THEN 0
WHEN @myQty > 0 AND availableQty >= @myQty THEN cost * @myQty
ELSE cost * availableQty END
WHERE item_id = 1 AND @myQty := CASE WHEN @myQty > availableQty THEN @myQty - availableQty ELSE @myQty END
ORDER BY purchased_on ASC;
这里有一些不同的计算成本的方法:
select sum(cost *
least(
availableQty,
greatest(
0,
40-ifnull(
(select sum(availableQty) from purchases as b where b.id<a.id),
0
)
)
))
as totalCost
from purchases as a
where item_id = 1
一些说明它是如何工作的:
Return 我们还应该为当前行购买多少:
greatest(
0,
40 - ifnull((select sum(availableQty) from purchases as b where b.id<a.id),0)
)
对于每一行,我们计算我们从前几行购买了多少,即我们总结了所有前几行的可用数量,从 40(我们想要购买的数量)中减去这个数字,我们就剩下了。
这基本上是您已经拥有的相同循环,但是您每次迭代都在减少 "rest",我重新计算之前的总和并发现它与总需求之间的差异,所以我得到为没有循环的每一行休息。
但是我们可以有负数,这就是我们使用greatest
的原因。因此,对于正值(即前面的行没有满足需求),我们从其他行中得到我们还需要多少select,对于所有其他行 - 0,即所有需求都得到满足。
我们可以从当前行中 "buy" 多少:
least(
availableQty,
demandRest /* construction above */
)
我们只是 select 全部可用数量或未满足的需求,具体取决于哪个较小
因此,基本上对于每一行,我们要么获得全部可用数量,要么要求剩余数量,或者如果前几行满足了需求,则为 0
这里是更新的解决方案:
update purchases as u
inner join
(
select id,
least(
availableQty,
greatest(
0,
40-ifnull(
(select sum(availableQty) from purchases as b where b.id<a.id),
0
)
)
)
as usedQty
from purchases as a
where item_id = 1) as t
on u.id = t.id
set u.availableQty = u.availableQty-t.usedQty;
fiddle: http://sqlfiddle.com/#!9/0a756/1