I've been working on a re-architecture of a project and I've been experimenting with using MySQL Stored Procedures for some of my business level logic. I'm using PHP 5.2.0, PHP's MySQLi Extension and MySQL 5.0, so stored procedures are new, but stable.
I also make use of the Qcodo ORM framework. Qcodo has support for both of the MySQL API's; Original Mysql and Mysqli. I created my first Stored Procedure using the following code:
<?php
--
-- Account Balance Stored Procedure
--
DROP PROCEDURE IF EXISTS Account_Balance;
DELIMITER |
CREATE PROCEDURE Account_Balance (IN account_id INT, IN before_date DATE)
BEGIN
DECLARE acct_credit, acct_debit, balance INT;
SELECT
SUM(t.amount) INTO acct_credit
FROM
trxn AS t
WHERE
t.credit_account_id = account_id
AND
t.date <= before_date
ORDER BY
t.created DESC, t.trxn_id ASC;
SELECT
SUM(t.amount) INTO acct_debit
FROM
trxn AS t
WHERE
t.debit_account_id = account_id
AND
t.date <= before_date
ORDER BY
t.created DESC, t.trxn_id ASC;
SET balance = acct_debit - acct_credit;
SET balance = IFNULL(balance,0);
SELECT balance;
END;
|
DELIMITER ;
?>There are a lot of tutorials around the internet on how to write a stored procedure for MySQL, so I won't delve into that here. The link to the MySQL documentation on stored procedures is above. The most significant detail was that I chose to return my result, balance, using a result set rather then an OUT variable. The reason for not using an OUT variable was because that would require two queries. For example:
<?php
/* pseudo code */
mysqli_query("CALL Account_Balance(1,'2007-04-09',@balance);"); // No Result Returned
$result = mysqli_query("SELECT @balance;");
?>Making two queries to get one value wasn't desirable for me. When I ran into problems using the result set return method, I even tested this method. It didn't work, so I went back to my result set method.
When I tested my stored procedure from the MySQL Command Line, it worked great.
mysql> CALL Account_Balance(1,'2007-04-09');
+---------+
| balance |
+---------+
| 117224 |
+---------+
1 row in set (0.02 sec)
Query OK, 0 rows affected (0.05 sec)The trouble started when I tried to run this procedure from my PHP code. The first call to my procedure works great, returns the balance and everything is great. However, the second call gives me a Commands out of sync; you can't run this command now error.
Here's a test script of what DIDN'T work.
<?php
$mysqli = new mysqli("localhost","**MyUser**","**MyPass**","**MyDB**");
/* check connection */
if (mysqli_connect_errno()) {
printf("<br />Connect failed: %s\n", mysqli_connect_error());
exit();
}
/* Call my stored procedure the first time */
if ($result = $mysqli->query("CALL Account_Balance(1,'2007-04-09');",MYSQLI_USE_RESULT)) {
printf("<br />First select returned %d rows.\n", $result->num_rows);
/* free result set */
$result->close();
}
else {
printf("<br />First Error: %s\n", $mysqli->error);
}
/* Call my stored procedure the second time */
/* THIS WILL FAIL! */
if ($result = $mysqli->query("CALL Account_Balance(2,'2007-04-09');",MYSQLI_USE_RESULT)) {
printf("<br />Second select returned %d rows.\n", $result->num_rows);
/* free result set */
$result->close();
}
else {
printf("<br />Second Error: %s\n", $mysqli->error);
}
$mysqli->close();
?>The second call to the stored procedure will error out. I discovered that this is because in MySQL 5, stored procedures can possibly return multiple result sets. I found the documentation on how the PHP API deals with multiple result sets a little sparse, but I eventually got something to work.
The solution I discovered was to use mysqli_multi_query instead of mysqli_query.
<?php
$mysqli = new mysqli("localhost","**MyUser**","**MyPass**","**MyDB**");
/* check connection */
if (mysqli_connect_errno()) {
printf("<br />Connect failed: %s\n", mysqli_connect_error());
exit();
}
/* Call my stored procedure the first time */
if ($mysqli->multi_query("CALL Account_Balance(1,'2007-04-09');")) {
do {
/* store first result set */
if ($result = $mysqli->store_result()) {
while ($row = $result->fetch_row()) {
printf("%s\n", $row[0]);
}
$result->close();
}
/* print divider */
if ($mysqli->more_results()) {
printf("-----------------\n");
}
} while ($mysqli->next_result());
}
else {
printf("<br />First Error: %s\n", $mysqli->error);
}
/* Call my stored procedure the second time */
/* THIS TIME IT WORKS!!! */
if ($mysqli->multi_query("CALL Account_Balance(2,'2007-04-09');")) {
do {
/* store first result set */
if ($result = $mysqli->store_result()) {
while ($row = $result->fetch_row()) {
printf("%s\n", $row[0]);
}
$result->close();
}
/* print divider */
if ($mysqli->more_results()) {
printf("-----------------\n");
}
} while ($mysqli->next_result());
}
else {
printf("<br />Second Error: %s\n", $mysqli->error);
}
$mysqli->close();
?>This time, the results are returned without the error. The main difference is the extra loop to check if more results are available, then retrieve them. After the result has been retrieved, you simply access the result set just like you would access a normal mysqli result set.
Now that I know it works, I'm definitely going to make more use of stored procedures. Qcodo 0.3.21 doesn't support the Multi-Query method. I've modified the QMySqli5Database.class.php to support Multi-Query and it works like a champ! I've attached the new database class file and have also submitted it to Mike Ho for inclusion in the next Qcodo release.
Here's the code to use the new Multi-Query functionality in Qcodo:
<?php
public static function AccountBalance($intAccountId, $objDate = NULL) {
$objDatabase = Trxn::GetDatabase();
if(is_null($objDate))
$objDate = new QDateTime('Now');
$strEnd = $objDate->PhpDate("Y-m-d");
$strEnd = $objDatabase->SqlVariable($strEnd);
$intAcctId = $objDatabase->SqlVariable($intAccountId);
$strQuery = sprintf("CALL Account_Balance(%s,%s);",$intAcctId,$strEnd);
$objResults = $objDatabase->MultiQuery($strQuery);
/**
* Because we may have multiple results, snag off the first
* result object
*/
$objResult = array_shift($objResults);
$objDbRow = $objResult->FetchRow();
$intBalance = QType::Cast(array_shift($objDbRow),QType::Integer);
return $intBalance;
}
?>| Attachment | Size |
|---|---|
| QMySqli5Database.class.php.txt | 2.84 KB |


It would be, except that it doesn't work (!) when calling a stored procedure because it returns a second, empty result set, which you have to fetch before you can execute another query. Using msqli_query you can't fetch that second result set, so you can't free it.
Am I the only person who read Henry M.'s comment? Isn't it a much easier solution to simply use mysqli_free_result() calls in the original code?
Thanks for the tutorial. Nice One..
The fact that you got "Commands out of sync; you can't run this command now error." is because you used "MYSQLI_USE_RESULT" in you query. And then you need to use mysqli_free_result($result) before any other query.
The following from PHP manual:
mixed mysqli_query ( mysqli $link, string $query [, int $resultmode] )
...
...
resultmode
Either the constant MYSQLI_USE_RESULT or MYSQLI_STORE_RESULT depending on the desired behavior. By default, MYSQLI_STORE_RESULT is used.
If you use MYSQLI_USE_RESULT all subsequent calls will return error Commands out of sync unless you call mysqli_free_result()
thanks a lot for this wonderful tutorial... i read a lot of tutorials on the net but this is the best one i have read.
more power mike!...
Good
Excellent article I have ever seen, thanks!! : )
Thank you very much, Mike! I couldn't find the solution for that problem on the Net. If not the entry above, I'd probably work on it much, much longer.
Awesome! I'm glad I was able to help, this turned out to be a lifesaver on my Inventory Management project.
Mike, you saved the day! I encountered almost this exact same problem with 0.3.21 and sprocs. I figured out the problem pretty quickly but the solution was elusive. After many hours of experimentation and research, I found this page and implemented your solution in my app in less than an hour (slightly different return requirements). Thank you!
Also, apparently that MultiQuery has now been incorporated in QMySqli5Database.class. It was in the 0.3.21 installation I sync'ed up with in the project I'm working on.
Thanks for posting this.
I was getting the "Commands out of sync" message when executing to stored procedures calls with one connection object and now I know why.
THX!!
Post new comment