PHP - Queries and looping through data structure - php

I'm working on a project that has multi-tiered PHP tasks, one of which combines some MySQL queries with the PHP structures to print results to a text file. I have the connection, txt file creation, and queries completed and successfully functioning. My issues is in the PHP loop for the data structure and the nested loop to iterate/loop through the structure for the results and print them properly. The data structure loop is correct:
$userQuery = "SELECT title, last_name, first_name
FROM film_actor
LEFT JOIN film f
ON film_actor.film_id = f.film_id
LEFT JOIN actor a
ON a.actor_id = film_actor.actor_id
ORDER BY title";
$filmActors =fopen("FilmActors.txt","w");
while ($row = mysqli_fetch_assoc($result))
{
$title = $row['title'];
$last = $row['last_name'];
$first = $row['first_name'];
$filmActor["$title"][] = "$last".","."$first";
}
The nested loop begins correctly for the key value, but every step I've used to loop and print results only prints the word 'array' rather than the elements. My SQL query is for a database that lists film names, and the actors in the films, so some of these films have 5+ actors listed. My goal is to print them to a text file like so: Film Name: Number of actors: Actor 1 last name, actor 1 first name; etc.
Here is what I"ve successfully started for the nested loop:
while ($k_v = each($filmActor)) {
foreach($filmActor as $k_v => $value) {
//$k_v => $value;
fputs($filmActors, "$title:$value\n");
}

Easiest solution looks like this one:
...
$filmActors = fopen("FilmActors.txt","w");
$text = '';
foreach ($filmActor as $film_title => $actors) {
$text .= $film_title . ':' . count($actors) . ':' . implode(';', $actors) . PHP_EOL;
}
fwrite($filmActors, $text);
fclose($filmActors);

Related

How can I store large chunks into json file without memory crash?

I have an array $table and it contains 6000 items.
When I want to convert this array to json and store it into a file, my memory crashes.
So I had the idea to break the array into chunks of parts with 500 items:
$table = array_chunk($table, 500);
$table_json = $serializer->serialize($table[0], 'json', $context);
$myfile = fopen($_SERVER['DOCUMENT_ROOT']."/files/myfile.json", "w") or die("Unable to open file!");
file_put_contents($_SERVER['DOCUMENT_ROOT']."/files/myfile.json", $table_json);
This runs fast now, but of course now only 500 items are stored. Is there a way to add the other parts of the $table array without memory crash?
You could do something like this as you mentioned you know how to use array_chunk();
Let's look into simplifying the process with a built-in PHP function called array_chunk();
We'll be using HTML tables for design which isn't recommended. The task is better accomplished with CSS, you can follow same way without HTML and CSS.
Our table :
id datePosted firstName lastName pictureName anotherColumn
1 2013-07-01 John Smith SmithJohn.jpg anotherValue
2 2013-05-06 Elroy Johnson JohnsonElroy.jpg anotherValue
3 2013-06-18 Jake Bible BibleJake.jpg anotherValue
4 2013-07-17 Steve Stevenson StevensonSteve.jpg anotherValue
5 2013-04-08 Bill Smith SmithBill2.jpg anotherValue
Building HTML Tables
PDO query is used to grab the database information with prepared statements, Note that the loop only generates the code to display the columns.
The tests to detect where the rows begin and end are unnecessary.
//INITIALIZE VARIABLES
$colsToDisplay = 3;
$htmlOutput = array();
//GET PICTURE LIST
$sql = "SELECT datePosted, firstName, lastName, pictureName FROM pictureList ORDER BY datePosted DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$htmlOutput[] = "<td><img src='images/{$row['pictureName']}' alt='' /><br />{$row['firstName']} {$row['lastName']}</td>";
}
Once the loop is done, the array containing the column information can be broken into groups of three... or whatever value was assigned to $colsToDisplay, What we did here ? we tooks 3 columns from table, So divide table in two parts.
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
//...
}
//BREAK THE COLUMNS INTO GROUPS
$htmlOutput = array_chunk($htmlOutput, $colsToDisplay);
All that's left is to display the table information. Note that array_chunk() creates a multi-dimensional array. The foreach loop is used to process the groups of columns. Each group is assigned to $currRow which contains an array of columns for the current row. The implode() function is used to quickly display the columns as a string.
Lets continue :
//BREAK THE COLUMNS INTO GROUPS
$htmlOutput = array_chunk($htmlOutput, $colsToDisplay);
//DISPLAY TABLE
print '<table>';
foreach($htmlOutput as $currRow) {
print '<tr>' . implode('', $currRow) . '</tr>';
}
print '</table>';
Checking For Missing Column Tags
One thing you may have noticed is the code to add missing columns was left out.
In other words, this example results in an HTML table where the last row only has two columns.
Apparently, the missing columns aren't needed according to the W3C Markup Validation Serviceā€¦ so they weren't included. However, they can be added by running the following code right before array_chunk() is called.
$colsDifference = count($htmlOutput) % $colsToDisplay;
if($colsDifference) {
while($colsDifference < $colsToDisplay) {
$htmlOutput[] = '<td></td>';
$colsDifference++;
}
}
Final Code :
//INITIALIZE VARIABLES
$colsToDisplay = 3;
$htmlOutput = array();
//GET PICTURE LIST
$sql = "SELECT datePosted, firstName, lastName, pictureName FROM pictureList ORDER BY datePosted DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$htmlOutput[] = "<td><img src='images/{$row['pictureName']}' alt='' /><br />{$row['firstName']} {$row['lastName']}</td>";
}
//OPTIONAL CODE
//IF NEEDED, ADD MISSING COLUMNS
$colsDifference = count($htmlOutput) % $colsToDisplay;
if($colsDifference) {
while($colsDifference < $colsToDisplay) {
$htmlOutput[] = '<td></td>';
$colsDifference++;
}
}
//END: OPTIONAL CODE
//BREAK THE COLUMNS INTO GROUPS
$htmlOutput = array_chunk($htmlOutput, $colsToDisplay);
//DISPLAY TABLE print '<table>';
foreach($htmlOutput as $currRow) {
print '<tr>' . implode('', $currRow) . '</tr>';
}
print '</table>';
Idea Behind This Tutorial :
You dont need to create a file and write arrays into that file to display in datatable.
If you still wants that you can (divide table in two parts) to create two arrays and than write into file, and than use array_push(); or array_merge(); to get both arrays into one.
I might have bad explanition please forgive for mistakes, Hope this help you in your coding life :)
Sticking to your solution, you can append the other chunks to your exisiting file. This works by setting the flag FILE_APPEND, e.g.:
$table_json = $serializer->serialize($table[1], 'json', $context);
$myfile = fopen($_SERVER['DOCUMENT_ROOT']."/files/myfile.json", "w") or die("Unable to
open file!");
file_put_contents($_SERVER['DOCUMENT_ROOT']."/files/myfile.json", $table_json, FILE_APPEND | LOCK_EX);
(LOCK_EX flag to prevent anyone else writing to the file at the same time)

Display multiple row data within 1 row MySQL PHP

I have the following query which pulls from 3 tables. I'll end up with 1 family per row but with multiple children for each. I want to be able to show all children ages within the family row. I thought about opening another connection/query, but figured there is a smarter way.
Query:
SELECT
families.*, job.*, children.*, families.first_name AS fam_firstname, children.first_name AS child_firstname
FROM job
LEFT OUTER JOIN families ON job.fam_id = families.fam_id
LEFT OUTER JOIN children ON families.fam_id = children.fam_id
WHERE
job.published = 2
GROUP BY job.job_id
ORDER BY job.created_on DESC
Loop:
if ($result = $mysqli->query($query)) {
$from = new DateTime($row['dob']);
$to = new DateTime('today');
while ($row = $result->fetch_assoc()) {
echo '<tr>';
echo '<td>' .$row['fam_firstname']. '</td>';
echo '<td>' .$row['last_name'].'</td>';
/* Looking to list all children ages. Separate by comma or break */
echo '<td>' . $from->diff($to)->y .'</td>';
echo '</tr>';
}
$result->free();
}
Desired Output:
Family First Name | Family Last Name | Child 1 Age, Child 2 Age
You need to use the mysql group_concat function to achieve this:
SELECT
families.*, group_concat(children.age)
FROM job
LEFT OUTER JOIN families ON job.fam_id = families.fam_id
LEFT OUTER JOIN children ON families.fam_id = children.fam_id
WHERE
job.published = 2
group by families.fam_id
ORDER BY job.created_on DESC
Follow this question: Nested Array from multiple tables.
Refer to the second option in the question, that explains how you subtract your data from the JOIN query.
P.S.
I'ts a question I've asked myself, with an implementation with what your'e trying to do here. If you need more lead on how to implement it here, ask in comments...
Here is a way to implement it in your code (notice you should order your JOIN query by "fam_firstname", for this code to work for you):
/* init temp vars to save current family's data */
$current = null;
$fam_firstname = null;
$children = array();
while ($row = mysql_fetch_assoc($result))
{
/*
if the current id is different from the previous id:
you've got to a new family.
print the previous family (if such exists),
and create a new one
*/
if ($row['fam_firstname'] != $fam_firstname )
{
// in the first iteration,
// current (previous family) is null,
// don't print it
if ( !is_null($current) )
{
$current['children'] = $children;
/*
Here you print the whole line
I'm just dumping it all here, but you can print
it more nicer...
*/
var_dump($current);
$current = null;
$fam_firstname = null;
$children = array();
}
// create a new family
$current = array();
$current['fam_firstname'] = $row['fam_firstname'];
/*
Add more columns value here...
*/
// set current as previous id
$fam_firstname = $current['fam_firstname'];
}
// you always add the phone-number
// to the current phone-number list
$children[] = $row['child_firstname'] . " is " . $row['child_age'] . " years old";
}
}
// don't forget to print the last family (saved in "current")
if (!is_null($current))
/*
Here you print the whole line
I'm just dumping it all here, but you can print
it more nicer...
*/
var_dump($current);

how do i left join 3 tables and create variables for multiple fileds in the 3 different tables?

here is entire code updated with suggestions. also added new column names. let me know if it's right.
<?php
//Initialize your first couple variables
$encodedString = ""; //This is the string that will hold all your location data
$x = 0; //This is a trigger to keep the string tidy
//Now we query to the database
$result = mysql_query("SELECT visitors_pb_list.first, visitors_pb_list.last, visitors_pb_list.ip, visitors_pb_list.landing_page as visitors_pb_list_landing_page, pb_list.address, pb_list.landing_page as pb_list_landing_page, ziplatlang.longitude, ziplatlang.latitude FROM visitors_pb_list LEFT JOIN pb_list ON visitors_pb_list.landing_page = pb_list.landing_page LEFT JOIN ziplatlang ON pb_list.zip = ziplatlang.zip_code WHERE landing_page='" . $pb_list_id . "' order by id desc");
//Multiple rows are returned
while ($row = mysql_fetch_array($result, MYSQL_NUM)) {
//This is to keep an empty first or last line from forming, when the string is split
if ( $x == 0 )
{
$separator = "";
}
else
{
//Each row in the database is separated in the string by four *'s
$separator = "****";
}
//Saving to the String, each variable is separated by three &'s
$encodedString = $encodedString.$separator.
"<p class='content'><b>Lat:</b> ".$row['latitude'].
"<br><b>Long:</b> ".$row['longitude'].
"<br><b>Name: </b>".$row['first'].$row['last'].
"<br><b>Address: </b>".$row['address'].
"<br><b>IP: </b>".$row['ip'].
"</p>&&&".$row['latitude']."&&&".$row['longitude'];
$x = $x + 1;
}
?>
<input type="hidden" id="encodedString" name="encodedString" value="<?php echo $encodedString; ?>" />
if you want to know where i got the base code, it's here:
http://www.macrostash.com/2011/09/17/demo-use-a-php-mysql-database-to-load-markers-on-a-google-map/#codesyntax_4
however i'm trying to adapt it to multiple tables. can't do it with just one. need to use visitors_pb_list for the loop. need to use ziplatlang for the latitude & longitude. need to use pb_list to bridge the two other tables via zip columns. so far not finding success.
You have to rename all columns that have the same name, for example if all three tables have a name column:
SELECT
visitors_pb_list.name as visitors_pb_list_name,
pb_list.name as pb_list_name,
ziplatlang.name as ziplatlong_name
FROM visitors_pb_list
LEFT JOIN pb_list ON visitors_pb_list.landing_page = pb_list.landing_page
LEFT JOIN ziplatlang ON pb_list.zip = ziplatlang.zip_code
WHERE landing_page='" . $pb_list_id . "' order by id desc
In php, I suggest you access the resulting columns by name, which is the default for PDO
or with mysql functions:
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
//This is to keep an empty first or last line from forming, when the string is split
if ( $x == 0 )
{
$separator = "";
}
else
{
//Each row in the database is separated in the string by four *'s
$separator = "****";
}
//Saving to the String, each variable is separated by three &'s
$encodedString = $encodedString.$separator.
"<p class='content'><b>Lat:</b> ".$row['latitude'].
"<br><b>Long:</b> ".$row['longitude'].
"<br><b>Name: </b>".$row['name'];
$x = $x + 1;
}

Wordpress custom query on two tables to get hierarcal data

Working with Wordpress and using its functions to query the database on two custom tables. I am trying to output data as a "tree/grouped" format. Example:
Team Name One
- Player Name One
- Player Name Two
- Player Name Three
- Etc...
Team Name Two
- Player Name one
- Player Name Two
- Player Name Three
- Etc...
I am not sure of what would be needed to get the results to display this way. Here is one query I have that joins the tables...
global $wpdb;
$user_id = '1';
$teamlist = $wpdb->get_results( $wpdb->prepare(
"
SELECT
fv_teams.team_id AS team_teamid,
fv_players.player_id,
fv_teams.team_name AS teamname,
fv_players.player_fname AS fname,
fv_players.player_lname AS lname,
fv_players.player_team AS team,
fv_players.player_position AS position
FROM
fv_teams
INNER JOIN fv_players ON fv_teams.team_id = fv_players.team_id
WHERE
fv_teams.user_id = %d
",
$user_id
), OBJECT );
I can certainly get results doing the following...
foreach ( $teamlist as $teamrow )
{
echo 'Team: ' . $teamrow->teamname . '<br />';
echo 'Player ID: ' . $teamrow->fname . '<br />';
}
But the team name will be duplicated in the results as
Team Name One
- Player One
Team Name One
- Player Two
Team Name One
- Player Three
- Etc...
I am fairly certain I need to add a nested loop in there, but I am uncertain as to the coding required.
Any thoughts or direction would be very much helpful to a guy who just lost a good portion of his hair on this. ; )
Not sure on the etiquette for answering your own question but I was able to get what I needed based on nesting the second query within a loop.
$teamlist = $wpdb->get_results( $wpdb->prepare(
"SELECT *
FROM fv_teams
WHERE user_id = '1'
",
$user_id
), OBJECT );
foreach ( $teamlist as $teamrow )
{
$get_team_id = $teamrow->team_id;
echo '<hr>';
echo '<h2>' . $teamrow->team_name . '</h2>';
$playerlist = $wpdb->get_results( $wpdb->prepare(
"SELECT *
FROM fv_players
WHERE team_id = %d
",
$get_team_id
), OBJECT );
echo '<ul>';
foreach ( $playerlist as $playerrow )
{
echo '<li>Player Name: ' . $playerrow->player_fname . '</li>';
}
echo '</ul>';
}
It needs cleaning up but does work for me.
You need a Group by on the teamid.
Examples: http://www.techonthenet.com/sql/group_by.php
Edit you need to write two loops.
<?php
// in plain language
if(TeamName has another value)
display name;
if (playername has another value)
display playername;
end if
end if
?>

Can this PHP code be simplified to improve performance?

The goal of this code, is to get all brands for all stores into one array, and output this to the screen. If a brand exists in multiple stores, it will only be added once.
But I feel I have too many for loops, and that it might choke the CPU on heavy traffic.
Is there a better solution to this?
function getBrands($stores, $bl)
{
$html = "";
//Loop through all the stores and get the brands
foreach ($stores as $store)
{
//Get all associated brands for store
$result = $bl->getBrandsByStore($store['id']);
//Add all brands to array $brands[]
while ($row = mysql_fetch_array($result))
{
//If this is the first run, we do not need to check if it already exists in array
if(sizeof($brands) == 0)
{
$brands[] = array("id" => $row['id'], "name" => $row['name']);
}
else
{
// Check tosee if brand has already been added.
if(!isValueInArray($brands, $row['id']))
$brands[] = array("id" => $row['id'], "name" => $row['name']);
}
}
}
//Create the HTML output
foreach($brands as $brand)
{
$url = get_bloginfo('url').'/search?brandID='.$brand['id'].'&brand='.urlSanitize($brand['name']);
$html.= ''.$brand['name'].', ';
}
return $html;
}
//Check to see if an ID already exists in the array
function isValueInArray($values, $val2)
{
foreach($values as $val1)
{
if($val1['id'] == $val2)
return true;
}
return false;
}
From your comment, you mention "Guide table has X stores and each store has Y brands". Presumably there's a "stores" table, a "brands" table, and a "linkage" table, that pairs store_id to brand_id, in a one-store-to-many-brands relationship, right?
If so, a single SQL query could do your task:
SELECT b.`id`, b.`name`
FROM `stores` s
LEFT JOIN `linkage` l
ON l.`store`=s.`id`
LEFT JOIN `brands` b
ON b.`id`=l.`brand`
GROUP BY b.`id`;
That final GROUP BY clause will only show each brand once. If you remove it, you could add in the store ID and output the full list of store-to-brand associations.
No need to loop through two sets of arrays (one to build up the array of brands, and then one to make the HTML). Especially since your helper function does a loop through -- use the array_key_exists function and use the ID as a key. Plus you can use the implode function to join the links with ', ' so you don't have to do it manually (in your existing code you'd have a comma on the end you'd have to trim off). You can do this without two sets of for loops:
function getBrands($stores, $bl)
{
$brands = array();
//Loop through all the stores and get the brands
foreach ($stores as $store)
{
//Get all associated brands for store
$result = $bl->getBrandsByStore($store['id']);
//Add all brands to array $brands[]
while ($row = mysql_fetch_array($result))
{
if (!array_key_exists($row['id'])
{
$url = get_bloginfo('url') . '/searchbrandID=' .
$brand['id'] . '&brand=' . urlSanitize($brand['name']);
$brands[$row['id']] .= '<a href="' . $url . '" id="' .
$brand['id'] . '" target="_self">' .
$brand['name'] . '</a>';
}
}
}
return implode(', ', $html);
}
That will get you the same effect a little faster. It's going to be faster because you used to loop through to get the brands, and then loop through and build up the HTML. Don't need to do that as two separate loops so it all at once and just store the HTML as you go along. Plus since it's switched to use array_key_exists, instead of the helper you wrote that checks by looping through yet again to see if a brand is in there, you'll see more speed improvements. Hashmaps are nice like that because each element in the hashmap has a key and there are native functions to see if a key exists.
You could further optimize things by writing a better SQL statement with a distinct filter to make it so you don't have to do a while inside a foreach.
How are your tables designed? If you had a store table, a brand table, and a link table that had the relationship between stores and brands, you could just pull in the list of brands from the brand table in one query and not have to do any other logic.
Design your tables so they easily answer the questions you need to ask.
If you need to get all the brands for a certain set of stores then you should consider using a query crafted to do that instead of iterating through all the stores and getting the separate pieces of information.

Categories