如何锁定行以进行更新,然后使用MySQL中的变量更新表

时间:2015-01-14 01:14:23

标签: mysql sql sql-update sqltransaction

我有一张购买表,用于存储商店收到的所有订单。在某些情况下,我必须以不同的成本重新订购商品。当我进行销售交易时,我需要能够使用由购买日期确定的FIFO(First In,First Our)系统来确定所售商品的总成本。

以下是我的表格的样子

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

但是如何使用新的totalAvailable值更新每一行?

所以最终记录需要看起来像这样

(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);

所以我的问题是这个

  1. 如何正确执行UPDATE?
  2. 如何使用查询查询来锁定行以进行更新,因此在提交此事务之前,其他任何事务都不能更改该值。请注意,查询选择和更新都在同一事务中。
  3. 有更好的方法来计算成本吗?
  4. 我尝试了以下查询来更新值

    - 这在此行@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;
    

1 个答案:

答案 0 :(得分:1)

这是计算成本的一点点不同的解决方案:

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

注意它的工作原理:

返回当前行仍应购买的数量:

greatest(
    0,
    40 - ifnull((select sum(availableQty) from purchases as b where b.id<a.id),0)
)

对于每一行,我们计算从前一行中购买的数量,即我们总结所有先前行的可用数量,从40减去这个数字(我们想要购买的金额),然后我们休息。

这基本上与你已经拥有的循环相同,但是你每次迭代都在减少“休息”,我重新计算整个前一个总和并找到它与总需求之间的差异,所以我在没有循环的情况下为每一行得到休息

但我们可以有负数,这就是我们使用greatest的原因。因此,对于正值(即前一行未满足需求),我们仍然需要从其他行中选择多少,对于所有其他行 - 0,即满足所有需求。

我们可以从当前行中“购买”多少:

least(
      availableQty,
      demandRest /* construction above */
      )

我们只选择整体可用数量或未实现的需求,具体取决于哪个

因此,基本上对于每一行,我们要么得到整个可用数量,要么得到休息,如果前一行满足需求则为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;

小提琴:http://sqlfiddle.com/#!9/0a756/1