Foreach key not incrementing - php

I'm creating a class book list - there's a master list of books, and a list of the books that the class can be assigned. I need to create an array of the master list, remove the books that have already been assigned to the class, then create a select list of the remaining books. I have most of it working, but I'm stuck on a small part.
First, I load the array (the book_id is the key, the book_title is the value):
while ($master_book = mysql_fetch_array($big_book_list)) {
$b_id = $master_book['master_book_list_id'];
$book_list[$b_id] = $master_book['book_title'];
}
then I remove the class's primary book (it's already been set):
unset($book_list[$primary_book]);
now I load an array of the secondary books that have already been assigned (primary and secondary books come from the same list):
$secondary_query = "select * from class_books cb, master_book_list mbl where class_id ='$class_id' and cb.book_id = mbl.master_book_list_id;";
$secondary_list = mysql_query($secondary_query);
Now I remove the books from $secondary_list from the main list:
while ($secondary_book = mysql_fetch_array($secondary_list)) {
$b_id = $secondary_book['book_id'];
unset($book_list[$b_id]);
}
at this point, print_r shows my array correctly. but this code:
foreach ($book_list as $book2) {
$the_key = key($book_list);
echo $the_key . ' and book2 is '. $book2 . '<br/>';
}
it will iterate the predicted number of times, but the key stays at the same value (which is '2', the first value in the array.
Can anyone see what I'm doing wrong?

You can grab the key from the foreach loop:
foreach ($book_list as $the_key => $book2) {
echo $the_key . ' and book2 is '. $book2 . '<br/>';
}

Related

How to display all posts under main category and its subcategories?

I have a custom php/mysql blog. In single post page, I have a sidebar where I am displaying categories. I want to display number of posts those belong to a main category and its subcategories in brackets as we usually see in WordPress. For now I have
Music
Pop
Nature
Lakes
Sea
I want to achieve
Music (5)
Pop (3)
Nature (8)
Lakes (2)
Sea (4)
As you see Music category has 5 posts under it and Pop has 3. But it doesn't mean there are 8 posts in reality. There are actually only 5 posts because Pop is subcategory of main category Music. 2 posts are directly under the main category Music while 3 posts are falling in its subcategory Pop. Thus 2 + 3 = 5.
The categories table looks as (Note that it has nth level of subcategories):
The posts table looks as (Note that a particular post can have its only one category parent i.e. a post can not have its multiple parents)
My code to fetch categories and their subcategories is
public function get_category_hierarchy(&$output, $parent_url, $parent = 0)
{
$sql = "SELECT * FROM `tb_categories` WHERE `category_parent` = " . $parent . " ORDER BY `category_name`";
$rows = $this->get_by_sql($sql);
// Removing trailing slash
$parent_url = rtrim($parent_url, "/");
if(is_array($rows))
{
foreach($rows as $row) :
$url = $parent_url . "/" . $row->category_url;
$output.= "<li><a href='" . $url . "'>" . $row->category_name . "</a></li>";
if($row->category_id != $parent)
{
$this->get_category_hierarchy($output, $url, $row->category_id);
}
endforeach;
}
return $output;
}
On sidebar, I am displaying these categories by following code:
$output = '';
// Create post object
$post_obj = new Post();
echo $post_obj->get_category_hierarchy($output, SITE_URL . 'category');
Now I'm wondering what code need to be add for fetching number of posts (post counts) that fall under a main category and subsequently its sub-sub-categories? Though I tried to fetch them but the result didn't come accurate.
Thank you in advance.
EDIT:
I modified function get_category_hierarchy() by doing following amendment:
foreach($rows as $row) :
$url = $parent_url . "/" . $row->category_url;
// Build database query
$sql = "SELECT COUNT(*) AS 'total_posts' FROM `tb_posts` WHERE `post_parent` = " . $row->category_id . " GROUP BY `post_parent`";
// Execute database query
$rows = $this->get_by_sql($sql);
$output[] = "<li><a href='" . $url . "'>" . $row->category_name . " (" . $rows[0]->total_posts . ")</a></li>";
But oops! It is giving me following result:
Music (2)
Pop (3)
Nature (2)
Lakes (2)
Sea (4)
Obviously, the parent category is having total number of post which are directly associated to it and not to its children. How to fix it?
The approach that I could think of :
Do a group by on the posts table on the post_parent.
From that form an associative array with the index as category_id and pair value as post_count.
Ex. $category_post_counts = array("4" => 5, "5" =>
10)
Now run a foreach loop for all the category ids and fetch its
corresponding parent.
Update the post count by using the indexes.
Suppose for category id 4(pop) there were 5 posts and in music there
were 10 posts. Now in the loop if the category parent is anything
else apart from 0 then update the post count in the array.
Ex.
$category_post_counts["<category_parent_id>"] =
$category_post_counts["<category_parent_id>"] +
$category_post_counts["<category_child_id>"]
This way your end result
will be an associative array with the category ids and all the post
counts.
Pseudo Code
//exctracting all the data
$rows = execute_this_query("SELECT COUNT(post_id) AS `post_count`, `post_parent` FROM `posts` GROUP BY `post_parent`");
//forming the associative array
foreach ($rows as $value) {
$associative_array[value["post_parent"]] = $value["post_count"];
}
$data = execute_this_query("SELECT `category_id`,`category_parent` FROM `categories` WHERE `category_parent` <> 0");
foreach ($data as $value) {
$associative_array[$value["category_parent"]] += $associative_array[$value["category_id"]];
}
//Your final associative array has the exact category_id and net post count as you want.
First you can populate an associative array for the quantity in each category like: category_id => post_qty.
After that, you can use the function bellow to calculate for every category and its children:
function count_posts($category_id, $cache) {
$sum = 0;
$sql = "SELECT * FROM `tb_categories` WHERE `category_parent` = " . $category_id;
$rows = $this->get_by_sql($sql);
foreach ($rows as $child) {
$sum += count_posts($child->category_id, $cache);
}
$sum += $cache[$category_id];
return $sum;
}

How to store a value in an array only if unique

I'm writing a PHP report which is designed to be exported purely as a CSV file, using commma delimiters.
There are three columns relating to product_id, these three columns are as follows:
SKU Parent / Child Parent SKU
12345 parent 12345
12345_1 child 12345
12345_2 child 12345
12345_3 child 12345
12345_4 child 12345
18099 parent 18099
18099_1 child 18099
At the moment the code looks like this:
<?php
$con_size = array (35,355,36,37,375,38,385,39,395,40,405,41,415,42,425,43,435,44,445,45,455,46,465,47,475,48,485);
$arrlength=count($con_size);
for($x=0;$x<$arrlength;$x++) {
// check if size is available
if($line['quantity_c_size_'.$con_size[$x].'_chain'] > 0 ) {
?>
<? echo 'Shoes'; ?>,
<?=$line['product_id']?>,
,
,
So at the moment this is simply echoing out the product_ID into the SKU column. How would I create an array which tests whether the 'product_id' is unique and if not suffixes the ID with _1, _2, etc.
Thanks,
Test whether it's in the array, and add it if it's not.
if (!in_array($line['product_id'], $SKU)) {
$SKU[] = $line['product_id'];
echo $line['product_id'];
} else {
echo $line['product_id'] . "_1";
}
If you need the suffixes to increment each time, you can do:
$SKU_counters = array();
if (isset($SKU_counters[$line['product_id']])) {
echo $line['product_id'] . "_" . $SKU_counters[$line['product_id']]++;
} else {
echo $line['product_id'];
$SKU_counters[$line['product_id']] = 1;
}
At the end of the loop you do:
$SKU = array_keys($SKU_counters);
Possibly putting most of the work onto the database (not tested):-
<?php
$con_size = array (35,355,36,37,375,38,385,39,395,40,405,41,415,42,425,43,435,44,445,45,455,46,465,47,475,48,485);
$con_check = 'AND (quantity_c_size_'.implode('_chain > 0 OR quantity_c_size_',$con_size).'_chain > 0)';
$sql = "SELECT DISTINCT product_id, #cnt AS out_counter, #cnt:=IF(#prev_product_id=product_id, #cnt + 1, 0), #prev_product_id:=product_id
FROM product
CROSS JOIN (SELECT #prev_product_id:=0, #cnt:=0) sub1
WHERE on_amazon = 'on'
AND active = 'on'
$con_check
ORDER BY product_id";
$result = mysql_query($sql) or die ( mysql_error() );
while ($line = mysql_fetch_assoc($result) )
{
if ($line['out_counter'] == 1)
{
$SKU[] = $line['product_id'];
}
else
{
echo $line['product_id'].'_'.$line['out_counter']." \r\n";
}
}
?>
Takes your list of $con size and builds up a load of WHERE clauses to check each value is greater than 0. This should save you bringing back values which you don't care about.
The SELECT then brings back items in product_id order, but adds a sequence number (which resets to 0 when the product_id is different from the one on the previous row).
Then when looping around the results it saves the value of product_id if the sequence is 1 (ie the first occurrence of that product_id), else it just outputs the product id with the sequence number concatenated onto the end.

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
?>

How do I extract and display hierarchical data from my database?

I have two tables.
The chapters table has the columns id and name.
The chapters_chapter table has columns id, master_id, and slave_id.
Lets say that the chapters table has 7 records:
id name
1 test01
2 test02
3 test03
4 test04
5 test05
6 test06
7 test07
And in the chapters_chapters table I have these records:
id master_id slave_id
1 1 5
2 1 6
3 6 7
4 7 2
Given that data, how can I extract the hierarchy of that data so that it looks like this?
test01
test05
test06
test07
test02
test03
test04
So this was kind of a pain because of the fact that we had to have the hierarchy stored in the DB. Because of this, each item can have multiple children, and each child can have multiple parents.
This second part means we cannot simply loop through the list once and be done with it. We might have to insert an item in multiple places in the hierarchy. While you probably won't actually structure your data that way, the database schema you've described supports this scenario, so the code must support it too.
Here's a high-level version of the algorithm:
Query both tables
Create a map (array) of a parent (number) to its children (another array)
Create a set of items that are children (array of numbers)
Create a function that displays a single item, indenting it to the desired depth.
If that item has children, this function increases the depth by one, and calls itself recursively
Loop through all items that aren't children (root items).
Call the function for each of those items, with a desired depth of 0 (no indent).
Here's two hours work. Enjoy :)
Note that I stuck it within a <pre> block, so you might have to mess with how the indentation is done (output something other than two spaces, mess with the style of the divs, etc).
<?php
$con = mysql_connect("localhost", "test_user", "your_password");
if(!$con)
{
die("could not connect to DB: " . mysql_error());
}
mysql_select_db("your_db", $con);
// get chapters
$chapters = array();
$result = mysql_query("SELECT * FROM chapters");
while($row = mysql_fetch_array($result))
{
$id = $row["id"];
$name = $row["name"];
$chapters[$id] = $name;
}
// get chapters_chapters - We'll call it "parent/child" instead of "master/slave"
$parent_child_map = array();
$is_child = array();
$result = mysql_query("SELECT master_id, slave_id FROM chapters_chapters");
while($row = mysql_fetch_array($result))
{
$parent_id = $row["master_id"];
$child_id = $row["slave_id"];
$children = $parent_child_map[$parent_id];
if($children == null)
{
$children = array();
}
$children[] = $child_id;
$parent_child_map[$parent_id] = $children;
$is_child[$child_id] = true;
}
// display item hierarchically
$display_item_and_children = function($id, $name, $depth)
use ($chapters, $parent_child_map, &$display_item_and_children)
{
echo "<div><pre>";
// indent up to depth
for($i = 0; $i < $depth; $i++)
{
echo " ";
}
echo "id: " . $id
. " name: " . $name
. "</pre></div>";
// if there are children, display them recursively
$children = $parent_child_map[$id];
if($children != null)
{
foreach($children as $child_id)
{
$child_name = $chapters[$child_id];
$display_item_and_children($child_id, $child_name, $depth + 1);
}
}
};
// display all top-level items hierarchically
foreach($chapters as $id => $name)
{
// if it is a top-level item, display it
if($is_child[$id] != true)
{
$display_item_and_children($id, $name, 0);
}
}
mysql_close($con);
?>
And here's a screenshot:
The question becomes how complex you want your solution to be. I'd do it with the following pseudo code.
SELECT all the chapters
SELECT all the *chapters_chapters*
loop over the chapters to create an array chapter objects
loop over the `chapters_chapters* and create the relationships using the chapter objects
Essentially you're creating a link-list.

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