How to Ensure I Caught All Errors from MySQLi::Multi_Query

How do I ensure I caught all errors from MySQLi::multi_query?

Despite the code example in the docs, perhaps the better method would be something like this:

if ($mysqli->multi_query(...)) {
do {
// fetch results

if (!$mysqli->more_results()) {
break;
}
if (!$mysqli->next_result()) {
// report error
break;
}
} while (true);
}

How to get error message with mysqli::multi_query?

Peter deleted his answer, but it hinted at what the problem was. I have to put the error check outside of the loop.

Below everything, I need to write

if($mysqli->errno) die($mysqli->error)

The next_result function returns false if there's an error on the next query, so it won't even enter the body of the loop.

How to get all errors after calling to mysqli_multi_query?

Actually your code doesn't really make sense. The proper way to handle multiqueries is the following (see php manual)

if ($mysqli->multi_query($query)) {
do {
// store first result set
if ($result = $mysqli->store_result()) {
while ($row = $result->fetch_row()) {
// do something with the row
}
$result->free();
}
else { error_report(); }
} while ($mysqli->next_result());
}
else { error_report(); }

Strict Standards: mysqli_next_result() error with mysqli_multi_query

While pipodesign corrected the error within the $querystring and alleviated the problem, the actual solution was not provided regarding the Strict Standards error.

I disagree with SirBT's advice, changing from DO WHILE to WHILE is not necessary.

The Strict Standards message that you receive is quite informative.
To obey, use this:

do{} while(mysqli_more_results($db) && mysqli_next_result($db));

Then, there is no need for you to write a conditional exit or break inside of the loop because the while condition will break the loop on the first occurrence of an error. *note, the if statement before the do-while will deny entry to the loop if the first query has an error.

In your example, you are only running INSERT queries, so you won't receive any result sets to process. If you want to count how many rows you've added, use mysqli_affected_rows().

As a complete solution for your question:

if(mysqli_multi_query($db,$querystring)){
do{
$cumulative_rows+=mysqli_affected_rows($db);
} while(mysqli_more_results($db) && mysqli_next_result($db));
}
if($error_mess=mysqli_error($db)){echo "Error: $error_mess";}
echo "Cumulative Affected Rows: $cumulative_rows";

Output:

 // if no errors
Cumulative Affected Rows: 2

// if error on second query
Error: [something]
Cumulative Affected Rows: 1

// if error on first query
Error: [something]
Cumulative Affected Rows: 0

LATE EDIT:

Since people new to mysqli are stumbling across this post, I'll offer a general yet robust snippet to handle queries with/without result sets using multi_query() and add a feature to display which query in the array is being handled...

Classic "IF(){DO{} WHILE}" Syntax:

if(mysqli_multi_query($mysqli,implode(';',$queries))){
do{
echo "<br><br>",key($queries),": ",current($queries); // display key:value @ pointer
if($result=mysqli_store_result($mysqli)){ // if a result set
while($rows=mysqli_fetch_assoc($result)){
echo "<br>Col = {$rows["Col"]}";
}
mysqli_free_result($result);
}
echo "<br>Rows = ",mysqli_affected_rows($mysqli); // acts like num_rows on SELECTs
} while(next($queries) && mysqli_more_results($mysqli) && mysqli_next_result($mysqli));
}
if($mysqli_error=mysqli_error($mysqli)){
echo "<br><br>",key($queries),": ",current($queries),"Syntax Error:<br>$mysqli_error"; // display array pointer key:value
}
//if you want to use the snippet again...
$mysqli_error=null; // clear variables
reset($queries); // reset pointer

Reinvented Wheel "WHILE{}" Syntax (...for those who don't like post-test loops):

while((isset($multi_query) && (next($queries) && mysqli_more_results($mysqli) && mysqli_next_result($mysqli))) || (!isset($multi_query) && $multi_query=mysqli_multi_query($mysqli,implode(';',$queries)))){
echo "<br><br>",key($queries),": ",current($queries); // display array pointer key:value
if($result=mysqli_store_result($mysqli)){
while($rows=mysqli_fetch_assoc($result)){
echo "<br>Col = {$rows["Col"]}";
}
mysqli_free_result($result);
}
echo "<br>Rows = ",mysqli_affected_rows($mysqli); // acts like num_rows on SELECTs
}
if($mysqli_error=mysqli_error($mysqli)){
echo "<br><br>",key($queries),": ",current($queries),"Syntax Error:<br>$mysqli_error"; // display array pointer key:value
}
//if you want to use the snippet again...
$multi_query=$mysqli_error=null; // clear variables
reset($queries); // reset pointer

So, either snippet given the following queries will offer the same output:

Query array:

$queries[]="SELECT * FROM `TEST`";
$queries[]="INSERT INTO `TEST` (Col) VALUES ('string1'),('string2')";
$queries[]="SELECT * FROM `TEST`";
$queries[]="DELETE FROM `TEST` WHERE Col LIKE 'string%'";

Output:

0: SELECT * FROM `TEST`
Rows = 0

1: INSERT INTO `TEST` (Col) VALUES ('string1'),('string2')
Rows = 2

2: SELECT * FROM `TEST`
Col = string1
Col = string2
Rows = 2

3: DELETE FROM `TEST` WHERE Col LIKE 'string%'
Rows = 2

Modify my snippets per your needs. Leave a comment if you discover a bug.

php mysqli/multi_query cannot be proceeded more then once

To address why your code isn't working, I'd suggest looking at the full string of $queryPartial before you submit it as a query. I bet there's a syntax error because you're not separating the statements with ; or something like that.

Looking at the query itself, instead of your code, will probably make it more clear where the mistake is.

You should also check for error status returned by the mysqli functions. Most mysqli functions return FALSE if an error occurs, and you should check $connection->error for the error message. If you don't do that, errors might occur, but you'll never know.

I would guess that you're using multi-query to save on database calls, assuming that making fewer calls will help performance.

But the cost of making the call is small compared to the cost of the transactions that insert data. There's practically no benefit to using multi_query().

The following code does the same thing, has virtually the same performance, it's much simpler code:

foreach ($data as $index => $queryToSend) {
$result = $connection->query($queryToSend);
if ($result === false) {
error_log("SQL: $queryToSend ERROR: {$connection->error}");
}
}

(always check for error status returned by mysqli functions!)

If you're trying to speed up performance of bulk inserts, using multi_query() has insignificant benefit compared to other optimizations:

  • Combine multiple rows into a single INSERT statement
  • Combine multiple statements into a single transaction
  • Use LOAD DATA INFILE instead of INSERT

See my presentation Load Data Fast! for performance comparisons.

MySQL get all affected rows for multiple statements in one query

Something like this maybe?

include $_SERVER['DOCUMENT_ROOT'] . '/goalview/includes/db.inc.php';

$sql = array();
$sql[] = "DELETE FROM `persons` WHERE `persons`.`id` = '$person' AND `owner = '$user' AND `userOrContact` = 'contact';"
$sql[] = "DELETE FROM `tabs` WHERE `person` = '$person' AND `ownerIdentity` = '$user' AND `selfOrOther` = 'other';"
$sql[] = "DELETE FROM `tabAccess` WHERE `person`= '$person' AND `givenToIdentity` = '$user';"
$sql[] = "DELETE FROM `personAccess` WHERE `viewedPerson` = '$person' AND `viewerIdentity` = '$user';"

$aff_rows = 0;

foreach($sql as $current_sql)
{
$deleteContacts = mysqli_query($link, $current_sql);
$aff_rows = $aff_rows + mysqli_affected_rows($link);
}

How to identify the query that caused the error using mysqli_multi_query?

Here is an approach that will not only improve the quality of your error messages, it will improve the way you handle your result sets.

$q["Orders"] = "SELECT * FROM orders WHERE location = 'IN' ORDER BY orderNum DESC LIMIT 20";
$q["Inventory"] = "SELECT * FRO inventory";

if (!$link = mysqli_connect("host", "user", "pass", "db")) {
echo "Failed to connect to MySQL: " , mysqli_connect_error();
} elseif (mysqli_multi_query($link, implode(';', $q))) {
do {
$q_key = key($q); // current query's key name (Orders or Inventory)
if ($result = mysqli_store_result($link)) { // if a result set... SELECTs do
while ($row = mysqli_fetch_assoc($result)) { // if one or more rows, iterate all
$rows[$q_key][] = $row;
}
mysqli_free_result($result);
echo "<div><pre>" . var_export($rows[$q_key], true) . "</pre></div>";
}
} while (next($q) && mysqli_more_results($link) && mysqli_next_result($link));
}
if ($mysqli_error = mysqli_error($link)) { // check & declare variable in same step to avoid duplicate func call
echo "<div style=\"color:red;\">Query Key = " , key($q) , ", Query = " , current($q) , ", Syntax Error = $mysqli_error</div>";
}

Error on first query:
If your first query tries to access a table that doesn't exist in the nominated database like: ordersXYZ
Array $rows will not exist, no var_export() will occur, and you will see this response:

Query Key = Orders, Query = SELECT * FROM ordersXYZ WHERE location='IN' ORDER BY orderNum DESC LIMIT 20, Syntax Error = Table '[someDB].ordersXYZ' doesn't exist

Error on second query:
If your first query is successful, but your second query tries to access a non-existent table like: inventory2
$rows["Orders"] will hold the desired row data and will be var_export()'ed, $row["Inventory"] will not exist, and you will see this response:

Query Key = Inventory, Query = SELECT * FROM inventory2, Syntax Error = Table '[someDB].inventory2' doesn't exist

No errors:
If both queries are error free, your $rows array will be filled with the desired data and var_export()'ed, and there will be no error response. With the queried data saved in $rows, you can access what you want from $rows["Orders"] and $rows["Inventory"].


Things to note:

  1. You may notice that I am making variable declarations and conditional checks at the same time, this makes the code more concise but some devs prefer to avoid this.

  2. As my approach uses implode() with a semi-colon on the elseif line, be sure not to add a trailing semi-colon to your queries.

  3. This set of queries always returns a result set because all are SELECT queries, if you have a mixed collection of queries that affect_rows, you may find some useful information at this link(https://stackoverflow.com/a/22469722/2943403).

  4. mysqli_multi_query() will stop running queries as soon as there is an error. If you are expecting to catch "all" errors, you will discover that there will never be more than one.

  5. Writing conditional break points like in the OP's question and solution is not advisable. While custom break points may be rightly used in other circumstances, for this case the break points should be positioned inside of the while() statement of the do() block.

  6. A query that returns zero rows will not cause a error message -- it just won't create any subarrays in $rows because the while() loop will not be entered.

  7. By using the key() function, the OP's if/elseif condition that counts the columns in each resultset row can be avoided. This is better practice because running a condition on every iteration can become expensive in some cases. Notice that the array pointer is advanced inside of $q at the end of each do() iteration. This is an additional technique that you will not find on the php manual page; it allows key() to work as intended.

  8. And, of course, the <div><pre>var_export()...</pre></div> line can be removed from your working code -- it was purely for demonstration.

  9. If you are going to run any more queries after this code block that reuse variables, be sure to clear all used variables so that residual data does not interfere. e.g. $mysqli_error=null; // clear errors & reset($q); // reset array pointer.

  10. Take heed to this somewhat vague warning at your own discretion: http://php.net/manual/en/mysqli.use-result.php :

One should not use mysqli_use_result() if a lot of processing on the
client side is performed, since this will tie up the server and
prevent other threads from updating any tables from which the data is
being fetched.


  1. Lastly and MOST IMPORTANTLY for security reasons, do not display query or query error information publicly -- you don't want sinister people to see this kind of feedback. Equally important, always protect your queries from injection hacks. If your queries include user-provided data, you need to filter/sanitize the data to death before using it in mysqli_multi_query(). In fact when dealing with user input, my very strong recommendation is to move away from mysqli_multi_query() and use either mysqli or pdo prepared statements for your database interactions for a higher level of security.


Related Topics



Leave a reply



Submit