It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
As per my system i need to search brand based on category and subcategory.
Brand table like this
id brandname categoryid subcategoryid
1 xys 1,2,3 1,5,6
Now when i search i select category then as per category all subcategory come now i select subcategory now need to show all the brand based on that category and subcategory.
My brand table look like this because same brand have multiple category and subcategory.Please help me to solve this problem
Given your database design, you can do that like this:
SELECT * FROM mytable WHERE
FIND_IN_SET('5', categoryid) > 0 AND FIND_IN_SET('3', subcategoryid) > 0;
This will find all items from category 5 and subcategory 3.
SELECT * FROM mytable WHERE
FIND_IN_SET('5', categoryid) > 0 AND (
FIND_IN_SET('3', subcategoryid) > 0
OR
FIND_IN_SET('9', subcategoryid) > 0
);
The above will find items in category 5, subcategories 3 and 9. Of course you can also restrict to items that are in both categories, by using AND instead of OR.
But all this is needlessly expensive. You would do better by having a table for brand names, and other tables for category and subcategory IDs, and links, like this:
// This is an article. Many-to-one relation with brands.
CREATE TABLE articles
(
id integer primary key not null auto_increment,
name varchar(...),
brand_id integer,
//, other data
);
CREATE TABLE brands
(
id integer primary key not null auto_increment,
name varchar(...)
//, other data
);
// Categories. Many-to-Many relationship with articles.
CREATE TABLE categories
(
id integer primary key not null auto_increment,
name varchar(...)
//, other data
);
// Subcategories. These are independent from categories, which
// may be right or wrong, depending. Being independent, we do not
// store here parent_category_id.
CREATE TABLE subcategories
(
id integer primary key not null auto_increment,
name varchar(...)
//, other data
);
// Many to many relationship between articles and categories
CREATE TABLE mtm_article_in_category
(
article_id integer not null,
category_id integer not null
);
CREATE TABLE mtm_article_in_subcategory
(
article_id integer not null,
subcategory_id integer not null
);
// Add article 5 to categories 25, 37 and 119:
INSERT IGNORE INTO mtm_article_in_category VALUES ( 5, 25 ), ( 5, 37 ), ( 5, 119 );
// Remove article 18 from subcategory 92
DELETE FROM mtm_article_in_category WHERE article_id = 18 AND subcategory_id = 92;
This way you can run much faster queries, and not have problems such as the inability to assign an article to more than "so many" categories (e.g. 50); nor the headaches if you wanted to move an article from a category to another, that with your current design would be next to impossible.
My search like this at first i chose category the all the subcategory
come based on category.Then when i chose subcategory all the brand
based on that category and subcategory come.Now i add one brand name
one time with multiple category and multiple subcategory.
I have to say, "Oh my God". To be able to "select all the subcategories" /now/, you would have to transform this
category subcategory
4,5 1,7,9,19
5 7,9,11
in
5 1
5 7
5 9
5 19
5 7
5 9
5 11
then run a DISTINCT, and finally use the subcategories as an INNER JOIN based on FIND_IN_SET.
The first step ("explode" a CSV row) can be done like this: http://www.marcogoncalves.com/2011/03/mysql-split-column-string-into-rows/ ... and as you can see it is all but trivial.
I expect that currently you are doing the splitting in PHP.
After doing that, the INNER JOIN is awfully expensive.
We are throwing good money after bad. Your current database design does not allow to do what you want, easily. The simplest way would be:
// My search like this at first i chose category the all the subcategory
// come based on category.
$query = "SELECT subcategoryid FROM mytable WHERE FIND_IN_SET(:mycategory, categoryid) > 0;";
// and run the query.
$subcategories = array();
while($tuple = sql_fetch_tuple($exec))
{
// Explode "1,2,3" into array {1, 2, 3}. Merge into subcategories removing
// duplicates. Rinse. Repeat.
$subcategories = array_unique(array_merge($subcategories, explode(',', $tuple['subcategoryid'])));
}
sql_free($exec);
// Now we have an array of subcategories.
// Then when i chose subcategory all the brand
// based on that category and subcategory come.
$subcat_query = array();
foreach($subcategories as $subcategory)
$subcat_query[] = "FIND_IN_SET('$subcategory', subcategoryid)";
$subcat_query_sql = implode(' OR ', $subcat_query);
$query = "SELECT DISTINCT brand FROM mytable WHERE FIND_IN_SET(:cat, categoryid) AND ( $subcat_query_sql );";
// And here we get all brands. It is wise to save $subcat_query_sql in _SESSION.
// Next search will be:
// >Now i add one brand name
// > one time with multiple category and multiple subcategory.
// Note that you've subtly moved the target once more, now the 'category' has become "multiple".
$brands_arr[] = array();
foreach($brands as $brand)
$brands_arr[] = "'" . sql_escape($brand) . "'";
$brands_sql = implode(',', $brands_arr);
// The cost of this $query is estimated as a significant percentage of U.S. gross internal product, so it ought to be cleared with the FED.
$query = "SELECT * FROM mytable WHERE brand IN ($brands_sql) AND FIND_IN_SET(:cat, categoryid) AND ( $subcat_query_sql );";
It is also possible that the above query will return nothing at all. Suppose that you looked for subcategory 5 and category 12. By your request, getting "all subcategories" and "all categories" might return also brand 6 and subcategory 9. Then these two rows come out,
Marlboro 5 12
Lucky 6 9
and the user selects "Marlboro 6 12". He won't get anything - no rows will match that query.
I am afraid that the user interface and workflow/use case needs looking into, too.
Related
I have a table categories which contains the columns ID & ParentID I would like to add a field called Level, which states which level in the category tree each category is.
I think i found my solution but it is in sql not mysql. So i have been converting it to the correct syntax. However, i think im missing a step. So, here is my code:
ALTER TABLE categories DROP Level;
ALTER TABLE categories ADD Level INT NULL;
UPDATE categories
SET Level = 0
WHERE ParentID IS NULL;
UPDATE categories AS A
INNER JOIN categories B ON A.ParentID = B.ID
SET A.Level = B.Level + 1
WHERE A.Level IS NULL AND
B.Level IS NOT NULL;
I think the problem may lie in the fact that in my DB The order of the categories do not come in any specific order, what i mean is as follows:
ID ParentID
2 NULL 0
4 55
7 2
.....more categories
55 2
So what i would like it do do is:
ID Parent Level
2 NULL 0
3 55 2
7 2 1
....
55 2 1
However, i think, but i might be wrong, is that i need to either order by ParentID first before i do the last operation, or my query is missformed.
I am not getting any errors however, but just not getting the results i am expecting this is what im getting;
ID Parent Level
2 NULL 0
3 55 NULL
7 2 1
....
55 2 1
Any ideas?
The question is how many levels do u have?
If they are 3 levels than u can do it like this
ALTER TABLE categories DROP Level;
ALTER TABLE categories ADD Level INT NULL;
UPDATE categories SET Level = 0 WHERE ParentID IS NULL;
UPDATE categories SET level = 1 where parentID = 2;
UPDATE categories SET level = 2 where parentID > 2;
I have a table that looks like this
id | itemID | catID | Title
----------------------------------------------------------------------------
0 3 4 Hello
1 3 6 Hello
2 4 4 Yo
3 4 8 Yo
4 5 2 Hi
5 1 3 What
I want to do a MySQL PHP Select that only gets one occurrence of the itemID. As you can see they are the same item, just in different categories.
This is what I tried
SELECT * FROM Table GROUP BY itemID
That didn't seem to work, it still just shows duplicates.
Is this what you are looking for? http://www.sqlfiddle.com/#!2/5ba87/1
select itemID, Title from test group by itemID;
As far as MySQL is concerned, the data is all unique, since you want all of the columns. You have to be more specific.
Do you just want the itemID (or other column)? Then say so:
select [column] from Table GROUP BY itemID
Do you want the last entry of a particular item ID? Then say that:
select * from Table where itemID = 1 ORDER BY id DESC
Or the first one?
select * from Table where itemID = 1 ORDER BY id
If none of these are what you want, then you probably need to restructure your tables. It looks like you want different categories for your items. If so, then you'll want to split them out into a new join table, because you have a many-to-many relationship between Items and Categories. I recommend reading up on database normalization, so you're not duplicating data (such as you are with the titles).
If you want everything for the distinct itemIDs, you could certainly take a long route by doing one selection of all of the distinct itemIDs, then doing a series of selections based on the first query's results.
select distinct(`itemID`) from Table
Then in your PHP code, do something like this:
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
$itemID = $row['itemID'];
$sql2 ="SELECT * FROM Table WHERE 1 and `itemID`=\"$itemID\" limit 1";
$result2 = #mysql_query($sql2, $connection);
while ($row2 = mysql_fetch_array($result2))
{
$id = $row2['id'];
$itemID = $row2['itemID'];
$catID = $row2['catID'];
$Title = $row2['Title'];
}
}
We have users, questions and unlimited levels of categories. The users can get some points from questions. Questions can have multiple categories.
What I want to do is to calculate the top users per category: It's simply total points taken from the questions under that category AND it's sub-categories too.
So, I have these tables:
questions
--------------
id
title
question
categories
--------------
id
parent_id
category
lft
rgt
question_categories
--------------
question_id
category_id
users
--------------
id
username
user_points
--------------
id
user_id
question_id
point_type
points
user_category
--------------
user_id
category_id
points
What I want to do is to calculate user_category.points value.
Summing up the points for each category is easy but including the sub-categories is getting complicated.
What might be the best way to do this?
Example calculation:
Let's say the categories are:
Programming
PHP
Zend Framework
Symfony
Java
Ruby on Rails
Assume that the user got 3 points from Zend Framework, 2 points from PHP, 5 points from java and 1 point from Rails. The points for this user per categories will be:
Programming 11 (5+5+1)
PHP 5 (2+3)
Zend Framework 3
Symfony
Java 5
Ruby on Rails 1
Perhaps it would be best to use tags instead of a hierarchy. For instance, anything with a "Zend Framework" will also have "PHP" and "Programming" tags. This also helps when some categories can appear in multiple places. For instance, I can use ajax in jQuery and also Javascript. Then, add 1 to each tag listed in the category for the user.
I would create a user_categories table in which I would store 3 values: user_id, category_id and user_score. It's easy to maintain (need only to INSERT or UPDATE) and it's also easy to query for top-users of every category.
If you're only going to sum per top-level category, then you should add a field to your categories table called root_id (holding the id of the transitive parent of the category).
Then your sum would be calculated as:
select up.user_id, ctg.root_id, sum(up.points)
from user_points up
join question_categories qc on up.question_id = qc.question_id
join categories ctg on qc.category_id = ctg.id
group by up.user_id, ctg.root_id
This php and SQL should get you the top 3 users for each category including sub categories:
$query = "SELECT id, parent_id FROM categories";
$parent = array();
...fetch mysql data loop depending on what connection you use, mysqli or pdo...
{
$parent[$result['id']] = $result['parent_id'];
}
$childs = array();
foreach($parent as $id => $parrent_id)
{
$childs[$parrent_id][$id] = $id;
$next_parrent_id = $parrent_id;
while($next_parrent_id = $parent[$next_parrent_id])
{
$childs[$next_parrent_id][$id] = $id;
}
}
foreach($parent as $id => $parrent_id)
{
$current_categories = array($id => $id) + $childs[$id];
$query = "SELECT user_id, username, SUM(points) AS total_points
FROM user_points
LEFT JOIN users ON (user_id = users.id)
LEFT JOIN question_categories USING (question_id)
WHERE category_id IN (" . implode(', ', $current_categories). ")
ORDER BY total_points DESC
LIMIT 3";
...fetch mysql data loop...
}
I have a table called categories which looks like this
+-------------+--------------+---------------+
| id | catName | parentId |
+-------------+--------------+---------------+
| 1 | Category 1 | 0 |
| 2 | Category 2 | 0 |
| 3 | Sub Cat 1 | 1 |
| 4 | Sub Cat 2 | 1 |
| 5 | Sub sub cat 1| 4 |
| 6 | Sub sub cat 2| 4 |
| 7 | Category 2 | 0 |
+-------------+--------------+---------------+
It would be easy to delete a category and its direct child:
mysql_query("DELETE FROM `categories` WHERE `id`='$id'");
mysql_query("DELETE FROM `categories` WHERE `parentId`='$id'");
However I need to be able to delete ALL children of a category. For example if Category 1 was deleted Sub Cat 1, Sub Cat 2, Sub sub cat 1, Sub sub cat 2 will be deleted
Any help appreciated.
You could use foreign keys, a very nice feature of mysql. This is a kind of integrity check for referencing different relations. If you create a foreign key, mysql ensures that referenced entries exist, for example. During the creation of a foreign key, you can define what mysql should do if the "parent element" is deleted. You can specify "SET NULL", "UPDATE" or "DELETE CASCADE". This means, if you delete a category, every connected sub category is deleted as well. And because of the fact that every sub category is the parent category of a sub sub category, those are deleted as well.
Here is a simple recursive script for deleting your category and all the nested categories nested inside.
try {
$db = new PDO(DB_TYPE.':host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS);
}catch( PDOException $e ) {
echo $e->getMessage();
}
class Category {
function delete_category($parrent){
global $db;
$stmt = $db->prepare("SELECT `id` FROM `categories` WHERE `parrentId`=:parrentId");
$stmt->execute(array('parrentId'=>$parrent));
while($category = $stmt->fetchObject('Category')){
$category->delete_category($category->id);
}
$stmt = $db->prepare("DELETE FROM `category` WHERE `id` = :id");
$stmt->execute(array('id'=>$parrent));
}
function __construct(array $data = NULL) {
if( empty($data) )
return;
foreach( $data as $field => $value ) {
$this->$field = $value;
}
}}
Where $db is your PDO object for connecting with the database. It may have some bugs, as i just modified mine to suit your problem. Hope this helps.
PS: for calling your function you just do: $category=new Category();
$category->delete_category($_GET['id'])
Quoted my original answer for clarity.
UPDATE 1: The problem with the table splitting is that it isn't clear that there is a fixed number of sub, sub, categories. For this to work, you would need to split into as many tables as there are subcategories, each with foreign keys back to their parent ids. Not practical if the number of sub (or sub sub) categories is dynamic.
This is a situation where I think you would benefit in changing your
table design. If you split the two tables into categories and
subcategories you could take advantage of the foreign key
functionality as described by strauberry. For example (these don't
look like tables, but hopefully this makes sense) Stuff in parentheses
is explanation:
categories
id (primary key)
catName
subcategories
sub_id (primary key)
id (foreign key referencing primary key of categories table)
subCatName
Then when you create the categories table, you can use the ON DELETE
CASCADE option. This will mean that whenever you delete a category
from the categories table, all related subcategories in the
subcategories table will be deleted automatically for you by MySQL.
Powerful, and reduces your code, and you don't have to make sure all
deletions happen in the right tables manually. Hope that clarifies a
little.
UPDATE 2: The problem with this option is, well, it won't work :) MySQL will not allow you to subquery on the same table that you are doing the DELETE on. So what appeared at first to be a simple problem... isn't.
If you can't change the table design then you could do the following
(substitute the id you actually want to delete for
category_id_to_delete):
DELETE FROM categories
WHERE id = category_id_to_delete OR id IN (
SELECT id
FROM categories
WHERE parentID = category_id_to_delete
)
So, I did some more checking, and came across this similar question on SO MySQL: How to find all IDs of children recursively?, and the highest rated answer points to this article Managing Hierarchical Data in MySQL. That article points out the type of queries with LEFT JOINS that you need to use to select data, but doesn't explicitly cover how to delete that data. The recommended solution would be to use the queries related to adjacent sets from the article to select all the id information you need, and then in your code (php I think?) loop over those ids and construct a new query to delete them.
For example, if you are trying to delete Category 1 (id=1), your goal by looping over the ids from the LEFT JOIN query from the article would be to create another query that looks like:
DELETE FROM categories
WHERE id IN (1,3,4,5,6);
The code in this forum post also may help you as a means of doing it recursively in php: Adjacency List Model recursive deletion
I know that isn't a neat and tidy answer, but perhaps this will point you in the right direction.
demonstration of the mysql "cascade on delete" feature. if you run this in your console, then you can just run all the queries at once, and the output will make sense. if you run it in some graphical tool, then i suppose you'll have to run the queries one by one.
CREATE DATABASE `recursivedemo`;
USE `recursivedemo`;
CREATE TABLE `categories` (
`id` INT AUTO_INCREMENT,
`catName` VARCHAR(50),
`parentId` INT,
PRIMARY KEY (`id`),
KEY `parentId` (`parentId`),
CONSTRAINT `categories_fk_self` FOREIGN KEY (`parentId`) REFERENCES `categories`(`id`) ON DELETE CASCADE
) Engine=InnoDB;
SELECT * FROM `categories`;
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Category 1', NULL);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Category 2', NULL);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Sub Cat 1.1', 1);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Sub Cat 1.2', 1);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Sub sub cat 1.2.1', 4);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Sub sub cat 1.2.2', 4);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Sub Cat 2.1', 2);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Sub Cat 2.2', 2);
INSERT INTO `categories`(`catName`, `parentId`) VALUES('Category 3', NULL);
SELECT * FROM `categories`;
DELETE FROM `categories` WHERE `id`=1;
SELECT * FROM `categories`;
DROP DATABASE `recursivedemo`;
You can use following Function in model for deleted nested categories:
public function delete_by_id($cat_id)
{
$this->db->delete('Categories', ['cat_id' => $cat_id]);
$q = $this->db->where('parent_id', $cat_id)->get('Categories');
foreach( $q->result() as $Child ) {
$this->delete_by_id($Child->cat_id);
}
}
I have a parent category that holds all Cars names, denoted by parent_name in table "parent". For each of these parents, there could be any number of car models & they all go in table called "model". Each of these models can have any number of images & refereced via the model_id as the Foreign Key. My task is to show all the Parent Name only once (as in a group) and at the same time, list all the models under that Parent with just 1 corresponding image. The parent_name should not be shown more than once.
MY EXPERIMENTS:
I basically tried to write 2 queries. One was to left join "parent" table on "models" & use GROUP BY parent_id and then in the while loop, write another query to fetch only 1 image using by joining the models & images tables by using model_id field. But doing this lists only 1 Model, even though there are multiple models. So I tried to use GROUP BY parent_id, model_id. Using this does show all the models but at the same time, also repeats showing the parent_name & I need the parent_name to show only once throughout the page. You can say that I am trying to GROUP the model_name under the parent & show all the models under a single parent and I am showing only 1 image of the model. If somehow I can avoid showing the parent_name multiple times, the issue would be solved.
Following are my table schemas:
//Table parent
parent_id parent_name
1 Par1
2 Par2
//Table model
model_id parent_id model_name
1 1 Model1
2 2 Model2
3 1 Model3
4 1 Model4
5 2 Model5
//Table model_images
image_id model_id
1 1
2 1
3 1
4 2
5 3
6 3
DESIRED OUTPUT:
Par1 ---> This is the parent. Needs to be shown only once.
Model1 --> This is a model. List all models that belong to this parent.
image_id 1 -> Show only 1 image of the model (model may have multiple images but I need just one)
Model3 --> This is a model.
image_id 5 -> Show only 1 image of the model
Model4 --> This is a model.
No Image -> Note that no image exists for this model. So we show "No Image" text.
------------------------------------------------------------
Par2 ---> This is the parent. Needs to be shown only once.
Model2 --> This is a model.
image_id 4 -> Show only 1 image of the model
Model5 --> This is a model.
No Image -> Note that no image exists for this model. So we show "No Image" text.
I need the PHP & mySQL code to achieve the above. All help in resolving the issue is appreciated.
Thank you very much.
EDIT 1:
Sorry, I forgot to add this. I am non-object oriented programmer. So I would really be thankful if you can avoid object oriented code in your solution and show me the same in a non-oops way. Thanks.
You might do it in one query and than combine it to an associative array:
$query = ' SELECT *
FROM parent AS p
LEFT JOIN model AS m
ON p.id = m.parent_id
LEFT JOIN model_images AS m_i
ON m.model_id = m_i.model_id';
$array = array();
if($mysli->query($quer)){
while($row = $result->fetch_assoc()){
$array[$row['parent_name']][$row['model_id']] = $row;
}
}
You will than have an associative array with the parent name as the key of the array. You can then use a for loop to print the key only once (with $i = 0) but the rest value by value.
Is that clear enough?
EDIT: Your array than might look like this:
Array(
'Par 1' =>
Array(
[0] => Array(
'parent_id' => 1,
'parent_name' => 'Par 1',
'model_id' => 1,
'model_name' => 'Model 1',
'image_id',
),
[1] => Array(...)
),
'Par 2' => Array(...)
)
So to print out you need two loops. One for the parents (and there names) and one for their childs (models in this case).
foreach($array as $par_name => $models){
echo 'Parent name: '.$par_name.'<br />';
echo 'Model ID: '.$models[0]['model_id'].', Model Name: '.$models[0]['name']; // replace with your desired output
}
Now an idea of how it works? An sure as Artefacto said, you can use procedural functions if you don't like OOP functions.