سؤال

I'm seeing a fair number of questions dealing with INSERTing into a single table from multiple tables. I'm trying to do the opposite, and using only MySQL.

I have a temporary table that contains denormalized data. I need to

  1. iterate through each row of the temp table
  2. create a new row on the primary table with some of the fields in the temp table
  3. use the auto-increment ID of the primary table row just created to create a new row on a secondary table that puts the LAST_INSERT_ID() into a primary_id field.

I understand the whole LAST_INSERT_ID() and I'm happy to run a transaction: I just don't know how to create the "outer select loop" that loops through the temp table and then runs 2 subsequent inserts.

لا يوجد حل صحيح

نصائح أخرى

This can work if the values in the temporary table are unique. The idea is to split the inserts into two steps, one into the primary table and the rest into the secondary table:

insert into primary( . . .)
    select . . .
    from temp;

insert into secondary(primaryid, . . . )
    select p.PrimaryId, t.col . . .
    from temp t join
         primary p
         on t.col1 = p.col1 and . . .;

There are some caveats. For instance, you will need more complex logic to handle joins. And, it assumes that each set of primary columns in the temporary table is unique.

The most common approach is a loop in a stored procedure or application code that uses LAST_INSERT_ID.

As @GordonLinoff said, one can loop over LAST_INSERT_ID(). If each set of primary columns in the temporary table might not be unique, there are essentially two ways of accomplishing this:

  1. Rely on a uniqueness constraint over the relevant columns in the primary table, together with MySQL's ON DUPLICATE KEY UPDATE extension to INSERT:

    CREATE PROCEDURE foo() BEGIN
      DECLARE done BOOLEAN DEFAULT FALSE;
      DECLARE cur CURSOR FOR SELECT . . . FROM temp;
      DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
    
      PREPARE ins1 FROM '
        INSERT INTO primary
          (. . .)
        VALUES
          (?, . . .)
        ON DUPLICATE KEY UPDATE
          PrimaryId = LAST_INSERT_ID(PrimaryId)
      ';
      PREPARE ins2 FROM '
        INSERT INTO secondary
          (primaryid, . . .)
        VALUES
          (LAST_INSERT_ID(), ?, . . .)
      ';
    
      OPEN cur;
      read_loop: LOOP
        FETCH cur INTO @a, @b, . . .;
        IF done THEN
          LEAVE read_loop;
        END IF;
    
        EXECUTE ins1 USING @a, . . .;
        EXECUTE ins2 USING @b, . . .;
      END LOOP;
      CLOSE cur;
    
      DROP PREPARE ins1;
      DROP PREPARE ins2;
    END
    
  2. Sort by the temporary table by the (non-unique) primary columns, then keep track of the last seen values and only insert into the primary table when a new record is encountered:

    CREATE PROCEDURE foo() BEGIN
      DECLARE done BOOLEAN DEFAULT FALSE;
      DECLARE cur CURSOR FOR SELECT . . . FROM temp ORDER BY . . .;
      DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
    
      PREPARE ins1 FROM '
        INSERT INTO primary
          (. . .)
        VALUES
          (. . .)  -- use "?" placeholders
      ';
      PREPARE ins2 FROM '
        INSERT INTO secondary
          (primaryid, . . .)
        VALUES
          (. . .)  -- use "?" placeholders
      ';
    
      OPEN cur;
      FETCH cur INTO @a, @b, . . .;
      WHILE NOT done DO
        SET @current_a := @a, . . .;  -- (non-unique) primary cols
        EXECUTE ins1 USING @a, . . .;
        SET @primaryid := LAST_INSERT_ID();
    
        REPEAT
          EXECUTE ins2 USING @primaryid, @b, . . .;
          FETCH cur INTO @a, @b, . . .;
        UNTIL done OR @a <> @current_a OR . . . END REPEAT;
      END WHILE;
      CLOSE cur;
    
      DROP PREPARE ins1;
      DROP PREPARE ins2;
    END
    
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top