19

I have a stored procedure that has multiple result sets. How do I advance to the 2nd result set in mysqli to get those results?

Let's say it's a stored proc like:

create procedure multiples( param1 INT, param2 INT )
BEGIN

SELECT * FROM table1 WHERE id = param1;

SELECT * FROM table2 WHERE id = param2;

END $$

The PHP is something like this:

$stmt = mysqli_prepare($db, 'CALL multiples(?, ?)');

mysqli_stmt_bind_param( $stmt, 'ii', $param1, $param2 );

mysqli_stmt_execute( $stmt );

mysqli_stmt_bind_result( $stmt, $id );

Then this is the part I can't get to work. I've tried using mysqli_next_result to move to the next result set, but can't get it to work. We did get it to work with mysqli_store_result and mysqli_fetch_assoc/array/row, but for some reason all the ints get returned as blank strings.

Any one else come across this and have a solution?

MacAnthony
  • 4,471
  • 2
  • 23
  • 26
  • Does the procedure call return properly using mysqli_multi_query() instead of a prepared statement? – gapple Nov 05 '09 at 21:44
  • Oh, sorry. My first response wasn't reading your comment correctly. We need to use prepared statements, but the query is correct and works for the first result set. I just can't figure out how to advance it to the 2nd result set. – MacAnthony Nov 05 '09 at 21:50

3 Answers3

18

I think you're missing something here. This is how you can get multiple results from stored procedure using mysqli prepared statements:

$stmt = mysqli_prepare($db, 'CALL multiples(?, ?)');
mysqli_stmt_bind_param($stmt, 'ii', $param1, $param2);
mysqli_stmt_execute($stmt);
// fetch the first result set
$result1 = mysqli_stmt_get_result($stmt);
// you have to read the result set here 
while ($row = $result1->fetch_assoc()) {
    printf("%d\n", $row['id']);
}
// now we're at the end of our first result set.

//move to next result set
mysqli_stmt_next_result($stmt);
$result2 = mysqli_stmt_get_result($stmt);
// you have to read the result set here 
while ($row = $result2->fetch_assoc()) {
    printf("%d\n", $row['id']);
}
// now we're at the end of our second result set.

// close statement
mysqli_stmt_close($stmt);

Using PDO your code would look like:

$stmt = $db->prepare('CALL multiples(:param1, :param2)');
$stmt->execute(array(':param1' => $param1, ':param2' => $param2));
// read first result set
while ($row = $stmt->fetch()) {
    printf("%d\n", $row['id']);
}
$stmt->nextRowset();
// read second result set
while ($row = $stmt->fetch()) {
    printf("%d\n", $row['id']);
}

By the way: do you use the procedural style deliberately? Using object oriented style with mysqli would make your code look a little bit more appealing (my personal opinion).

Dharman
  • 30,962
  • 25
  • 85
  • 135
Stefan Gehrig
  • 82,642
  • 24
  • 155
  • 189
  • We are aware of PDO and still need to evaluate it to see if it's better than the mysqli, but this is an existing project and replacing the DB layer isn't realistic. Mixing 2 DB layers is just bad design, IMO. – MacAnthony Nov 06 '09 at 15:00
  • Yes, you are right, that I left out the mysqli_stmt_fetch( $stmt ) line. A previous developer used mysqli-stmt objects instead of result set objects. As stated, when we tried the result set with fetch_assoc, all the int columns returned as blank strings. – MacAnthony Nov 06 '09 at 15:02
  • 1
    This is an old question & answer, but the mysqli solution no longer works... `mysqli_use_result` will just return `false` after `mysqli_stmt_execute`... `mysqli_use_result` can only be used after a "query" – Brad Kent Nov 08 '17 at 20:19
3

This has worked really well for me, it will deal with (as an example) as many Select Lists as there are in your SP. Note how you have to close the $call BEFORE you can then get to the OUT parameters from your SP...

?><pre><?
$call = mysqli_prepare($db, 'CALL test_lists(?, ?, @result)');
if($call == false) {
    echo "mysqli_prepare (\$db, 'CALL test_lists(?, ?, @result) FAILED!!!\n";
} else {
    // A couple of example IN parameters for your SP...
    $s_1 = 4;
    $s_2 = "Hello world!";

    // Here we go (safer way of avoiding SQL Injections)...
    mysqli_stmt_bind_param($call, 'is', $s_1, $s_2);

    // Make the call...
    if(mysqli_stmt_execute($call) == false) {
        echo "mysqli_stmt_execute(\$call) FAILED!!!\n";
    } else {
        //print_r($call);

        // Loop until we run out of Recordsets...
        $set = 0;
        while ($recordset = mysqli_stmt_get_result($call)) {
            ++$set;
            //print_r($recordset);
            echo "\nRecordset #" . $set . "...\n";
            if ($recordset->num_rows > 0) {
                $ctr = 0;
                while ($row = $recordset->fetch_assoc()) {
                    ++$ctr;
                    //print_r($row);
                    echo "\t" . $ctr . ": ";
                    forEach($row as $key => $val) {
                        echo "[" . $key . "] " . $val . "\t";
                    }
                    echo "\n";
                }
            }
            echo $recordset->num_rows . " record" . ($recordset->num_rows == 1 ? "" : "s") . ".\n";
            // Clean up, ready for next iteration...
            mysqli_free_result($recordset);

            // See if we can get another Recordset...
            mysqli_stmt_next_result($call);
        }

        // Then you have to close the $call...
        mysqli_stmt_close($call);
        // ...in order to get to the SP's OUT parameters...
        $select = mysqli_query($db, "SELECT @result");
        $row = mysqli_fetch_row($select);
        $result = $row[0];
        echo "\nOUT @result = " . $result . "\n";
    }
}
?></pre><?

And this is what the output from the above code looks like using my test_lists SP...

Recordset #1...
    1: [s_1] 4  [user_name] Andrew Foster   
    2: [s_1] 4  [user_name] Cecil   
    3: [s_1] 4  [user_name] Sheff   
3 records.

Recordset #2...
    1: [s_2] Hello world!   [section_description] The Law   
    2: [s_2] Hello world!   [section_description] History   
    3: [s_2] Hello world!   [section_description] Wisdom Literature 
    4: [s_2] Hello world!   [section_description] The Prophets  
    5: [s_2] Hello world!   [section_description] The Life of Jesus and the Early Church    
    6: [s_2] Hello world!   [section_description] Letters from the Apostle Paul 
    7: [s_2] Hello world!   [section_description] Other Letters from Apostles and Prophets  
    8: [s_2] Hello world!   [section_description] Prophecy - warnings for the present and revelation of the future  
8 records.

OUT @result = 16
  • Note from [mysqli_stmt_get_result doc](https://www.php.net/manual/en/mysqli-stmt.get-result.php): *"Available only with mysqlnd."* – ToolmakerSteve Aug 14 '20 at 01:44
1

It looks like MySQLi may only support multiple result sets through mysqli_multi_query(), since MySQLi_STMT objects work differently from MySQLi_Result objects.

PDO seems to be somewhat more abstracted, with the PDOStatement objects being able to handle multiple result sets for both regular queries (PDO::query) and prepared statements(PDO:prepare).

gapple
  • 3,463
  • 22
  • 27
  • can also iterate over multiple result sets using `$stmt->get_results()` ... `$stmt->next_result()`.. both functions/methods are only available with "MySQL Native Driver" – Brad Kent Nov 08 '17 at 20:21