Database model correction mysql? - mysql

I have created a database of 3 tables: "products", "brands" and "categories".
products table has product_id, brand_id, category_id, etc. So by using category_id and brand_id from other tables, I can pick up and show the different data from product table.
However, I want a solution like, for example, what if a product lies in two or more categories? What should be the database model and what table changes should I need to do more in my database?
My table structures are listed below:
Database:
Brand table:
Category table:
Product table:

Mapping cardinalities between product and category table are many to many. So you have to create a new table to keep records product and category relationship according to database normalization. SO remove category_id from the product table and create a table like as the below. For example
create table productCategory(
product_id integer references product_table(product_id),
category_id interger references category_table(category_id),
primary key (product_id,category_id)
);

there is two solution from my logic:
Change type of cat_id column in product table, using VARCHAR or TEXT, so each product have cat_id with separated by comma or colon, example value: 1,4,5
Remove column cat_id from product table, Add another table for mapping product into cat_id, example scheme:
CREATE TABLE product_mapping (
id INT(11) NOT NULL AUTO_INCREMENT,
product_id INT(11) NOT NULL,
cat_id INT(11) NOT NULL,
PRIMARY KEY (id)
)
So.. you can query by joining 3 table for product information, with example query as below:
SELECT pm.cat_id AS m_cat_id, pm.product_id AS m_product_id, p.*, c.*
FROM product_mapping AS pm
INNER JOIN products AS p ON p.id = pm.product_id
LEFT JOIN categories AS c ON c.cat_id = pm.cat_id
WHERE pm.product_id = :produc_id

Related

How to select entries in a join table based on the aggregate selection of data from several other tables?

I have an application based on 4 MySQL tables:
persons
------------
id -- Primary key, unique
name
entity_id -- foreign key => entities
entities
------------
id -- Primary key, unique
name
company_id -- foreign key => companies
companies
------------
id -- Primary key, unique
name
persons_linked_companies -- join table : persons <=> companies
--------------------------
id -- Primary key, unique
persons_id -- foreign key => persons
companies_id -- foreign key => companies
Each "person" belong to an "entity"
Each "entity" belong to a "company"
A "person" can only have one "entity"
An "entity" can only have one "company"
A "person" can be linked to one or more third parties (meaning other companies). For this there is a join table called "persons_linked_companies"
A person can have multiple linked companies, but a person shouldn't be linked to his own company
I can't figure out what kind of subquery/join I should issue to get the following data:
I need to select entries in the join table "persons_linked_companies" to get all persons whose linked company is the same has the company they belong to (because of bullet point 6).
Pseudo code below:
select id from persons_linked_companies where companies_id = (select id from companies where persons.entity.company_id = persons_linked_companies.companies_id)
Besides using aliases. you can join all tables
But this would only yield a result, if you entered such a row in the table persons_linked_companies, which should be not be done by your rule 6
SELECT
id
FROM
persons_linked_companies pcl
WHERE
companies_id = (SELECT
c.id
FROM
companies c
INNER JOIN entities e ON e.company_id = c.id
INNER JOIN persons p ON p.entity_id = e.id
WHERE
p.id = pcl.persons_id)

Inner Join on many-to-many relationship on the same table

I came across a weird problem while I was trying to apply the Inner Join on the table which has 3 columns -
Table category_subcategory_relation
id (PK)
category_id_ref (FK)
subcategory_id_ref (FK)
The columns category_id_ref and subcategory_id_ref are the primary keys of the table category.
Table category
id (PK)
category_name (Varchar(200))
I want to Inner Join this two tables and get the columns as following -
Category_Name
Subcategory_Name
I am not able understand how would I be able to get the category_name column from the category table twice, once for the column Category_Name (FK) and again for the column Child_Category_Name (FK).
You would have to perform two joins, one each on category_id_ref and subcategory_id_ref:
SELECT a.*,
b.category_name AS Category_Name,
c.category_name AS Subcategory_Name
FROM category_subcategory_relation a
JOIN category b
ON a.category_id_ref = b.id
JOIN category c
ON a.subcategory_id_ref = c.id
While your category table contains category and subcategory information, you'll have to look at them as two separate tables (one for category and other for subcategory) to bring this information back to your category_subcategory_relation table.

Need a MySQL JOIN to return all categories a product is in, per product, even if only one category is searched for

I have the following tables defined:
CREATE TABLE products (
product_id INTEGER(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
section VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (product_id)
) ENGINE=MyISAM;
CREATE TABLE categories (
category_id INTEGER(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (category_id)
) ENGINE=MyISAM;
CREATE TABLE product_categories (
product_id INTEGER(11) NOT NULL,
category_id INTEGER(11) NOT NULL,
PRIMARY KEY (product_id, category_id)
) ENGINE=MyISAM;
There are actually many more, and this is part of optimizing much larger, more complex queries. Part of it is moving some slow subqueries into views, which have helped a lot so far.
Queries became very slow when I added the categories/product_categories tables and joined against them when allowing users to search by products.section or categories.category_id. The UI passes those in as search parameters, and I'm trying to get a row per product with its id, name, section, and a comma-separated list of category names associated with it. I was able to make that much faster with the following view and query:
CREATE OR REPLACE
ALGORITHM = MERGE
VIEW view_products_with_categories
AS
SELECT
p.product_id,
p.name,
p.section,
pc.name AS category
products p
LEFT JOIN product_categories pc on p.product_id = pc.product_id
LEFT JOIN categories c ON pc.category_id = c.category_id;
SELECT
product_id,
name,
section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
GROUP BY product_id;
Let's say we have the following rows:
product_id name section category_id category
332913 Model Train Engine child-and-baby 1160 child-and-baby>baby-and-pre-schooltoys>playsets
332913 Model Train Engine child-and-baby 1308 toys>baby-and-preschool>playsets
332913 Model Train Engine child-and-baby 1312 toys>carstrains-and-planes>cars-and-vehicles
The simple query above gives me the following:
product_id name section categories
332913 Model Train Engine child-and-baby child-and-baby>baby-and-pre-schooltoys>playsets,toys>baby-and-preschool>playsets,toys>carstrains-and-planes>cars-and-vehicles
That's fine, and as expected. However, I'd like the user to be able to search by category_id. Currently, our UI does some auto-complete magic on category names and adds a filter to the dynamically-generated SQL with the category_id in it. Had I left the category_id in the GROUP_CONCAT query, it'd be 1160. Let's say they wanted to search for the second one (1308), so we modify the query like so:
SELECT
product_id,
name,
section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
WHERE category_id = 1308
GROUP BY product_id;
Now we get the following:
product_id name section categories
332913 Model Train Engine child-and-baby toys>baby-and-preschool>playsets
Again, exactly what you'd expect. However, the customer wants to see all categories associated with the product that has one or more of the categories they're searching for. So, let's make some simplified sample data to show you what I'm looking for:
product_id name section category_id category
1 product_1 section_1 1 category_1
1 product_1 section_1 2 category_2
1 product_1 section_1 3 category_3
2 product_2 section_2 3 category_3
2 product_2 section_2 4 category_4
2 product_2 section_2 5 category_5
If the user searches for category_id = 3, I'd like them to get the following:
product_id name section categories
1 product_1 section_1 category_1, category_2, category_3
2 product_2 section_2 category_3, category_4, category_5
But I'm currently only getting:
product_id name section categories
1 product_1 section_1 category_3
2 product_2 section_2 category_3
I just can't figure out a way to do it without a subquery, whose slowness is the reason I'm moving to views in the first place. I'm hoping there's something blindingly obvious I'm missing, probably due to sleep deprivation, so any help would be appreciated.
UPDATE: I also should mention that it's possible that a product is not in any categories, hence the LEFT JOINs in my code.
The following query works: (although I am not using the view)
select pc1.product_id, products.name, products.section,
group_concat(categories.name)
from products
inner join product_categories pc1
on (pc1.product_id = products.product_id and pc1.category_id = 3)
inner join product_categories pc2
on (pc2.product_id = products.product_id)
inner join categories
on (categories.category_id = pc2.category_id)
group by pc1.product_id, products.name, products.section
If you want to use the view the following will work:
SELECT vpc.product_id,vpc.name,vpc.section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories vpc
inner join product_categories pc
on (pc.product_id = vpc.product_id and pc.category_id=3)
GROUP BY vpc.product_id, vpc.name, vpc.section;

how do I do a join with a polymorphic has_many 'through'?

I have the following data structure (simplified), which includes a polymorphic association in the purchases table (MySql)
Purchases
product_type (this refers to a table, book, dvd, shirt,...)
product_id (this refers to the product's id in the table in product_type)
amount
Books
category_id
DVDs
category_id
Shirts
category_id
Categories
name
I would like to select categories from the db, ordered by total sales (purchases.amount). How can I do this with joins and aggregate functions?
I made a sqlfiddle for this:
http://sqlfiddle.com/#!2/74705
Polymorphic IDs are similar to inheritance structures in a DB, in that to query across all of them often you have to use a union.
However, if your Books/Shirts/DVDs tables have no common columns, it makes absolutely no sense to query across them. If they did have common columns, probably makes more sense to pull that out into a parent Product table that acts as a base for the others.
If you for example wanted total sales per product, you'd just do
Select sum(amount), product_id -- or product_type if you wanted sales per type
From Purchases
Group By product_id -- product_type
You could pull in product titles like this:
Select sum(p.amount), p.product_id, b.Title
From Purchases p
Inner Join Books b on b.category_id = p.product_id
Group By product_id
Union
Select sum(p.amount), p.product_id, s.Title
From Purchases p
Inner Join Shirts s on s.category_id = p.product_id
Group By product_id
It might be more performant to do the union first, then the group by outside of that.
My experience with inheritance hierarchies is that you should do whatever you can to avoid unions. They are a pain to query, result in non-DRY queries, and perform badly. This means pulling out commonalities such as Title into a proper normalized structure to avoid having to query across the concrete/derived types. This is essentially what Wrikken mention in comments "Usually, a better way for this would be a Products table with the shared information types"
Here's an example of how you could achieve this using temporary tables. The principle is that you create a "temporary" table and populate it with the data that you want, in the structure that you want, when your query is run. You can modify the data, and return it at the end and the table is then destroyed when the connection closes.
Anyway - you can set up your temporary table like with the schema combining a new unique, product id, product type, product name and category id. You then populate the table with a union query between all the records in your books, dvds and shirts tables to suit that format:
-- delete the temp table in case it's still there
DROP TEMPORARY TABLE IF EXISTS all_products;
-- create your new temp table
CREATE TEMPORARY TABLE IF NOT EXISTS all_products (
new_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_id INT(11) NOT NULL,
product_type VARCHAR(50) NOT NULL,
product_name VARCHAR(128) NOT NULL,
category_id INT(11) NOT NULL
) AS (
-- populate it with unioned queries
SELECT * FROM (
(SELECT NULL AS new_id, id AS product_id, 'book' AS product_type, `name` AS product_name, category_id FROM books)
UNION ALL
(SELECT NULL AS new_id, id AS product_id, 'dvd' AS product_type, `name` AS product_name, category_id FROM dvds)
UNION ALL
(SELECT NULL AS new_id, id AS product_id, 'shirt' AS product_type, `name` AS product_name, category_id FROM shirts)
) AS temp -- an alias is required - doesn't matter what it is
);
Now you can access the data in this table as you normally would. The benefit of using temporary tables is that you don't have to change your database structure - in your case it's maybe not ideal to have it set up the way it is, but doing something like this could help you to select data efficiently without changing it.
To select data, you could use a query like this:
SELECT
purchase.*,
product.product_name,
category.name
FROM purchases AS purchase
-- get your product info from the temp table
INNER JOIN all_products AS product
ON (product.product_id = purchase.product_id AND product.product_type = purchase.product_type)
-- get the category name
INNER JOIN categories AS category
ON (category.id = product.category_id)
You said you want to order your results by amount, etc. Add your conditions and ordering to the query above as you want to.
Example output:
id product_id product_type amount product_name name
1 1 book 10 how to educational
2 1 dvd 10 video how to educational
3 1 shirt 10 tshirt clothes
These can actually be very efficient to run, for example running this on my local server is executing in 0.001s - it will scale very nicely. I would've set you up a fiddle for this, but SQLFiddle doesn't seem to support temporary tables very well. Just run the CREATE and SELECT statements at the same time on your local server to see.

MYSQL Conditions

i have a mysql table category with a field events as varchar which its values is like this : 11,29,32.and this values referer to event table ID. ( so i can say : i have a several event ID from event table in category table)
So i want to select events from category like that :
SELECT *
FROM
event e, category c
where e.event_id in (c.events)
But that not give the correct result instead when i put the values manually like :
SELECT *
FROM
event e, category c
where e.event_id in (11,29,32)
I hope that's clear,
Any help please
Part of your problem is the way that you have set up your tables. Typically you will have an events, category and then a join table between the two. You should not store data in a comma separated list to query against.
If you cannot alter your table structure, then you can use the MySQL function FIND_IN_SET():
SELECT *
FROM event e
INNER JOIN category c
on find_in_set(e.event_id, c.events)
See SQL Fiddle with Demo
If you can alter your tables, then the structure should be:
create table events
(
event_id int not null auto_increment primary key,
event_name varchar(50) not null
);
create table category
(
cat_id int not null auto_increment primary key,
cat_name varchar(50) not null
);
create table events_category
(
event_id int not null,
category_id int not null,
PRIMARY KEY(event_id, category_id),
constraint fk_event
foreign key (event_id) references events (event_id),
constraint fk_category
foreign key (category_id) references category (cat_id)
);
Then when you query the data you would use:
select *
from events e
left join events_category ec
on e.event_id = ec.event_id
left join category c
on ec.category_id = c.cat_id
You can use FIND_IN_SET:
SELECT *
FROM event e INNER JOIN category c
ON FIND_IN_SET(e.event_id, c.events)
FIND_IN_SET returns 0 if e.event_id is not present in c.events, otherwise it returns its position. If the resulting value is greater than 0, the join will succeed.
It looks like you're trying to create a many-to-many relationship between categories and events. If that's the case, you really need to refactor your design to include a table that maps this relationhsip.
category
--------
- id
- name
event
-----
- id
- name
category_to_event
-----------------
- id
- category_id
- event_id
Creating this type of structure will allow you to perform the queries you seek.