-2

I would like to ask for a solution on how to insert n rows based on the values of a field in another table.

Consider the following tables:

CREATE TABLE Input (
    ID INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(128),
    Ticket_Piece INT
);

CREATE TABLE Output (
    Ticket_ID INT AUTO_INCREMENT PRIMARY KEY,
    Transaction_ID INT,
    Ticket_Number VARCHAR(23) UNIQUE,
    FOREIGN KEY (Transaction_ID)
        REFERENCES Input (ID)
        ON UPDATE CASCADE
        ON DELETE CASCADE
);

If a row from the Input table has n in the Ticket_Number column, then n rows should be inserted into the Output table, with Ticket_Number having values "ID-1" through "ID-n" (e.g. (4, "D", 5) in Input should result in rows with ticket numbers "4-1" through "4-5" being added to Output). How can rows for Output be generated in a range of numbers based on the Ticket_Piece column using PHP and MySQL?

For example, with the input:

INSERT INTO Input (ID, Name, Ticket_Piece)
  VALUES
(1, 'A', 2),
(2, 'B', 1),
(3, 'C', 3)
;

the result should be:

Ticket_ID Transaction_ID Ticket_Number
1 1 1-1
2 1 1-2
3 2 2-1
4 3 3-1
5 3 3-2
6 3 3-3
outis
  • 75,655
  • 22
  • 151
  • 221
  • Could you explain the relationship between the input and output more clearly? Where do the transaction IDs come from? – Barmar Sep 05 '22 at 05:49
  • the ID in the input is connected to the Transaction_ID. it is repeated based on the values in the TICKET PIECE field. I was aiming to insert rows based on the values in the TICKET PIECE while simultaneously generating values in the Ticket_number field (1-1. 1-2. 1-3) where the first digit is the transaction id and the second digit is the ticket piece. – Benjamin Joseph Bonnevie Sep 05 '22 at 05:57
  • Covered in part by "[Generating a range of numbers in MySQL](/q/186756/90527)". – outis Sep 10 '22 at 22:01
  • See also "[How to generate a range of numbers between two numbers?](/q/21425546/90527)". Many of the solutions (such as recursive CTEs) can be adapted to MySQL. – outis Sep 10 '22 at 22:11

2 Answers2

1

For each row you fetch from the input table, use a for loop to insert multiple rows into the output table.

$res = $pdo->query("SELECT id, ticket_piece FROM Input_Table");
$insert_stmt = $pdo->prepare("INSERT INTO Output_Table (transaction_id, ticket_number) VALUES (:id, :ticket)");
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
    $pieces = $row['ticket_piece'];
    $id = $row['transaction_id'];
    for ($i = 1; $i <= $pieces; $i++) {
        $insert_stmt->execute([':id' => $id, ':ticket' => "$id-$i"]);
    }
}
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Thank you so much, I will be trying this as soon as possible. I'm sorry I couldn't leave because I only started here in the stack and I have yet to gain the number of reputations to cast a vote. Thank you so much, you're a life-saver. – Benjamin Joseph Bonnevie Sep 05 '22 at 06:07
  • While I'm impressed you may know what a ticket piece is, and you did not use `SELECT *` , I am very disappointed you use FETCH_ASSOC. – Misunderstood Sep 05 '22 at 06:49
  • @Misunderstood I was going to use `FETCH_OBJ`, but most people seem to prefer assoc, so I went with the flow. The important thing is the algorithm. – Barmar Sep 05 '22 at 16:18
  • @Misunderstood: what issue do you see with `FETCH_ASSOC` in this case? Are you envisioning a proper ORM? – outis Sep 10 '22 at 22:12
  • @outis I would use this: `while (list($id,$pieces ) = mysqli_fetch_array($results, MYSQLI_NUM)){` – Misunderstood Sep 11 '22 at 00:14
  • @Misunderstood I never use that style when processing query results. – Barmar Sep 11 '22 at 00:29
  • @Barmar What style? Procedural or assigning the variable using `list()`. PHP assigned to values to an associative array, and then later you assigned them to $id and $pieces. Most of the time I echo the variable from the query or use it in a string. Associative arrays are problematic for echo and strings. Why would you never do that? – Misunderstood Sep 11 '22 at 00:44
  • @Misunderstood I just use `FETCH_ASSOC` or `FETCH_OBJ`, then I use `$row['col']` or `$row->col` – Barmar Sep 11 '22 at 02:05
  • @Barmar I understand what you do. It seemed to me, you saying to me that you would NEVER do it my way. I used to do it your way years ago. There was even a time I used `SELECT *`. Not that I would ever admit that. If nothing else it's less typing. And as I got older I have a really difficult time typing. In general if I can avoid using an associative array I will. It goes back to my electrical engineering days when I wrote a sort and search app in assembly language for my Intro to Microprocessors class. I visualize how the µP has to work so much harder with an associative array. – Misunderstood Sep 11 '22 at 02:27
  • @Misunderstood: I see what you mean. I had though you were referring to something such as "[PDO:: fetch_assoc and fetch_object difference & when best to use](/q/54652036/90527)". As for associative array vs. object vs. destructuring a numeric array, that portion of the code is so likely to take up so little of the execution time (especially compared to I/O delays) that it scarcely matters which is used. [Premature optimizations &c](/q/385506/90527), [micro-optimizations](/q/3470990/90527) and all that. – outis Sep 11 '22 at 04:02
  • Also, I'm fairly sure PHP uses hash tables internally not only for string keys but numeric keys, so there's little difference between the two when it comes to performance. – outis Sep 11 '22 at 04:05
  • Looks like it's a little more complex than that, as [numerically indexed arrays](https://www.zend.com/php-arrays) can be "packed" (essentially, index-is-offset) hash tables in some circumstances, and standard hash tables (which have an extra table lookup but are still essentially O(1)) in others. They aren't the tree-based implementations some systems used, if that was your experience. – outis Sep 11 '22 at 04:31
  • @outis The efficiency does not matter in this app. I find associative arrays be be more cumbersome. It's a personal choice. I started writing code on the original 1 Mhz IBM PC Where it took 4 cycles to execute a NOP instruction. So I grew up when efficiency made a huge difference. One thing I learned over the years is the little thing add up and become significant. Today's Intel processors have a pipeline. It is very important to not cause the pipeline to have to reload. Branch statements can do that . Intel recommends not to use IF statements. In place of IF's I use arrays. – Misunderstood Sep 11 '22 at 04:56
  • @outis The other day I tried to write a javascript Tic Tac Toe app. Think about how you would do that. How many IF statements would you need? Take a look at the JS in my Tic Tac Toe app. http://eatled.com/ttt.html No IF statements. Making moves was very easy. It's just the first 4 lines in the move() function. It checks if the move was valid and who is the next player. The rest of the code is to check when someone won and then stop anymore clicks. Yeah. I'm not normal. I cannot even think how awful it would be to be normal. – Misunderstood Sep 11 '22 at 04:56
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/247960/discussion-between-outis-and-misunderstood). – outis Sep 11 '22 at 05:01
0

A solution in PHP or SQL will likely need to use a loop.

If this comes from the data model rather than business rules (and depending on other factors), a trigger might be a fairly simple option. The trigger body could have a WHILE or other loop to iterate over the ticket piece numbers, and CONCAT to combine the ID and piece number into a ticket number, inserting each in turn.

DELIMITER ;;
CREATE TRIGGER create_ticket_pieces
  AFTER INSERT ON Input
  FOR EACH ROW
BEGIN
  DECLARE piece INT DEFAULT 1;
  WHILE piece <= NEW.Ticket_Piece DO
    INSERT INTO Output (Transaction_ID, Ticket_Number) Values (NEW.ID, Concat(NEW.ID, '-', piece));
    SET piece := piece + 1;
  END WHILE;
END;;
DELIMITER ;

An alternative some use is to pregenerate a table of numbers, then join with this table to generate rows:

INSERT INTO Output (Transaction_ID, Ticket_Number)
  SELECT Input.ID, Concat(Input.ID, '-', Numbers.Number)
    FROM Input
      JOIN Numbers ON Numbers.Number <= Input.Ticket_Piece
    WHERE ... -- select Input rows

It should be noted that by duplicating information (the transaction ID) in two different columns, the Output table isn't normalized. In particular, it violates 3rd normal form, due to a functional dependency of Transaction_ID on Ticket_Number. The way to resolve this is to leave the transaction ID out of the ticket number field (i.e. Output.Ticket_Number holds only the generated integer ≤ Input.Ticket_Piece). (See: "Third Normal Form: Composite PRIMARY KEY vs System-Generated Surrogate (IDENTITY)")

outis
  • 75,655
  • 22
  • 151
  • 221