products categories - php

I have a simple question but I don't know which term I should use to find the answer (english is not my first language).
I have a classical database design of products like and categories.
CREATE TABLE IF NOT EXISTS `a` (
`id_a` int(11) NOT NULL auto_increment,
`type` varchar(255) NOT NULL,
PRIMARY KEY (`id_a`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;
CREATE TABLE IF NOT EXISTS `b` (
`id_b` int(11) NOT NULL,
`id_a` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id_b`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Where b.id_a is a foreign key to a.id_a
I want to get a hierarchy of all thoses like
A VALUE 1
b_value_1
b_value_2
A VALUE 2
b_value_11
b_value_12
A VALUE 3
b_value_21
b_value_22
b_value_23
The request doesn't matters but I get this kind of anwser:
VALUEOF-TABLE-A | VALUEOF-TABLE-B
A VALUE 1 | b_value_1
A VALUE 1 | b_value_2
and so on.
My current code is something like:
$categ = '';
while ($row = mysql_fetch_row ($ressource))
{
if ($row['VALUEOF-TABLE-A']!=$categ)
{
$categ = $row['VALUEOF-TABLE-A'];
echo '<h3>', $categ, '</h3>';
}
echo '<h4>', $row['VALUEOF-TABLE-B'], '</h4>';
}
But I don't like much the idea of the categ variable, be it a string or an id.
Is there an other way to get the data and display them?
Ideally, I'de like a tree object, having only one node for identical children.
Hope you understood what I want.

When working with foreign keys in Mysql, you should use the InnoDB engine instead of MyISAM.
There seems to be a problem in the conception of the b table, id_b should be the primary key, not id_a.
To solve your problem, maybe you should first retrieve the list of id_a, then make one selection request by id_a to select the corresponding id_b using a JOIN.
EDIT : the script should look like this with a little more presentation :
$category_array = mysql_query("SELECT id_a, type FROM a");
while ($category = mysql_fetch_array($category_array))
{
echo $category['type'];
$product_array = mysql_query("SELECT * FROM b WHERE id_a = $id_a");
while ($product = mysql_fetch_array($product_array))
{
echo $product['name'];
}
}

The example Chico gives is on the right track, but has a problem: when there are many categories, the script will also execute many queries, which is inefficient. The following example is much more efficient:
$categories = array();
$category_array = mysql_query("SELECT id_a, type FROM a");
while ($category = mysql_fetch_array($category_array))
{
$category['products'] = array();
$categories[$category['id_a']][] = $category;
}
$product_array = mysql_query("SELECT * FROM b");
while ($product = mysql_fetch_array($product_array))
{
$categories[$product['id_a']]['products'][] = $product
}
foreach($categories as $category) {
echo $category['type'];
foreach ($category['products'] as $product) {
echo $product['name'];
}
}
As an added bonus, this also separates the retrieval of the data more cleanly from the output.

Related

How Do I Sort By The Newest Database Entry In PHP?

I my webpage has it so you can make a post and then people can reply to it so it has a postId that increments by one for identifying each new thread and a replyId that increments one to identify each reply. Right now on the navigator I have it so the highest (or newest) post is shown at the top and going down as they get older with
<?php
$thread1TitleR = "SELECT * FROM newThread ORDER BY postId DESC";
$thread1Title = mysqli_query($con, $thread1TitleR);
if ($thread1Title) {
while ($row = $thread1Title->fetch_assoc()) {
echo "<a href='" . $row['postId'] . ".php'>" . $row['thread'] . "</a>" . "<br/><br/>";
}
}
?>
But I want to make it so the thread with the newest user input is at the top going down. So for example I want it so a brand new thread that has just been created is at the top or if an old thread with no replies it goes to the top or if its a old thread with a lot of replies if someone replies to it it will go back up to the top. Basically I want it to go by latest user input. If this does not make sense or if you have any questions please ask in the comments I have been trying to figure this out for awhile.
When I run show create table newThread; it returns Array ( [Table] => newThread [Create Table] => CREATE TABLE `newThread` ( `id` int(11) NOT NULL AUTO_INCREMENT, `thread` text NOT NULL, `threadText` text NOT NULL, `name` text NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8mb4 )
some of that is not related and when simplifying the code to where it didn't matter got rid of it
When I run show create table reply it returns ( [Table] => reply [Create Table] => CREATE TABLE `reply` ( `replyId` int(11) NOT NULL AUTO_INCREMENT, `threadId` int(11) NOT NULL, `reply` text NOT NULL, PRIMARY KEY (`replyId`) ) ENGINE=InnoDB AUTO_INCREMENT=80 DEFAULT CHARSET=utf8mb4 ) I hope this helps.
It might be better to first get the database sorted. Currently you are not using foreign keys and therefor the database does not know that threadId in reply corresponds to the id of newThread, meaning you can get invalid data and more.
As to sorting out depending on the newest reply,
SELECT
newThread.id,
newThread.thread,
newThread.threadText,
newThread.name
FROM newThread
LEFT JOIN reply on newThread.id = reply.threadId
JOIN (SELECT reply.threadId,
MAX(reply.replyId) replyId
FROM reply
GROUP BY reply.threadId) t1
ON reply.threadId = t1.threadId AND reply.replyId = t1.replyId
Order By reply.replyId DESC
And so inside php
<?php
$thread1TitleR = "SELECT
newThread.id,
newThread.thread,
newThread.threadText,
newThread.name
FROM newThread
LEFT JOIN reply on newThread.id = reply.threadId
JOIN (SELECT reply.threadId,
MAX(reply.replyId) replyId
FROM reply
GROUP BY reply.threadId) t1
ON reply.threadId = t1.threadId AND reply.replyId = t1.replyId
Order By reply.replyId DESC";
$thread1Title = mysqli_query($con, $thread1TitleR);
if ($thread1Title) {
while ($row = $thread1Title->fetch_assoc()) {
echo "<a href='" . $row['postId'] . ".php'>" . $row['thread'] . "</a>" . "<br/><br/>";
}
}
?>
This should get you a list of all the topics with the newest/highest reply id to them. Might not be the most efficient method, but it works.
Edit, quickly inversed the order so the newest is on top.

Separate hashtags and text PHP

Hi this is an example of my table, I want to be able to search like this: #mat#godis and be able to get the 2 results having those tags (ID 5 and ID 7)
This i my PHP code so far and it works while searching for example (Globen), (Globen #mat) and (#mat)
//Strip Search
function selectSearch($search){
$lol = preg_replace('/(^|\s)#(\w*[a-zA-Z_]+\w*)/'," ",$search);
$keywords = explode(" ", preg_replace("/\s+/", " ", $lol));
foreach($keywords as $keyword){
$wherelike[] = "name LIKE '%$keyword%'";
}
$search = implode(" and ", $wherelike);
return $search;
}
//Strip tags
function selectTags($tags){
$str = $tags;
$bits = explode(' ', $str);
$newquery = array();
foreach($bits as $bit){
if(strlen($bit) > 0 && $bit[0] === '#') $newquery[] = $bit;
}
$newquery = implode('', $newquery);
$keywords = explode(" ", preg_replace("/\s+/", " ", $newquery));
foreach($keywords as $keyword){
$wherelike[] = "tags LIKE '%".ltrim($keyword,'#')."%'";
}
$tags = implode(" or ", $wherelike);
return $tags;
}
It returns (searching for #mat#godis)
select * from stores where name LIKE '%%' and name LIKE '%#godis%' and tags LIKE '%mat#godis%'order by id desc limit 8
I want the query to be something like this
select * from stores where name LIKE '%%'and tags LIKE '%mat%' and tags LIKE '%godis%' order by id desc limit 8
This design is really bad. You need to use a relation table. The problem is, if you try to search %#mat%, then, if there is a tag like #matheus, that will also give you result.
Of course, there are workaround for this situation, but that is too complicated.
And i do not understand what is this: name LIKE '%%'
So what you need is to create table, as ChrisForrence mentioned, called store_tag like this:
CREATE TABLE IF NOT EXISTS `store_tag` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag` varchar(255) NOT NULL COMMENT 'Tag like #mat',
`user_id` int(11) NOT NULL COMMENT 'The foreign ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
After this, you need to write a little script, what is loop thorugh on all the records in your original table, get out the id and the tags, explodes the tags, insert it individually in your new table store_tag.
And then, use:
SELECT * FROM store_tag, orig_table INNER JOIN origTable ON origTable.id = store_tag.user_id WHERE tag = '#mat' OR tag = '#godis'
(The query above is not tested, this is just for example, and of course you do not need to SELECT *).
This will give you back the desired results.
It's much more easier to maintain your data.
Of course befor this creat a backup from your existing database.
As I mentioned in my comment, I would create a separate table for tags, like such:
# The original table. Notice the lack of a 'tags' column
CREATE TABLE `stores` (
`id` INTEGER UNSIGNED AUTO_INCREMENT,
`name` VARCHAR(64) UNIQUE,
PRIMARY KEY(`id`)
);
# A new table, relating stores to tags
CREATE TABLE `store_tags` (
`store_id` INTEGER UNSIGNED,
`label` VARCHAR(64),
PRIMARY KEY(`store_id`, `label`),
FOREIGN KEY(`store_id`) REFERENCES `stores`(`id`) ON DELETE CASCADE
);
From here, you can use a variation of the following query to check for stores matching all of the tags:
SELECT * FROM `stores`
WHERE `id` IN (SELECT `store_id` FROM `store_tags` WHERE `label`='mat')
AND `id` IN (SELECT `store_id` FROM `store_tags` WHERE `label`='godis')
ORDER BY `id` DESC LIMIT 8
You can use this to split the tags out into the proper WHERE clause, along with an array of tags used as input parameters for your PDO query
/* Returns an array containing the WHERE clauses, along
* with the tags themselves (used to populate the input parameters for PDO)
*/
function searchTags($in) {
$rQ = '';
// Filter out empty array input elements
$tags = array_filter(explode('#', $in));
if(!count($tags)) {
return false;
}
$first = true;
foreach($tags as $tag) {
$rQ .= ($first ? ' WHERE' : ' AND') . ' `id` IN (SELECT `store_id` FROM `store_tags` WHERE `label`=?)';
if($first) $first = false;
}
return array($rQ, $tags);
}

Echoing Sorted Multidimensional Array

Ok, so I am creating a web app with php and mysqli.
I have a table friends which is a simple set up:
f_id int(11)
uid int(11)
fids TEXT
now its basically like a row for each user with the fids consisting of a lot of numerical values (other userids) separated by commas like: 1,2,3
so I use this function to get each user's friends:
function getFriends($db, $userid)
{
$q = $db->query("SELECT fids FROM friends WHERE uid='$userid'");
$ar = $q->fetch_assoc();
$friends = $ar['fids'];
$fr = explode(",", $friends);
return $fr;
}
but each posts comments that appear to each of their friends. my problem comes from trying to sort these comments by the time they were posted.
lets say my comments table is:
c_id int(11)
uid int(11)
c_text TEXT
c_time int(11)
I want to be able to get the comments posted by each 'friend' put them all into an array together, then sort them from their c_time value, then all the values from that particular row in the comments table.
The problem comes from my how I've set up my friends table.
I'm using:
$fr = getFriends($db, $userid);
$updates = array();
$i = 0;
foreach( $fr as $friend)
{
// Get Updates from friends and from self
$q = $db->query("SELECT up.*, u.* FROM updates up
LEFT JOIN users u ON u.id = '$friend'
WHERE (up.userid = '$userid') ORDER BY up.up_id DESC");
while($ar = $q->fetch_array(MYSQLI_BOTH))
{
$updates[$i] = $ar;
$i++;
}
}
$sortArray = array();
foreach($updates as $update){
foreach($update as $key=>$value){
if(!isset($sortArray[$key])){
$sortArray[$key] = array();
}
$sortArray[$key][] = $value;
}
}
$orderby = "up_id";
array_multisort($sortArray[$orderby],SORT_DESC,$updates);
$updates_limit = array_slice($updates, 0, 20);
to get the comments from each friend, sorting it by time, then slicing it to the first 20.
However when I var_dump($updates_limit) it takes the last row in the comments table, and then makes it look like each friend posted the same comment.
Can anyone see the problem or a better way of addressing this issue?
I'd completely refactor the friends table to look something more like this: (Also, use english - Characters are cheap :c))
CREATE TABLE friends (
user_id int FOREIGN KEY REFERENCES user(id)
, friend_id int FOREIGN KEY REFERENCES user(id)
, PRIMARY KEY (user_id, friend_id)
);
Then you can take essentially the same comment table:
CREATE TABLE comment (
comment_id int PRIMARY KEY
, user_id int FOREIGN KEY REFERENCES user(id)
, comment_text text
, comment_time datetime
);
And your "query for friend's comments" becomes:
SELECT comment_id, comment.user_id, comment_text, comment_time
FROM friends
INNER JOIN comment
ON comment.user_id = friends.friend_id
WHERE friends.user_id = ? #Target it
ORDER BY comment_time DESC
LIMIT 0, 20;
You can even speed this up by adding a few indexes - like comment(user_id).

PHP Recursive Find siblings

working with this for a while.. cant get my head right.. sooo... help here ;-)
It is quite simple I am sure..
Table:
CREATE TABLE IF NOT EXISTS `items` (
`item_id` bigint(20) NOT NULL AUTO_INCREMENT,
`item_parent_id` bigint(20) NOT NULL COMMENT 'itemid which this item belongs to',
`item_name` varchar(255) NOT NULL,
`item_serialnumber` varchar(255) NOT NULL,
`item_model` varchar(255) NOT NULL,
PRIMARY KEY (`item_id`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
I am trying to create an array of item_id and the item_id that it belongs to - via the item_parent_id - recursivly -
so that even if you find a child to a parent, check if the child is a parent to others.
Tried with something like this:
function get_item($item_id, $menu)
{
$sql = "
SELECT
items.*,
customers.*
FROM
assets
LEFT JOIN item_customer_rel USING(item_id)
LEFT JOIN customers USING(customer_id)
WHERE
items.item_parent_id = '".$parent."'
ORDER BY
items.item_name
";
$res = mysqli_query($db, $sql) or die("ERROR: SQL Select a2a ancestor", $sql, mysqli_error($db) , $_SESSION["u_id"]);
while ($items = mysqli_fetch_assoc($res))
$menu = build_ancestor_array($parent, $menu);
}
function build_ancestor_array($parent, $menu)
{
GLOBAL $db;
$sql = "
SELECT
items.*,
customers.*
FROM
items
LEFT JOIN item_customer_rel USING(item_id)
LEFT JOIN customers USING(customer_id)
WHERE
items.item_parent_id = '".$parent."'
";
$res = mysqli_query($db, $sql) or cc("ERROR: SQL Select a2a ancestor", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
while ($items = mysqli_fetch_assoc($res))
{
if ($ancestor_item_array[$parent] == $items["item_id"])
$menu = build_ancestor_array($parent, $menu);
$ancestor_item_array[$parent] = $items["item_id"];
// Creates entry into items array with current menu item id ie. $menu['items'][1]
$menu['items'][$items['item_id']] = $items;
$menu['items'][$items['item_id']]["connection_type"] = 2;
// Creates entry into connected_to array. connected_to array contains a list of all items with connected_to
$menu['connected_to'][$items['item_parent_id']][] = $items['item_id'];
}
return $menu;
} // end build item array
It only goes one "level" down.
Refer, the 2 links below, I had recently posted answers for these, this is done in pure SQL
Recursive MySQL Query with relational innoDB
and
How to find all child rows in MySQL?
Recursive worked.. Just needed to try manually with pen and paper ;-)
function get_item_data($parent, $menu, $ancestor_item_array = "")
{
GLOBAL $db;
$sql = "
SELECT
items.*,
customers.*
FROM
items
LEFT JOIN item_customer_rel USING(item_id)
LEFT JOIN customers USING(customer_id)
WHERE
items.item_parent_id = '".$parent."'
ORDER BY
items.item_name
";
$res = mysqli_query($db, $sql) or cc("ERROR: SQL Select a2a ancestor", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
while ($items = mysqli_fetch_assoc($res))
{
$ancestor_item_array[] = $items["item_id"];
if (!in_array($items["item_parent_id"], $ancestor_item_array))
$menu = get_item_data($items["item_id"], $menu, $ancestor_item_array);
// Creates entry into items array with current menu item id ie. $menu['items'][1]
$menu['items'][$items['item_id']] = $items;
$menu['items'][$items['item_id']]["connection_type"] = 2;
// Creates entry into connected_to array. connected_to array contains a list of all items with connected_to
$menu['connected_to'][$items['item_parent_id']][] = $items['item_id'];
}
}
It wont work on pure SQL.
You should take a look at stored procedures, the sql you're trying to make will only go 'inwards' one level because all the relations will be shown as if they're first level connections.
for example.
parent->son->grandson->ggson
parent.item_parent_id = null
son.item_parent_id = parent
grandson.item_parent_id = son
ggson.item_parent_id = grandson
even tough grandson is a lower level connection, he will show up as a first level connection.
it cant be done with pure sql, sadly..
that's one of the reasons that made me go to NOSQL databases.

Is there a better way to group query results with a loop in PHP?

that´s my first question in stackoverflow.
I have two MYSQL tables: categories and products. I manage the query results with a while loop in PHP to group every category with its products. It´s the first time I do something so, and I think I made it very "crafty/hardcoded" (Sorry for my English). I think that should be a better way to do this. So, I´d like to ask to professional programmers. Here is my code:
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) NOT NULL auto_increment,
`name` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ;
CREATE TABLE IF NOT EXISTS `products` (
`id` int(11) NOT NULL auto_increment,
`name` text NOT NULL,
`description` text NOT NULL,
`category` int(11) NOT NULL,
`photo` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=33 ;
I have a query that returns products relationed with their category:
SELECT categories.name as category,products.name as product
FROM categories
INNER JOIN products ON(categories.id = products.category)
With PHP I manage the results to create an unordered list for every category with its products:
<?php
$category = '';//A control variable
while($row = mysql_fetch_array($query))
{
//if its a new category I start a new <ul>
if($row['category'] != $category)
{
//If it´s not the firt category I need to close previous one
if($category != '')
{
echo "</ul>\n";
}
//I start the new <ul>
echo '<h2>' . $row['category'] . '</h2>';
echo '<ul>';
}
//I create the correspondient <li>
echo '<li>' . $row['product'] . "</li>\n";
//Asign the value of actual category for the next time in the loop
$category = $row['category'];
}
//I neeed to close last <ul>
echo "</ul>\n";
?>
Is there a better way to do this operation?
Thanks in advance for your answers!
It's always best not to mix HTML with PHP.
Since you wanted my advice (veteran PHP coder of some 12 years), I'll just code it from scratch real fast:
<?php
$pdo = new PDO($dsn, $user, $pass);
$sql = "SELECT categories.name as category,products.name as product
FROM categories
INNER JOIN products ON(categories.id = products.category)";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$categories = array();
while (($row = $stmt->fetch(PDO::FETCH_ASSOC)))
{
$category = $row['category'];
$categories[$category][] = $row['product'];
}
// In your view:
?>
<html>
<body>
<?php
foreach ($categories as $category => $products)
{
?>
<h2><?php echo $category; ?></h2>
<ul class="products">
<?php
foreach ($products as $product)
{
?>
<li><?php echo $product; ?></li>
<?php
}
?>
</ul>
<?php
}
?>
</body>
</html>
I would suggest reading into Hierarchical Data. A great read. You should particularly pay attention to the Nested Set Model.
It does take a bit to learn and also takes a bit to implement, but is well worth it for the end result!

Categories