Mysql:使用INSERT SELECT中的自动增量ID进行后续INSERT

时间:2017-07-02 10:59:59

标签: mysql select insert auto-increment

短版

有人会举一个例子吗?有3个SQL表。使用INSERT ... SELECT,从表1中获取数据并将其插入表2.然后,使用INSERT ... SELECT语句插入的每个表2行的自动增量ID,将行INSERT到表3中。 / p>

INSERT ... SELECT创建多行,但无法从中获取自动增量ID,以便在后续的INSERT语句中使用。

扩展版

我正在寻找一种有效的方法来在第二次INSERT中使用从INSERT ... SELECT创建的自动增量ID。

想象一下仓库中的这种情况。

仓库从供应商处收到货物托盘。托盘包含多个单独的项目,必须将其分派给不同的客户。托盘被预订,分解和检查。然后将每个项目分配给正确的客户并标记为" ready"。此时,将按照每个客户记录的调度状态调度每个项目。每个客户的帐户余额根据项目减少给定值。

问题是将帐户减少与项目派遣联系起来。有3个表:

GoodsIn:记录从供应商到货的托盘

CREATE TABLE GoodsOut ('OutID', 'CustomerID', 'ItemSKU_ID', 'DateDispatched')

GoodsOut:将SKU调度记录到客户

CREATE TABLE Ledger ('LedgerID', 'BalanceClose', 'AdjustmentAmount',  'CustomerID', 'ActionID')

帐户:记录每个客户交易/余额

INSERT INTO Ledger (BalanceClose, AdjustmentAmount, CustomerID) 
SELECT Ledger.BalanceClose + 
    (SELECT @Price:=ItemSKUData.ItemPrice FROM ItemSKUData WHERE ItemSKUData.ItemSKU_ID = GoodsIn.ItemSKU_ID)  AS NEWBALANCECLOSE,
    @Price AS ADJUSTMENTAMOUNT,
    Ledger.CustomerID
FROM Ledger
INNER JOIN GoodsIn ON GoodsIn.CustomerID = Ledger.CustomerID
WHERE GoodsIn.HasBeenChecked = TRUE
    AND Ledger.LedgerID IN (SELECT MAX(Ledger.LedgerID) FROM Ledger GROUP BY Ledger.CustomerID)

(我已经大量简化了这一点 - 请接受不能合并GoodsIn和GoodsOut)

当SKU被标记为准备发货时,我可以使用以下内容自动更新Ledger余额,获取每个客户的最后一个余额行并进行更新

{{1}}

这一切都非常正常 - 我为GoodsIn.HasBeenChecked = TRUE的每个GoodsIn行获得了一个新的Ledger行,其中包含更新的BalanceClose。这些Ledger行中的每一行都在INSERT上获得自动增量Ledger.LedgerID。

然后,我可以执行相同的代码来INSERT到GoodsOut表中。与Ledger一样,GoodsOut.OutID也是一个自动增量ID。

我现在需要将这些Ledger行(Ledger.ActionID)链接到GoodsOut.OutID。这是Ledger.ActionID的目的 - 它需要映射到每个GoodsOut.OutID,以便将Ledger余额的减少与将货物发送给客户的操作相关联。

理论上,如果这是一个INSERT而不是INSERT SELECT,我只需要使用GoodsOut.LAST_INSERT_ID()并在INSERT INTO Ledger上使用它。

但是因为我使用INSERT ... SELECT,我无法获得每行的自动增量ID。

我能看到的唯一方法是在GoodsOut表中使用虚拟列,并将GoodsIn.InID存储在其中。然后,我可以使用INSERT ... SELECT中的WHERE获取GoodsOut.OutID。

虽然它并不是非常优雅和安全。

所以这是我的问题。当BOTH表A和表B中的所有行都是使用INSERT ... SELECT创建时,我需要使用表B的自动增量ID将表A链接到表B.

2 个答案:

答案 0 :(得分:1)

您是对的,当您INSERT...SELECT进行批量插入时,您无法轻松访问自动增量ID。 LAST_INSERT_ID()仅返回生成的第一个 ID。

批量插入的一个记录行为是生成的id保证是连续的,因为批量插入会锁定表直到语句结束。

https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html说:

  
      
  • innodb_autoinc_lock_mode = 1(“连续”锁定模式)

         

    这是默认的锁定模式。在此模式下,“批量插入”使用特殊的AUTO-INC表级锁定并保持它直到语句结束。这适用于所有INSERT ... SELECTREPLACE ... SELECTLOAD DATA语句。一次只能执行一个持有AUTO-INC锁定的语句。

  •   

这意味着如果您知道生成的第一个值,以及插入的行数(您应该从ROW_COUNT()获得),以及插入的行的顺序,那么您可以可靠地知道所有id& #39;生成。

例如,MySQL JDBC驱动程序依赖于此。当您执行批量插入时,生成的id的完整列表不会返回给客户端(即JDBC驱动程序),但驱动程序有一个Java方法来返回完整列表。这是通过Java代码推断值并假设它们是连续的来实现的。

答案 1 :(得分:0)

感谢比尔,这是有道理的。该方法将为我生成的自动ID提供最小值和最大值,但它不允许我唯一标识table2中的每一行,从而将(外键)映射到table3。

考虑客户在表1(GoodsIn)中有2个SKU订单的情况。两者都将添加到表2(GoodsOut),表3(Ledger)将减少。但是,使用此方法无法知道GoodsOut中的哪一行映射到Ledger中的行,因为我们只有INSERT ... SELECT添加的最大 - 最大自动ID范围。

在我的脑海里完成它,我真正追求的(现在我意识到它不可能存在)是一个函数,它返回一个auto-id数组加上SELECT后的给定列中的值。 ..INSERT。

令人沮丧的是,这在PHP中非常简单,但我不想将所有数据库数据加载到PHP中,转换,然后再将其写回,因为与直接在数据库查询中实现它相比,性能成本很高。

我决定坚持我目前的方式 - 使用包含table1.ID的临时列table2.temp_table1_id。然后,我可以在表3中进行另一次INSERT UPDATE,连接表1和表1。 2,并根据需要检索table2数据。

我有点希望在没有额外步骤的情况下有一种优雅的方式来做到这一点,但似乎没有。我认为我现在的解决方案目前只有最小的重复和重复。

有兴趣听取任何其他建议/方法。