Cannot figure out how to run a mysqli_multi_query and use the results from the last query
I've never used mysqli_multi_query before and it's boggling my brain, any examples I find on the net aren't helping me to figure out exactly what it is I want to do.
Here is my code:
<?php
$link = mysqli_connect("server", "user", "pass", "db");
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$agentsquery = "CREATE TEMPORARY TABLE LeaderBoard (
`agent_name` varchar(20) NOT NULL,
`job_number` int(5) NOT NULL,
`job_value` decimal(3,1) NOT NULL,
`points_value` decimal(8,2) NOT NULL
);";
$agentsquery .= "INSERT INTO LeaderBoard (`agent_name`, `job_number`, `job_value`, `points_value`) SELECT agent_name, job_number, job_value, points_value FROM jobs WHERE YEAR(booked_date) = $current_year && WEEKOFYEAR(booked_date) = $weeknum;";
$agentsquery .= "INSERT INTO LeaderBoard (`agent_name`) SELECT DISTINCT agent_name FROM apps WHERE YEAR(booked_date) = $current_year && WEEKOFYEAR(booked_date) = $weeknum;";
$agentsquery .= "SELECT agent_name, SUM(job_value), SUM(points_value) FROM leaderboard GROUP BY agent_name ORDER BY SUM(points_value) DESC";
$i = 0;
$agentsresult = mysqli_multi_query($link, $agentsquery);
while ($row = mysqli_fetch_array($agentsresult)){
$number_of_apps = getAgentAppsWeek($row['agent_name'],$weeknum,$current_year);
$i++;
?>
<tr class="tr<?php echo ($i & 1) ?>">
<td style="font-weight: bold;"><?php echo $row['agent_name'] ?></td>
<td><?php echo 开发者_运维知识库$row['SUM(job_value)'] ?></td>
<td><?php echo $row['SUM(points_value)'] ?></td>
<td><?php echo $number_of_apps; ?></td>
</tr>
<?php
}
?>
All I'm trying to do is run a multiple query and then use the final results from those 4 queries and put them into my tables.
the code above really doesn't work at all, I just get the following error:
Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given in C:\xampp\htdocs\hydroboard\hydro_reporting_2010.php on line 391
any help?
From the manual: mysqli_multi_query()
returns a bool
indicating success.
To retrieve the resultset from the first query you can use mysqli_use_result() or mysqli_store_result(). All subsequent query results can be processed using mysqli_more_results() and mysqli_next_result().
Here is a function that returns the last result of a multi-query:
function mysqli_last_result($link) {
while (mysqli_more_results($link)) {
mysqli_use_result($link);
mysqli_next_result($link);
}
return mysqli_store_result($link);
}
Usage:
$link = mysqli_connect();
$query = "SELECT 1;";
$query .= "SELECT 2;";
$query .= "SELECT 3";
mysqli_multi_query($link, $query);
$result = mysqli_last_result($link);
$row = $result->fetch_row();
echo $row[0]; // prints "3"
$result->free();
mysqli_close($link);
Okay after some fiddling around, trial and error and taking reference from another post that I came across in a Google search I've managed to solve my problem!
Here's the new code:
<?php
$link = mysqli_connect("server", "user", "pass", "db");
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$agentsquery = "CREATE TEMPORARY TABLE LeaderBoard (
`agent_name` varchar(20) NOT NULL,
`job_number` int(5) NOT NULL,
`job_value` decimal(3,1) NOT NULL,
`points_value` decimal(8,2) NOT NULL
);";
$agentsquery .= "INSERT INTO LeaderBoard (`agent_name`, `job_number`, `job_value`, `points_value`) SELECT agent_name, job_number, job_value, points_value FROM jobs WHERE YEAR(booked_date) = $current_year && WEEKOFYEAR(booked_date) = $weeknum;";
$agentsquery .= "INSERT INTO LeaderBoard (`agent_name`) SELECT DISTINCT agent_name FROM apps WHERE YEAR(booked_date) = $current_year && WEEKOFYEAR(booked_date) = $weeknum;";
$agentsquery .= "SELECT agent_name, SUM(job_value), SUM(points_value) FROM leaderboard GROUP BY agent_name ORDER BY SUM(points_value) DESC";
mysqli_multi_query($link, $agentsquery) or die("MySQL Error: " . mysqli_error($link) . "<hr>\nQuery: $agentsquery");
mysqli_next_result($link);
mysqli_next_result($link);
mysqli_next_result($link);
if ($result = mysqli_store_result($link)) {
$i = 0;
while ($row = mysqli_fetch_array($result)){
$number_of_apps = getAgentAppsWeek($row['agent_name'],$weeknum,$current_year);
$i++;
?>
<tr class="tr<?php echo ($i & 1) ?>">
<td style="font-weight: bold;"><?php echo $row['agent_name'] ?></td>
<td><?php echo $row['SUM(job_value)'] ?></td>
<td><?php echo $row['SUM(points_value)'] ?></td>
<td><?php echo $number_of_apps; ?></td>
</tr>
<?php
}
}
?>
after sticking mysqli_next_result in there multiple times for each query it magically worked! yay! I understand why it works, because i'm telling it to skip to the next result 3 times, so it skips to the result for query #4 which is the one i want to use.
Seems a bit clunky to me though, there should just be a command for something like mysqli_last_result($link) or something if you ask me...
Thanks for the help rik and f00, I got there eventually :)
I would simplify what you're trying to do by creating a stored procedure which will produce the leader/agent stats and then just call it from your php (single call) as follows:
full script here : http://pastie.org/1451802
alternatively, you could probably combine your individual queries into a single select/group by statement.
see here : http://pastie.org/1451842
select
leaders.agent_name,
sum(leaders.job_value) as sum_job_value,
sum(leaders.points_value) as sum_points_value
from
(
select
agent_name,
job_number,
job_value,
points_value
from
jobs
where
year(booked_date) = 2011 and weekofyear(booked_date) = 2
union all
select distinct
agent_name,
0,0,0
from
apps
where
year(booked_date) = 2011 and weekofyear(booked_date) = 2
) leaders
group by
agent_name
order by sum_points_value desc;
Stored procedure
drop procedure if exists list_leaders;
delimiter #
create procedure list_leaders
(
in p_year smallint unsigned,
in p_week tinyint unsigned
)
begin
create temporary table tmp_leaders(
agent_name varchar(20) not null,
job_number int unsigned not null default 0, -- note the default values
job_value decimal(3,1) not null default 0,
points_value decimal(8,2) not null default 0
)engine=memory;
insert into tmp_leaders (agent_name, job_number, job_value, points_value)
select agent_name, job_number, job_value, points_value from jobs
where year(booked_date) = p_year and weekofyear(booked_date) = p_week;
insert into tmp_leaders (agent_name) -- requires default values otherwise you will get nulls
select distinct agent_name from apps
where year(booked_date) = p_year and weekofyear(booked_date) = p_week;
select
agent_name,
sum(job_value) as sum_job_value,
sum(points_value) as sum_points_value
from
tmp_leaders
group by
agent_name order by sum_points_value desc;
drop temporary table if exists tmp_leaders;
end#
delimiter ;
call list_leaders(year(curdate()), weekofyear(curdate()));
PHP script
<?php
ob_start();
try
{
$db = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);
if ($db->connect_errno)
throw new exception(sprintf("Could not connect: %s", $db->connect_error));
$sqlCmd = "call list_leaders(2011, 2)";
$result = $db->query($sqlCmd);
if(!$result) throw new exception(sprintf("Invalid query : %s", $sqlCmd));
if($db->affected_rows <= 0){
echo "no leaders found !";
}
else{
$leaders = $result->fetch_all(MYSQLI_ASSOC);
foreach($leaders as $ldr){
// do stuff
echo $ldr["agent_name"], "<br/>";
}
}
}
catch(exception $ex)
{
ob_clean();
echo sprintf("zomg borked - %s", $ex->getMessage());
}
if(!$db->connect_errno) $db->close();
ob_end_flush();
?>
Kinda simpler now - hope it helps :)
Store result in a variable and in the end use that variable.
do{
if($result = $con->store_result()){
$data=$result->fetch_all();
$result->free();
}
} while($con->more_results()&&$con->next_result());
echo(json_encode($data));
I'd like to tidy up the poster's currently accepted solution to align it more with what I consider to be best practice.
- While multiple lines of mysqli_next_result($link) gives the intended result, it looks a bit hackish. So I'm going to create a DO-WHILE in their place.
- rik's solution involving the creation of function mysqli_last_result($link) is superfluous. My WHILE condition will accomplish the same effect. Additionally, rik's code mixes procedural and object oriented styles, which should be avoided.
- I won't bounce around between php and html.
- I'll just tweak the class toggle a little.
- I'll put getAgentAppsWeek() directly inline, as I don't like to create variables that are only mentioned one time; and because Harry Weinert did a good job of naming the function.
IMO this is just easier to read.
if(mysqli_multi_query($link,$agentsquery)){ do{ if($result=mysqli_store_result($link)){ // ignore if no record set while($row=mysqli_fetch_array($result)){ echo "<tr class=\"tr",(++$i & 1),"\">"; echo "<td style=\"font-weight:bold;\">",$row['agent_name'],"</td>"; echo "<td>",$row['SUM(job_value)'],"</td>"; echo "<td>",$row['SUM(points_value)'],"</td>"; echo "<td>",getAgentAppsWeek($row['agent_name'],$weeknum,$current_year),"</td>"; echo "</tr>"; } mysqli_free_result($result); } } while(mysqli_more_results($link) && mysqli_next_result($link)); } if($error_mess=mysqli_error($link)){ echo "<tr class=\"error\"><td colspan=\"4\">Error: $error_mess</td></tr>"; } //if any query returns false, mysqli_multi_query will stop // and the individual query error to blame will be provided.
I suppose I'll throw my hat in the ring on this question too (and hey it's still procedural and all that jazz). This is not fool proof, but I have fought and fought with MySQLi and it's band of merry men involved with multi_query and I couldn't get it to play nicely the way I wanted to, or have the flexibility that I needed. I saw several examples where some programmers were simply running explode(';', $sql_statements)
which made my eyes bleed with how horribly wrong that can be.
My solution may not work for you, but this worked for me. (no it's not bulletproof either, but does the job for my particular application).
<?php
$file = file_get_contents('test_multiple_queries.sql');
$result = preg_split("/;(?=\s*(create|insert|update|alter|show|explain|truncate|drop|delete|replace|start|lock|commit|rollback|set|begin|declare|rename|load|begin|describe|help))/im", $file);
$result = array_map('trim', $result);
foreach($result as $sql_query) {
// Procedural style
$my_query = mysqli_query($link, $sql_query);
// Now you can get errors easily, or affected_rows, or whatever
// using much simpler, readable code
mysqli_error($link);
mysqli_affected_rows($link);
// or go crazy with some other stuff
$words = preg_split("/\s+/", $sql_query);
switch(strtolower($words[0])) {
case 'insert':
// do something nifty like...
echo 'New ID: '.mysqli_insert_id($link)."\n";
break;
case 'drop':
// obviously run this before the query, simply here for example
echo 'Hey young (man|lady)! We don\'t drop anything!';
break;
case 'select':
// hooray for selecting stuff
while($rs = mysqli_fetch_assoc($my_query)) {
// have fun with data
}
break;
}
}
精彩评论