I have a design issue with my Mysql MyISAM tables.
One of my tables contains a list of 'categories', (approx 100 rows)
id |catcode |name
-------------------------------
1 #01 lorem
2 #0101 ipsum
3 #02 dolor
Another table contains 'products'. (approx 28.000 rows)
id |cats |name ...
---------------------------
1 #01#02 lorem ...
2 #0101 ipsum ...
3 #02#0101 dolor ...
in my products table, I define the list of categories where they belong to.
that's working perfectly, but now:
I'd like to customize the ordering they appear depending on the category selected.
At the moment, I have a column with the id of the categorie in my product table, but this seams very dirty (ordering_1, ordering_2, ordering_3, ...)
Especially because each product appears in only 2 or 3 different categories, so most of the columns are just empty:
id |cats |name |ordering_1 |ordering_2 |ordering_3 ...
-----------------------------------------------------------------------
1 #01#02 lorem 17 12
2 #0101 ipsum 13
3 #02#0101 dolor 21 8
I'd like to use a third table to determine the ordering, but how?
The basic idea is to have a table with the categories and an 'ordering' list, but one of these lists contains all the products! (the main category) I suppose it would be veeery slow to do a request with 28.000 id in it...
How to handle this?
You should use foreign keys and a join table to link products with categories, instead of your catcode construct.
Create the table structure somewhat like the following (example is for MySQL):
CREATE TABLE product (
id INT NOT NULL,
name VARCHAR(255),
PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE category (
id INT NOT NULL,
name VARCHAR(255),
PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE product_category (
product_id INT NOT NULL,
category_id INT NOT NULL,
ordering INT NOT NULL,
PRIMARY KEY (product_id, category_id),
FOREIGN KEY (product_id) REFERENCES product (id),
FOREIGN KEY (category_id) REFERENCES category (id)
) ENGINE=InnoDB;
Then to get all products in a particular category sorted as defined for this category, query them like this:
SELECT * FROM product p LEFT JOIN product_category pc ON p.id = pc.product_id
WHERE pc.category_id = 1
ORDER BY pc.ordering ASC;
1 is the category id in this. You could also query by category name for example:
SELECT * FROM product p
LEFT JOIN product_category pc ON p.id = pc.product_id
LEFT JOIN category c ON pc.category_id = c.id
WHERE c.name = "foo"
ORDER BY pc.ordering ASC;
Related
I indeed read this post (Get all posts from a specific category) but it does not seem to apply to my situation.
My situation is that i have two tables as follows:
1/ table categories
category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
lang_id TINYINT UNSIGNED NOT NULL,
name VARCHAR(60) NOT NULL,
PRIMARY KEY (category_id),
UNIQUE (name)
) ENGINE = INNODB';
in which lang_id values are 1 (equivalent to English) and 2 (equivalent to Vietnamese) which is used for filtering by using $_SESSION['lid'].
category_id lang_id name
1 1 Arts and Entertainment
2 1 Computers
3 2 Nghệ thuật và Giải trí
4 2 Máy tính
2/ table posts:
'CREATE TABLE posts (
post_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
category_id TINYINT UNSIGNED NOT NULL,
lang_id TINYINT(3) UNSIGNED NOT NULL,
user_id INT UNSIGNED NOT NULL,
subject VARCHAR(150) NOT NULL,
PRIMARY KEY (post_id),
INDEX (category_id),
INDEX (lang_id),
INDEX (user_id)
) ENGINE = MYISAM';
In which the category_id is the foreign key of the first table.
question_id category_id lang_id user_id subject
1 1 1 1 arts
2 4 2 1 máy tính
3 5 1 1 business and money
I would like to select the posts in a certain category when we mouse-click on it. So, I run this query:
$q = "SELECT subject
FROM categories AS ca
INNER JOIN questions AS q
USING (category_id)
WHERE q.lang_id = {$_SESSION['lid']}
GROUP BY ca.category_id
$r = mysqli_query($database_connect, $q)
if(mysqli_num_rows($r) > 0) {
while ($subject = mysqli_fetch_array($r, MYSQLI_ASSOC)) {
echo '<ul>
<li>'. $subject['subject']. '</li>
</ul>';
}
But the result does not return as desired. For example, When I click on category 1 (Arts and Entertainment), the result returns two subjects (arts posted in category_id 1, business and money posted in category_id 5 ).
Can you help me to re-build the query, please? I really really got stuck here.
Add the category id in WHERE clause to select post of a given category
SELECT subject
FROM categories AS ca
INNER JOIN questions AS q
USING (category_id)
WHERE q.lang_id = {$_SESSION['lid']}
AND ca.category_id = 1
SELECT categories.*, posts.* from posts left join posts on categories.category_id=posts.category_id where posts.lang_id = {$_SESSION['lid']} and posts.category_id={your given category}
i have a 2 category system, basically what i want to do is i have 2 tables, top_category and bottom_category, i have created my sidebar which will list all the products using sql query. is there a way i can pull the top_category and bottom_category data in one sql query and have the bottom_category sorted by the foreign key id of top_category so when i loop them in a list they end up in the right nest?
Here are my tables,
CREATE TABLE top_category (
id INT PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE bottom_category (
id INT PRIMARY KEY,
NAME VARCHAR(100) ,
top_category_id INT REFERENCES top_category
);
And here is my products table, so when i click on a bottom_category link i want it to list the products linked to the bottom_category_id's:
create table product (
id int primary key,
name varchar(100) ,
bottom_category_id int references bottom_category
);
You could write something like
SELECT product.*, bottom_category.name, top_category.name
FROM product
LEFT JOIN bottom_category ON bottom_category.id = product.bottom_category_id
LEFT JOIN top_category ON top_category.id = bottom_category.top_category_id
ORDER BY top_category.id,bottom_category.id
But if you have really big tables then just forget about 3nd normal form and add names for categories into product table. But only if you have really big tables with categories.
UPD
Add ORDER BY
select p.*,
bc.name bc_name,
tc.name tc_name
from product p
left join bottom_category bc on p.bottom_category_id=bc.id
left join top_category tc on bc.top_category_id=tc.id
order by tc.id,bc.id
I have 3 tables products and stock and purchases.
Puchases has a primary key of purchaseID.
My products table has a primary key of productID and then columns with details on the product.
My stock table has a primary key of framenumber a foreign key of productID and another foriegn key of purchaseID.
Not all products have any stock.
I would like to join the prodcuts and the stock table by the productID and have a row on the end which counts the amount of null values in the purchaseID column, which represents how many i have in stock.
I have tried a load of queries but seem to always just come up with total amount of entries in stock table or with subqueries not left joining properly.
Some things ive tried
SELECT * , COUNT( * ) AS Count
FROM Products
LEFT JOIN Stock ON Products.productID = Stock.productID
WHERE Stock.OrderID IS NULL AND Stock.framenumber IS NOT NULL
GROUP BY Products.productID
Just gives me the count of all the objects in stock grouped by productID and doesnt join all the products
Schema
CREATE TABLE BikeStock (
FrameNumber VARCHAR(10) PRIMARY KEY,
BikeCode VARCHAR(40),
OrderID INT,
FOREIGN KEY (BikeCode) REFERENCES Bike(BikeCode),
FOREIGN KEY (OrderID) REFERENCES Purchase(OrderID)
);
CREATE TABLE Bike (
BikeCode VARCHAR (40) PRIMARY KEY,
Manufacturer VARCHAR (30),
Model VARCHAR (30),
SubType VARCHAR (30),
Year SMALLINT,
FrameMaterial VARCHAR (20),
Description TEXT,
Gender VARCHAR (6),
Type VARCHAR (20),
Price DECIMAL (11,2)
);
CREATE TABLE Purchase (
DateOfPurchase DATETIME,
OrderID INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(OrderID),
CustomerID INT,
FOREIGN KEY (CustomerID) REFERENCES Customer(CustomerID)
);
SELECT Products.productID , SUM(
case
when (Stock.OrderID IS NULL AND Stock.framenumber IS NOT NULL)
then 1
else 0
end case
) AS Count
FROM Products
LEFT JOIN Stock ON Products.productID = Stock.productID
GROUP BY Products.productID
I've been looking at this far too long and my brain feels like spaghetti noodles. Could someone help me out with a couple of queries?
Tables:
Presentation
------------
id int
name varchar
fk_template int (references template.id)
Template
--------
id int
name varchar
Position
--------
id int
zorder int (if it is the 1st, 2nd, 3rd position of the given template)
fk_template int (references Template.id)
Asset
-----
id int
name varchar
description varchar
AssetForPresentation
--------------------
fk_asset int (references Asset.id)
fk_presentation int (references Presentation.id)
fk_position int (references Position.id)
What I think I need to be asking at this point is basically "give me all of the assets AND their positions for this template."
You see, when a Presentation is brought it, it has a specific Template. Each Template has designated Positions, and each Position holds an Asset or NULL.
I need to be able to bring in all Assets and their individual positions for a particular Template used by a Presentation. How would I query for something like that?
I hope this makes sense to you.
I think the part you're having hard time with is two conditions on JOIN to the AssetForPresentation table.
SELECT
a.id,
a.name,
a.description
FROM Presentation AS p
JOIN Template AS t
ON p.fk_template = t.id
LEFT JOIN Position AS pos
ON pos.fk_template = t.id
LEFT JOIN AssetForPresentation AS afp
ON afp.fk_presentation = p.id
AND afp.fk_position = pos.id
LEFT JOIN Asset AS a
ON a.id = afp.fk_asset
WHERE p.id = 123
ORDER BY pos.zorder ASC
Based on what you have described in your question, you need to get all assets for a given template. I have created the table structure but didn't define the relation constraints on the table but used them while formulating the query.
You can join the Asset table to the AssetForPresentation table. Through AssetForPresentation table, you can join the Presentation and Position tables. The relation to the Template can be made through the Position table. Thus, joining the Template table to the Asset table to fetch all matching records.
You can view the demo in the below link.
Click here to view the demo in SQL Fiddle.
Hope that helps.
Script:
CREATE TABLE Presentation
(
id INT NOT NULL AUTO_INCREMENT
, name VARCHAR(30) NOT NULL
, PRIMARY KEY (id)
);
CREATE TABLE Template
(
id INT NOT NULL AUTO_INCREMENT
, name VARCHAR(30) NOT NULL
, PRIMARY KEY (id)
);
CREATE TABLE Position
(
id INT NOT NULL AUTO_INCREMENT
, zorder INT NOT NULL
, fk_template INT NOT NULL
, PRIMARY KEY (id)
);
CREATE TABLE Asset
(
id INT NOT NULL AUTO_INCREMENT
, name VARCHAR(30) NOT NULL
, description VARCHAR(30) NOT NULL
, PRIMARY KEY (id)
);
CREATE TABLE AssetForPresentation
(
fk_asset INT NOT NULL
, fk_presentation INT NOT NULL
, fk_position INT NOT NULL
);
INSERT INTO Presentation (name) VALUES
('presenation 1'),
('presenation 2');
INSERT INTO Template (name) VALUES
('template 1'),
('template 2');
INSERT INTO Position (zorder, fk_template) VALUES
(1, 1),
(2, 2);
INSERT INTO Asset (name, description) VALUES
('asset 1', 'asset description 1'),
('asset 2', 'asset description 2');
INSERT INTO AssetForPresentation (fk_asset, fk_presentation, fk_position)
VALUES
(1, 1, 1),
(1, 2, 1),
(2, 2, 1),
(2, 2, 2);
SELECT *
FROM Asset A
RIGHT OUTER JOIN AssetForPresentation AP
ON A.id = AP.fk_asset
RIGHT OUTER JOIN Presentation P
ON P.id = AP.fk_presentation
RIGHT OUTER JOIN Position PO
ON PO.id = AP.fk_position
RIGHT OUTER JOIN Template T
ON T.id = PO.fk_template
WHERE T.id = 1;
Output:
ID NAME DESCRIPTION FK_ASSET FK_PRESENTATION FK_POSITION ZORDER FK_TEMPLATE
-- ------- ------------------- -------- --------------- ----------- ------ -----------
1 asset 1 asset description 1 1 1 1 1 1
1 asset 1 asset description 1 1 2 1 1 1
2 asset 2 asset description 2 2 2 1 1 1
Failing to find a solution to this question I was wondering if there was a better way of storing data for this problem.
That db structure allows items to be stored in multiple categories, but doesn't allow easy access to the parent category hierarchy.
What I would like is to have a category relationship such as:
Books
> Novels
> Paperbacks
> Hardbacks
And have an item stored against Paperbacks for instance that would also show up in Novels and Books. So the 'categories' actually work more like filters than actual categories.
First of all you need to design your category table with usage of Nested Set architecture. With usage of Nested Sets you will easily select whole branch of categories, and then you will be able to select products for these categories.
So the first table will be:
CREATE TABLE categories (
id int unsigned NOT NULL auto_increment,
name varchar(255) NOT NULL,
left int unsigned NOT NULL,
right int unsigned NOT NULL,
PRIMARY KEY (id)
);
The second table will be:
CREATE TABLE products (
id int unsigned NOT NULL auto_increment,
name varchar(255) NOT NULL,
PRIMARY KEY (id)
);
And the third table will be:
CREATE TABLE product_categories (
category_id int unsigned NOT NULL,
product_id int unsigned NOT NULL,
PRIMARY KEY (category_id, product_id)
);
Now to select all products for whole branch of categories, you need to use query like this:
SELECT p.*
FROM categories AS c1
LEFT JOIN categories AS c2 ON c1.left <= c2.left AND c2.right <= c1.right
LEFT JOIN product_categories AS pc ON pc.category_id = c2.id
LEFT JOIN products AS p ON pc.product_id = p.id
WHERE c1.id = #id
Nested set operations
Add new node
1st step: update already existed categories
UPDATE categories
SET right = right + 2, left = IF(left > #right, left + 2, left)
WHERE right >= #right
2nd step: insert new category
INSERT INTO categories SET left = #right, right = #right + 1, name = #name
Delete existing node
1st step: delete node
DELETE FROM categories WHERE left >= #left AND right <= #right
2nd step: update else nodes
UPDATE categories
SET left = IF(left > #left, left – (#right - #left + 1), left),
right = right – (#right - #left + 1)
WHERE right > #right