-2

Suppose I have a table T with 3 columns like this:

x | y | z
---------

I also have PHP arrays of variable lengths $x, $y and $z. Let's say for the purpose of illustration that $x = array("A1", "A2"), $y = array("B1", "B2") and $z = array("C1", "C2").

What the best way to insert into T all combinations of $x, $y and $z, where the value of each column in each row is taken from the array with the same name? Thus, in my example, if T was empty beforehand, it should look like this after this operation:

x  |  y  |  z
-------------
A1   B1    C1
A1   B2    C1
A1   B1    C2
A1   B2    C2
A2   B1    C1
A2   B2    C1
A2   B1    C2
A2   B2    C2

Again, the length of each array is not fixed in advance and needn't be the same for all of them, so the solution can't assume a specific length for those arrays.

One way to do that is to create a two-dimensional array in PHP that contains all the combinations, use implode to generate the VALUES clause of the INSERT INTO statement and execute the resulting query, but I would be surprised if there wasn't a way to do this entirely in SQL and if this weren't more efficient. To be honest, performance isn't really an issue for what I'm doing (because there aren't going to be more than a few hundreds combinations at most in each case), but I'm still curious.

EDIT: Based on the replies, it seems I wasn't clear enough about what I was asking for, so let me elaborate. I know how to prepare one or several SQL queries to insert all the possible combinations into T by looping over the arrays in PHP. But what I would like to know is how to write a SQL query based on the inputs in the PHP arrays that will generate the cartesian product and insert that product into T, which I'm guessing would be more efficient because presumably MySQL is going to generate the cartesian product faster than I could in PHP.

Philippe
  • 65
  • 6
  • 4
    This isnt a database problem. It is a combination/permutation problem. Do you have any code to show? – GetSet Sep 15 '20 at 13:13
  • 2
    1. Showing code is _way_ more effective than talking about it. 2. If you have working code and just want someone to look it over, you should post it over at [Code Review](https://codereview.stackexchange.com/) instead. 3. If you don't have any working code, start there. – M. Eriksson Sep 15 '20 at 13:14
  • I haven't tried the solution I described in the last paragraph, but I know it's going to work and that's not why I'm asking this question, hence the absence of code. What I want to know is whether there is a better way to do this entirely in SQL. In other words, instead of generating all the combinations in PHP and preparing a huge `INSERT INTO` statement with a very long `VALUES` clause, I'm wondering if there isn't a way to write a much shorter SQL query that generates the combinations and inserts them into a table. – Philippe Sep 15 '20 at 13:23
  • Okay I edited my question to clarify what I'm asking. Hopefully this will convince you that it _is_ a database/SQL problem. – Philippe Sep 15 '20 at 14:17
  • Maybe you can CREATE a Trigger with INSERT INTO as loop. –  Sep 15 '20 at 13:55

1 Answers1

2

I think the most straight-forward way to do this would be to have three nested loops that generate the VALUES of the INSERT statement.

The following code is meant to be illustrative. Take care that you don't run into common SQL vulnerabilites like e.g. SQL Injections (as mentioned in some of the comments below) should you ever put this code to production!

Try it like this:

<?php
$x = ["A1", "A2"];
$y = ["B1", "B2"];
$z = ["C1", "C2"];

foreach ($x as $i) {
    foreach ($y as $j) {
        foreach ($z as $k) {
            echo "INSERT INTO `T` (`x`, `y`, `z`) VALUES ('$i', '$j', '$k');\n";
        }
    }
}
?>

The output of this would be

INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A1', 'B1', 'C1');
INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A1', 'B1', 'C2');
INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A1', 'B2', 'C1');
INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A1', 'B2', 'C2');
INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A2', 'B1', 'C1');
INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A2', 'B1', 'C2');
INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A2', 'B2', 'C1');
INSERT INTO `T` (`x`, `y`, `z`) VALUES ('A2', 'B2', 'C2');

Upate:

To solve this entirely in MySQL you could use CROSS JOIN to get a cartesian product (A B C). You will first have to define the three sets (A, B and C) and insert your data like this:

CREATE TABLE `A` (`x` VARCHAR(100));
CREATE TABLE `B` (`y` VARCHAR(100));
CREATE TABLE `C` (`z` VARCHAR(100));
INSERT INTO `A` VALUES('A1'), ('A2');
INSERT INTO `B` VALUES('B1'), ('B2');
INSERT INTO `C` VALUES('C1'), ('C2');

Now you can query the cartesian product with CROSS JOIN:

SELECT * FROM `A` CROSS JOIN `B` CROSS JOIN `C`;
+------+------+------+
| x    | y    | z    |
+------+------+------+
| A1   | B1   | C1   |
| A2   | B1   | C1   |
| A1   | B2   | C1   |
| A2   | B2   | C1   |
| A1   | B1   | C2   |
| A2   | B1   | C2   |
| A1   | B2   | C2   |
| A2   | B2   | C2   |
+------+------+------+
8 rows in set (0,00 sec)

And I just learned from the MySQL Reference Manual that CROSS JOIN is an syntactic equivalent for JOIN aswell as INNER JOIN so these would work the same way:

 SELECT * FROM `A` JOIN `B` JOIN `C`;
 SELECT * FROM `A` INNER JOIN `B` INNER JOIN `C`;

I hope that this is a better answer for what you were aiming at :-)

digijay
  • 1,329
  • 4
  • 15
  • 25
  • Thanks, this is very similar to the solution I sketched in the last paragraph of my question (except that I would prepare a unique `INSERT INTO` statement instead of one per row), but I'm wondering if there is a way to generate the combinations in SQL, which I'm guessing would be more efficient. – Philippe Sep 15 '20 at 13:26
  • 2
    beware this is vulnerable to SQL injection. I know right now the values are coming from an array of defined values, but if that changes and the values come from the user this is bad news. https://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php for more info – WOUNDEDStevenJones Sep 15 '20 at 13:27
  • [Little Bobby](http://bobby-tables.com/) says the code in your answer **[may be at risk for SQL Injection Attacks](https://stackoverflow.com/q/60174/)**. **Please** _add a warning about this_, and you should include information on [Prepared Statements](https://en.wikipedia.org/wiki/Prepared_statement) for [MySQLi](http://php.net/manual/en/mysqli.quickstart.prepared-statements.php) or [PDO](http://php.net/manual/en/pdo.prepared-statements.php). – GrumpyCrouton Sep 15 '20 at 13:37
  • How would this be open to SQL injections when a) the values are string literals (as opposed to input parameters) and b) I'm not executing any SQL statement, but just generate INSERT statements as strings? This is just meant to be an example for generating the desired statements. – digijay Sep 15 '20 at 13:47
  • @GetSet sorry, but I can't follow you, I just tried to answer the question. I think you mistake me for Philippe. But thanks for the upvote. – digijay Sep 15 '20 at 13:50
  • 1
    @Philippe okay, I get your point now, maybe I'll find a better solution later, then I will add it. – digijay Sep 15 '20 at 13:52
  • 1
    Yeah i did mistake that @digijay. My bad. – GetSet Sep 15 '20 at 13:55
  • 1
    @digijay, thanks a lot, I'm sorry if I wasn't clear, I just edited my question to clarify what I'm asking. – Philippe Sep 15 '20 at 13:59
  • By the way, SQL injection attacks are definitely something I will have to think about (in my actual problem, the values in the arrays are user inputs), but I know how to do that and I wanted to restrict my question to the part I don't know about. So @WOUNDEDStevenJones is right that I should keep that in mind, but it's fine that digijay ignored that in his answer since, in my question, I illustrated with arrays of defined values. – Philippe Sep 15 '20 at 14:06
  • 1
    I personally use PDO for anything everywhere, and I can only recommend it to anyone who is working on web projects involving relational databases for data persistence. But this was, as I said, just for illustration. – digijay Sep 15 '20 at 14:08
  • @digijay Like I said, "I know right now the values are coming from an array of defined values, but if that changes and the values come from the user this is bad news." Though technically even if you have array hardcoded with a string like `Robert'); DROP TABLE Students;--` this query will be exploited. – WOUNDEDStevenJones Sep 15 '20 at 15:45
  • 2
    @WOUNDEDStevenJones You're absolutely right with that, I will add an admonition to my answer. Thanks! BTW. I (hopefully) found another solution according to Phillipe's explanation and made a new answer of it (see below). – digijay Sep 15 '20 at 16:02
  • Thanks @digijay, this is what I wanted, though I was hoping it wouldn't be necessary to create tables that contain the values in the PHP arrays. I'm guessing it's possible to write a query that will create derived tables for that instead of using a CREATE TABLE statement, but it's still less elegant than I had hoped. Anyway, it's good to know in case I need to do something like that again, but performance is an issue. – Philippe Sep 15 '20 at 21:11