PHP inserting array into an object - php

I am trying to inject an array into an object but it's just not working. This is what I am doing:
1) Get a specific Match record from the database
2) Get all the Player records from the database that are associated with that match
3) Add them players to the Match object
Code:
$matchQuery = "SELECT * FROM matches where new = 1 order by date asc limit 1";
$matchResult = mysql_query($matchQuery,$link) or die('Errant query: '.$matchQuery);
/* create one master array of the records */
$matches = array();
if(mysql_num_rows($matchResult)) {
while($match = mysql_fetch_assoc($matchResult)) {
$playersQuery = "SELECT p.* FROM match_players mp
LEFT JOIN players p on p.id = mp.player_id
WHERE mp.match_id = '$match->id'";
$playerResult = mysql_query($playersQuery,$link) or die('Errant query: '.$playersQuery);
$players = array();
if(mysql_num_rows($playerResult)) {
while($player = mysql_fetch_assoc($playerResult)) {
$match->players[] = $player; //<-- This doesn't seem to work
}
}
$matches[] = $match;
}
}
The objects within Match are being spat out, BUT, the Players are not.

$match is an array, the result of the deprecated mysql_fetch_assoc(). So $match->players[] = $player; is not going to work.
If there is no players key in the sql result, you can add it to the array:
$match['players'][] = $player;
Otherwise you would have to use a different key.
Another problem is your query in the loop: You use $match->id and that should be $match['id'] as $match is an array.
By the way, doing sql queries in a loop is never a good idea, you should try to get your results in one query joining the different tables.

$match["players"] = array();
while($player = mysql_fetch_assoc($playerResult)) {
$match["players"][] = $player;
}

Related

Is there a better way to run multiple SQL queries to the same table using PHP?

I have a query that requests an ID (the PK) and an order number and throws them into an array. I then loop through the returned data in the array and run two more queries to find the number of times the order number shows up in the database and to get the invoice numbers that belong to that order number. The problem I'm seeing with this setup is that it is taking a while (around 9 seconds) to return the compiled data array. Is there a faster way to get the returned results I'm looking for?
I've tried to find some articles online and came across mysqli_multi_query. Is this the better route to make multiple queries to gather the type of data I am trying to get?
<?php
require 'config.php';
$sql = "SELECT id,internal_order_number FROM orders GROUP BY internal_order_number ORDER BY created_date desc LIMIT 0 ,50";
$query=mysqli_query($mysqli, $sql);
if (!$query) {
throw new Exception(mysqli_error($mysqli)."[ $sql]");
}
$data = array();
while( $row=mysqli_fetch_array($query) ) { // preparing an array
$nestedData=array();
$nestedData['line_id'] = $row["id"];
$nestedData['internal_order_number'] = $row["internal_order_number"];
$data[] = $nestedData;
}
$compiled_data = array();
// Loop through data array with additional queries
foreach($data as $line){
$new_data = array();
// Get item counts
$item_counts = array();
$get_count = " SELECT internal_order_number FROM orders WHERE internal_order_number = '".$line['internal_order_number']."' ";
$count_query=mysqli_query($mysqli, $get_count);
while ($counts=mysqli_fetch_array($count_query)){
if (isset($item_counts[$counts['internal_order_number']])) {
$item_counts[$counts['internal_order_number']]++;
} else {
$item_counts[$counts['internal_order_number']] = 1;
}
}
$product_count = $item_counts[$line['internal_order_number']];
// Get invoice numbers
$invoice_array = array();
$get_invoices = " SELECT invoice_number FROM orders WHERE internal_order_number = '".$line['internal_order_number']."'";
$invoice_query=mysqli_query($mysqli, $get_invoices);
while ($invoice=mysqli_fetch_array($invoice_query)){
if(!in_array($invoice['invoice_number'], $invoice_array)){
$invoice_array[] = $invoice['invoice_number'];
}
}
$invoices = implode(", ",$invoice_array);
$new_data['order_number'] = $line['internal_order_number'];
$new_data['count'] = $product_count;
$new_data['invoices'] = $invoices;
$compiled_data[] = $new_data;
}
mysqli_close($mysqli);
print_r($compiled_data);
?>
What, why are you doing basically the same query 3 times. You first one selects them all, you second query requires the same table making sure the first tables order number == the tables order number and the last just grabs the invoice number...?
Just do one query:
SELECT internal_order_number, invoice_number FROM table WHERE ...
Then loop through it and do what you need. You don't need 3 queries...

how to use PHP function to get ranks for multiple columns in a table more efficent

I am trying to get single rank for a user for each stat "column" in the table. I am trying to do this as more efficiently because i know you can.
So I have a table called userstats. in that table i have 3 columns user_id, stat_1, stat_2, and stat_3. I want to me able to get the rank for each stat for the associated user_id. with my current code below i would have to duplicate the code 3x and change the column names to get my result. please look at the examples below. Thanks!
this is how i currently get the rank for the users
$rankstat1 = getUserRank($userid);
<code>
function getUserRank($userid){
$sql = "SELECT * FROM ".DB_USERSTATS." ORDER BY stat_1 DESC";
$result = mysql_query($sql);
$rows = '';
$data = array();
if (!empty($result))
$rows = mysql_num_rows($result);
else
$rows = '';
if (!empty($rows)){
while ($rows = mysql_fetch_assoc($result)){
$data[] = $rows;
}
}
$rank = 1;
foreach($data as $item){
if ($item['user_id'] == $userid){
return $rank;
}
++$rank;
}
return 1;
}
</code>
I believe there is a way for me to get what i need with something like this but i cant get it to work.
$rankstat1 = getUserRank($userid, 'stat_1'); $rankstat2 =
getUserRank($userid, 'stat_2'); $rankstat3 = getUserRank($userid,
'stat_3');
You can get all the stat ranks using one query without doing all the PHP looping and checking.
I have used PDO in this example because the value of the $userid variable needs to be used in the query, and the deprecated mysql database extension does not support prepared statements, which should be used to reduce the risk of SQL injection.
The function could be adapted to use the same query with mysqli, or even mysql if you must use it.
function getUserRanks($userid, $pdo) {
$sql = "SELECT
COUNT(DISTINCT s1.user_id) + 1 AS stat_1_rank,
COUNT(DISTINCT s2.user_id) + 1 AS stat_2_rank,
COUNT(DISTINCT s3.user_id) + 1 AS stat_3_rank
FROM user_stats f
LEFT JOIN user_stats s1 ON f.stat_1 < s1.stat_1
LEFT JOIN user_stats s2 ON f.stat_2 < s2.stat_2
LEFT JOIN user_stats s3 ON f.stat_3 < s3.stat_3
WHERE f.user_id = ?"
$stmt = $pdo->prepare($sql);
$stmt->bindValue(1, $userid);
$stmt->execute();
$ranks = $stmt->fetchObject();
return $ranks;
}
This should return an object with properties containing the ranks of the given user for each stat. An example of using this function:
$pdo = new PDO($dsn, $user, $pw);
$ranks = getUserRanks(3, $pdo); // get the stat ranks for user 3
echo $ranks->stat_2_rank; // show user 3's rank for stat 2
$sql = "SELECT user_id, stat_1, stat_2, stat_3 FROM ".DB_USERSTATS." ORDER BY stat_1 DESC";
Also, unless there is a reason you need ALL users results, limit your query with a WHERE clause so you're only getting the results you actually need.
Assuming you limit your sql query to just one user, this will get that user's stats.
foreach($data as $item){
$stat_1 = $item['stat_1'];
$stat_2 = $item['stat_2'];
$stat_3 = $item['stat_3'];
}
If you get more than one user's stats with your sql query, consider passing your $data array back to the calling function and loop through the array to match the users stats to particular user id's.

How to optimise large mySQL within a foreach loop in PHP?

I have a function that gets user details and returns an associative array consisting of arrays of each user with its related data. My function works except that it doesn't work as well when it has to fetch large number of rows from mySQL.
function function_name($DB, $id)
{
//Prepare, bind and execute statements
//Returns one value or an array
}
function main_function($DB, $id_list)
{
foreach($id_list as $user_id)
{
//Calls function_name
$data = function_name($DB, $user_id);
}
//Returns a nested associative array
}
I have been told that I should move the bind param statement outside of the foreach loop in my case but I have tried and I keep getting error "MySQL has went away" message. How can I optimise querying from mysql when I could potentially be querying 10,0000 id at one time?
Please refer to the code snippet below for detailed explanation.
function getUserEvent($DB_3308, $user_id)
{
$user_event = array ();
$sql_get_user_event = "SELECT * FROM user_event WHERE user_id = ?";
$statement_user_event = $DB_PUMA_3306->link->prepare ( $sql_get_user_event);
$statement_user_event ->bind_param ( "s", $user_id );
$statement_user_event ->execute ();
if ($rs_user_event = $statement_user_event->get_result ())
{
while ( $row = $rs_user_event->fetch_assoc () )
{
$user_event [] = $row;
}
}
return $user_event;
}
function getUserDetails($DB_3306, $DB_3308, $user_list)
{
$user_details = array ();
foreach ( $user_list as $user_id )
{
$temp = array ();
$user_personal = null;
$user_event = null;
$user_personal = getUserContact ( $DB_3306, $user_id );
$user_event = getUserEvent( $DB_3308, $userid );
$temp ['user_id'] = $user_id;
$temp ['full_name'] = $user_personal ['full_name'];
$temp ['tel_no'] = $user_personal ['tel_no'];
$temp ['email'] = $user_personal ['email'];
$temp ['events'] = $user_event ;
$user_details [] = $temp;
}
return $user_details;
}
Why can't you get some 50 or 100 userID in array before fetching that from database and fetch it in bulk to reduce more query load?
$implodedUserIDs = implode(',', $userIDs);
$query = "SELECT * FROM user_event WHERE user_id IN ($implodedUserIDs)";
It will reduce some load. Also you can give some sleep in every load. Just try to optimize your code as much as possible. :)
You appear to be looping around (potentially) 10000 users and for each one performing at least 2 queries. Each query has a small over head to parse it, etc, and hence with a large number of queries this can rapidly add up.
I would suggest that if possible you merge the 2 queries together, doing a join to get both the users contact details and the user event details.
I would also suggest that you perform this single query once in total for all user ids rather than once per user id. Normally this would be easy to do using IN with a list of user ids, but with 10000 this is not really viable. As such generate a temp table containing your list of user ids.
Very roughly (and making assumptions on your database class and on your actual data) something like this:-
function getUserDetails($DB_3306, $DB_3308, $user_list)
{
$sql = 'CREATE TEMPORARY TABLE user_list_tmp
(
user_id INT
)';
$DB_3306->execute($sql);
$user_list_split = array_chunk($user_list, 250);
foreach($user_list_split as $user_list_split_chunk);
{
$sql = 'INSERT INTO user_list_tmp (user_id) VALUES ('.implode('),(', $user_list_split_chunk).')';
$DB_3306->execute($sql);
}
$sql = "SELECT a.user_id, b.full_name, b.tel_no, b.email, c.event_id
FROM user_list_tmp a
INNER JOIN user_contact b
ON a.user_id = b.user_id
LEFT OUTER JOIN user_event c
ON a.user_id = c.userid
WHERE user_id = ?
ORDER BY a.user_id, c.event_id";
$statement_user_event = $DB_3306->link->prepare ( $sql);
$statement_user_event ->execute ();
$user_details = array();
if ($rs_details = $statement_user_event->get_result ())
{
while ( $row = $rs_details->fetch_assoc () )
{
$user_details[$row['user_id']]['user_id'] = $row['user_id'];
$user_details[$row['user_id']]['full_name'] = $row['full_name'];
$user_details[$row['user_id']]['tel_no'] = $row['tel_no'];
$user_details[$row['user_id']]['email'] = $row['email'];
$user_details[$row['user_id']]['events'][] = $row['event_id'];
}
}
return $user_details;
}
This takes you array of passed user ids, chunks it into arrays of 250 and inserts them into a temp table (I tend to insert in batches of 250 as a reasonable balance between a readable and fast insert statement and performing a min number of separate statements - you may chose to split it into larger or smaller chunks).
It then performs a single query that joins the temp table against the user_contact table and left joins it against the user_event table. Each user will return multiple rows, one for each even (but still one row if no events). It is putting these into an array, and I have cheated a bit here by using the user_id as the key of the array. So for the first row for the user id it will save the details for the user, and on any subsequent rows for the user (for further events) the user details will just over write themselves. The events details are just put into the next array member of the events array for that user.

Displaying 5 rows of data from query

I have a simple table (mgap_orders) with customer orders in it. Multiple have the the same id (mgap_ska_id) and I simply want to pull 5 records from the table and display all five.
I can easily get one record with the following query and PDO, but how can I display 5 rows instead of only one row?
$result_cat_item = "SELECT * FROM mgap_orders WHERE mgap_ska_id = '$id' GROUP BY mgap_ska_id";
while($row_cat_sub = $stmt_cat_item->fetch(PDO::FETCH_ASSOC))
{
$item=$row_cat_sub['mgap_item_description'];
$item_num=$row_cat_sub['mgap_item_number'];
$item_type=$row_cat_sub['mgap_item_type'];
$item_cat=$row_cat_sub['mgap_item_catalog_number'];
$item_ven=$row_cat_sub['mgap_item_vendor'];
$item_pur=$row_cat_sub['mgap_item_percent_purchased'];
$item_sales=$row_cat_sub['mgap_item_sales'];
}
Use limit 5 then put the results in an array, like this:
$result_cat_item = "SELECT * FROM mgap_orders WHERE mgap_ska_id = '$id' GROUP BY mgap_ska_id LIMIT 5";
$items = array();
while($row_cat_sub = $stmt_cat_item->fetch(PDO::FETCH_ASSOC))
{
$items['item'] = $row_cat_sub['mgap_item_description'];
$items['item_num'] = $row_cat_sub['mgap_item_number'];
$items['item_type'] = $row_cat_sub['mgap_item_type'];
$items['item_cat'] = $row_cat_sub['mgap_item_catalog_number'];
$items['item_ven'] = $row_cat_sub['mgap_item_vendor'];
$items['item_pur'] = $row_cat_sub['mgap_item_percent_purchased'];
$items['item_sales'] = $row_cat_sub['mgap_item_sales'];
}
Then you can do:
foreach($items as $item) {
// echo $item['item'] or whatever
}
EDIT: Or you can skip putting them in the array and just use the while() to do what you need to do with the data.

Queries with conditional queries

Let's say i have a query with quite a number of joins and subqueries in one php file that handles queries.
Nb: i put an example of what $query looks like at the bottom
$query = query here;
if ($query) {
return $query->result();
} else {
return false;
}
}
Then in my php file that handles the html, i have the usual foreach loop with some conditions that require making other queries e.g;
Note: result houses object $query->result().
foreach ($results as $item) {
$some_array = array();
$some_id = $item->id;
if ($some_id != 0) {
//id_return_other_id is a function that querys a db table and returns the specified column in the same table, it returns just one field
$other_id = id_return_other_id($some_id);
$some_query = another query that requires some joins and a subquery;
$some_array = the values that are returned from some_query in an array
//here i'm converting obj post into an array so i can merge the data in $some_array to item(Which was converted into an array) then convert all of it back into an object
$item = (object)array_merge($some_array, (array)$item);
}
//do the usual dynamic html stuff here.
}
This works perfectly but as i don't like the way i'm doing lot's of queries in a loop, is there a way to add the if $some_id != 0 in the file that handles queries?
I've tried
$query = query here;
//declaring some array as empty when some_id is 0
$some_array = array();
if ($query) {
if ($some_id != 0) {
//same as i said before
$other_id = $this->id_return_other_id($some_id);
$some_query = some query;
$some_array = array values gotten from some query;
}
$qresult = (object)array_merge($some_array, (array)$query->result);
return $qresult;
} else {
return false;
}
}
This doesn't work for obvious reasons, does any one have any ideas?
Also if there's a way to make these conditions and queries in the $query itself i'd love you forever.
Ps: A demo query would be something like
$sql = "SELECT p.*,up.*,upi.someField,etc..
FROM (
SELECT (another subquery)
FROM table1
WHERE table1_id = 3
UNION ALL
SELECT $user_id
) uf
JOIN table2 p
ON p.id = uf.user_id
LEFT JOIN table3 up
ON .....
LEFT JOIN table4
ON ....
LEFT JOIN table5
ON ....
And so on..etc..
ORDER BY p.date DESC";
$query = mysql_query..
It seems like you just need to run two queries in your query file. The first query would get a broad set of what you’re looking for. The second query would query an id that’s in the result and perform a new query to get any details about that particular id. I use something similar to this in the customer search page for my application.
$output = array();
$query1 = $this->db->query("SELECT * FROM...WHERE id = ...");
foreach ($query->result_array() as $row1)
{
$output[$row1['some_id']] = $row1;
$query2 = $this->db->query("SELECT * FROM table WHERE id = {$row1['some_id']}");
foreach ($query2->result_array() as $row2)
{
$output[$row1['some_id']]['data_details'][$row2['id']] = $row2;
}
}
Then in your page that displays html, you’ll just need two foreaches:
foreach($queryresult as $key=> $field)
{
echo $field['some_field'];
foreach($child['data_details'] as $subkey => $subfield)
{
echo $subfield['some_subfield'];
}
}
I know you’re using objects, but you could probably convert this to use that format. I hope this makes sense/helps.
use this
if ($some_id !== 0) {
instead of
if ($some_id != 0) {

Categories