tags:

views:

643

answers:

5

I often have large arrays, or large amounts of dynamic data in PHP that I need to run MySQL queries to handle.

Is there a better way to run many processes like INSERT or UPDATE without looping through the information to be INSERT-ed or UPDATE-ed?

Example (I didn't use prepared statement for brevity sake):

$myArray = array('apple','orange','grape');

foreach($myArray as $arrayFruit) {
$query = "INSERT INTO `Fruits` (`FruitName`) VALUES ('" . $arrayFruit . "')";
mysql_query($query, $connection);
}
+14  A: 

OPTION 1 You can actually run multiple queries at once.

$queries = '';

foreach(){
    $queries  .= "INSERT....;";  //notice the semi colon
}

mysql_query($queries, $connection);

This would save on your processing.

OPTION 2

If your insert is that simple for the same table, you can do multiple inserts in ONE query

$fruits = "('".implode("'), ('", $fruitsArray)."')";
mysql_query("INSERT INTO Fruits (Fruit) VALUES $fruits", $connection);

The query ends up looking something like this:

$query = "INSERT INTO Fruits (Fruit)
  VALUES
  ('Apple'),
  ('Pear'),
  ('Banana')";

This is probably the way you want to go.

jerebear
right...so one large payload (potentially 100 insert statements bundled) over separate. makes sense...thanks:)
johnnietheblack
Option #2 is really the better way to go if you can swing it.
jerebear
haha that is some savvy implosion...clever. thanks
johnnietheblack
Yeah...the day I showed that to some co-workers it opened up new doors
jerebear
Of course, don't forget to escape the $fruitsArray, else it'll bite you the moment somebody tries to insert "bunch o'grapes"
Piskvor
Please note that Option 1 does not work as mysql_query() does not support multiple queries in its $query parameter. That's the same for mysqli::query() [mysqli_query()] and PDO::query(). The only function that copes with multiple queries is mysqli::multi_query() [mysqli_multi_query()]
Stefan Gehrig
See...confirmed! Option 2 is the way to go.
jerebear
This answer (opt 2) was very helpful for me. For an import I was doing one insert per record for a huge set of data. I spent an hour re factoring to use something based on your second idea and it changed the runtime for the import from 10 hours to 2 hours.
Zoredache
Method 2 eliminates the possibility to use mysql_insert_id() for all but the last inserted values.
Jacco
If you're doing a mass import, you likely won't need the mysql_insert_id but duly noted
jerebear
+4  A: 

If you have the mysqli class, you can iterate over the values to insert using a prepared statement.

$sth = $dbh->prepare("INSERT INTO Fruits (Fruit) VALUES (?)");
foreach($fruits as $fruit)
{
    $sth->reset(); // make sure we are fresh from the previous iteration
    $sth->bind_param('s', $fruit); // bind one or more variables to the query
    $sth->execute(); // execute the query
}
Jeff Ober
+1  A: 

one thing to note about your original solution over the implosion method of jerebear (which I have used before, and love) is that it is easier to read. The implosion takes more programmer brain cycles to understand, which can be more expensive than processor cycles. premature optimisation, blah, blah, blah... :)

good point...my brains are scrambled as it is
johnnietheblack
Of course, you can hide all the implosion and the other uglier bits behind a function or class to make it easier to read and still get the benefit.
Zoredache
I'd still argue that the function needs to do a predictable and understandable thing, the original php loop is very simple to read and I'd bet that there is not much to be gained in terms of performance.i have to admit to being a bit of a devils advocate on this one, as I love 'clever' solutions.
A: 

I was inspired by jerebear's answer to build something like his second option for one of my current projects. Because of the shear volume of records I couldn't save and do all the data at once. So I built this to do imports. You add your data, and then call a method when each record is done. After a certain, configurable, number of records the data in memory will be saved with a mass insert like jerebear's second option.

// CREATE TABLE example ( Id INT, Field1 INT, Field2 INT, Field3 INT);
$import=new DataImport($dbh, 'example', 'Id, Field1, Field2, Field3');
foreach ($whatever as $row) {
  // add data in the order of your column definition
  $import->addValue($Id);
  $import->addValue($Field1);
  $import->addValue($Field2);
  $import->addValue($Field3);
  $import->nextRow();
}
$import->lastRow();
Zoredache
+1  A: 

One thing to note about jerebear's answer with multiple VALUE-blocks in one INSERT:

It can be rather dangerous for really large amounts of data, because most DBMS have an upper limit on the size of the commands they can handle. If you exceed that with too many VALUE-blocks, your insert will fail. On MySQL for example the limit is usually 1MB AFAIK.

So you should figure out what the maximum size is (ideally at runtime, might be available from the database metadata), and make sure you don't exceed it by spreading your lists of values over several INSERTs.

sleske
In this case, it would likely be wise to implement a combination of array_chunk and the loop to avoid hitting the limit.
jerebear