Say I am selling a number of product. Sometimes, the product is actually a combination of other product. For example, say I am selling a:
hot dog
soda
hot dog + soda combo
How should I model something like this? Should I have a product table to list the individual products, then a product_combo table that describes the combo, and another table that is associated with product and product_combo to itemize the products in the combo? This seems straightforward to me.
However, what if I wanted to record all the sales in one table? Meaning, I don't want product_sales table and a product_combo_sales table. I want all sales to be in just one table. I'm a bit unsure how to model product and product combos in such a way I can later record all sales in one table.
Suggestions?
NOTE: I'm wondering if I could put product and product combo in one table using a parent-child relationship. With one table, then recording sales won't be hard. I'd just have to implement a business rule that editing a product combo when a sale is already recorded against that combo that the edit actually results in a new entry. Could get messy, though.
This depends on what you actually need to do with your system. A system that needs to track inventory is going to need to understand that a "combo meal" needs to debit the inventory by one hot dog and 32 ounces of soda (or whatever). A system that only keeps track of orders and dollars, however, doesn't really care about what "goes into" the combo meal -- only that you sold one and got paid for it.
That said, let's assume you need the inventory system. You can reduce your complexity by changing your definition a little bit. Think in terms of (1) inventory items and (2) menu items. Your inventory_items table contains items that you purchase and track as inventory (hot dogs, soda, etc). Your menu_items table contains items that you sell (Big Dog Combo Meal, Hot Dog (sandwich only), etc).
You can have some menu items that, coincidentally, have the same name as an inventory item but for these menu items treat them the same way you do a combo item and stick a single record into the linking table:
inventory_items menu_items recipes (menu_item, inventory, qty)
--------------- ------------ ----------
hot dog Hot Dog Hot Dog, hot dog, 1
hot dog bun Hamburger Hot Dog, hot dog bun, 1
hamburger patty (4oz) Big Dog Combo Hamburger, hamburger patty (4oz), 1
hamburger bun Soda (32oz) Hamburger, hamburger bun, 1
cola Big Dog Combo, hot dog, 1
ginger ale Big Dog Combo, hot dog bun, 1
Big Dog Combo, *soda, 32
Soda (32oz), *soda, 32
Just constructing this example, it turns out that even the lowly hot dog has two components (you have to count the bun), not just one. To come up with the simplest case (a menu item with a single component), I added Soda to the menu. Consider, however, that if you are going to inventory non-food items (cups) then even a simple Soda is going to have two components (three if you're inventorying the straws).
Note that with this design there will be no special codepaths for handling combo items and non-combo items. All menu-related functionality will use only the menu_items table, all inventory and food-prep related functionality will JOIN menu_items to recipes and (if additional fields are needed) to inventory_items.
You'll need special handling for optional components (sauerkraut, relish, chili, etc) and for components that can be selected from different inventory items (represented as *soda in this model), but this should get you started.
Both you're approaches are OK. But there's at least one other way to solve the problem which is to apply discounts to product combinations (which means you can also apportion the discount selectively) e.g.
CREATE TABLE products
(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128),
description TEXT,
price INT
);
CREATE TABLE combo_discounts
(
id NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128),
description TEXT
);
CREATE TABLE cd_products
(
cd_id INT /* REFERENCES combo_discounts.id */,
p_id INT /* REFERENCES product.id */
price_reduction INT
);
CREATE TABLE sales
(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
location ...whatever...
);
CREATE TABLE sales_items
(
sale_id INT /* REFERENCES sales.id */
p_id INT /* REFERENCES product.id */
cd_discount INT /* REFERENCES cd_products.cd_id */
);
But bear in mind that you'll need to use procedural code to assign the discounts the sale (and flag each sold item as you go) to address the problem of someone buying 2 hot dogs and one soda (and hence only getting one discount).
...and hence the total price for a sale is
SELECT SUM(p.price)-SUM(cd.price_reduction)
FROM sales s INNER JOIN sales_items si ON (si.sale_id=s.id)
LEFT JOIN cd_products cdp ON (si.cd_discount = cdp.cd_id
AND si.p_id=cdp.p_id)
AND s.id=?
I suggest you think in terms of "orders" and "items". An Order consists of many items. Items can be different "products". So, examples for Orders can be:
1) hot dog
2) soda
3) hot dog + soda
Examples for Items can be:
A) hot dog
B) soda
Also this way you can keep sales, in the orders table.
I don't think you need to have the prises for the "combo" in your database.
This is business logic that should be applied in the code, not in database.
You can apply all your discounts later in the code.
Related
I am trying to get my database design right. It is a large set of alcohol drinks consisting of beer, liquor, wine and so on. I could keep it all in a single table as follows:
id category brand type price quantity description
1 Beer Heineken bottle $2.00 100 some description...
2 Beer Calsburg bottle $3.00 200 some description
3 Beer Heineken can $1.00 300 some description....
4 Liquor JWalker bottle $30.00 100 some descri...
Seems this is bad design considering repetitions for category and brand will occur. Thus I split it into 3 tables as follows:
Category Table
id name(pk)
1 Beer
2 Liquor
Brand Table
id name(pk) category_name(FK)
1 Heineken Beer
2 Carlsburg Beer
3 Lindemans Wine
4 JWalker Liquor
Product Table
id(PK) type price quantity description category_name(FK) brand_name(FK)
1 Bottle $2.00 100 some description Beer Heineken
Thought this would be better normalised but the way I see it, hardly a difference from the first table. And I end with type repeatnig too since I can get repetitions on bottle, can and so on. So should I get a 4th table for that?
Trying to normalise and keep it as sensible as possible. Is there a better way to go about doing this?
Brand Table
brandID(PK) BrandName
Category table
BrandID(FK) CategoryID(PK) Categoryname
Product table
ProductID(PK) CategoryID(FK) description price quantity
Normalization requires knowing functional dependencies (FDs) and join dependencies (JDs) that hold. You haven't given them. So we can't normalize. But guessing at your application and your table, it is in 5NF.
Presumably id is a unique column. So it functionally determines every column set. Since no smaller subset of {id} is unique, it is a candidate key (CK). Presumably no other FDs hold other than the ones that hold because of that CK. So the table is in 5NF.
But suppose also one more FD holds: that a given brand only ever appears with the same category. Then to normalize to 5NF column category should dropped and a new table should be added with brand & category columns and CK {brand}.
Or suppose that a brand has one or more categories, and instead of a row stating that category is its product's category, it states that category is a category of its product's brand. (Weird, since then for brands with more than one category the table wouldn't give a product's category.) Then normalization also gives those two tables, with new CK {category, brand}. But in this case it's because of a multi-valued dependency (MVD), ie because of a binary JD.
PS Introducing ids has nothing to do with normalization.
PPS You seem to think that repeated subrow values imply a need for normalization. They don't. Normalization is for sometimes replacing a table by tables that always join to it.
Normalization through BCNF is based on functional dependencies. It's not based on whether a column contains text or numbers. You seem to think that, because the category column contains the word Beer more than once, it needs to be "normalized". That's not the case.
So what are the functional dependencies here?
id -> category, brand, type, price, quantity, description
category, brand, type, -> id, price, quantity, description
That second FD might be wrong. It might be that {brand, type} is the determinant. But I think it's likely that there's a company somewhere that makes both beer and liquor under the same brand name. So I think that the determinant is probably {category, brand, type}.
That's in 5NF already. "Splitting" isn't going to improve this table.
Table creation would look something like this:
create table product (
product_id int not null identity,
brand_id int not null,
category_id int not null,
primary key(product_id),
foreign key brand_id references brand(brand_id),
foreign key category_id references category(category_id)
);
create table brand (
brand_id int not null identity,
name varchar(80),
primary key(brand_id)
);
create table category (
category_id int int not null identity,
name varchar(80),
primary key(category_id)
);
You do a JOIN to get the record back:
select p.product_id, c.name as category_name, b.name as brand_name
from product as p
join category as c on p.category_id = c.category_id
join brand as b on p.brand_id = b.brand_id
I'm creating one project for restaurants where user can browse and choose food. I didn't made before such a think and have some troubles with design and relations in database tables. This is the case
1. Restaurants
2. Customer click on restaurant-1
3. Customer get menu list for foods and drinks
4. Customer browse the food via sub-categories ( Salads, Drinks, Desserts and so on )
5. Customer choose some food and drinks...
As I can see here I would need 4 main tables restaurants, meals, meal_types and menu. Table Restaurants will hold restaurants
id
name
menu
image
text
address
Table Meal_types will hold main meal category - Drinks, Salads, Desserts and so on
id
name
Table meals will hold all foods/drinks
id
name
image
text
weigh
price
Table menu must keep which food/drinks to which restaurant to show when is selected.
id
name
So here is the tricky/hard part for me. How to make relations between them. One of the relations that I see and must have is between meals and meal_types. But others? How to connect restaurants with them and when user click on some restaurant to see the food that is served only from this restaurant. First thought for me was with this table menu but don't know how exactly.
I found it easier to Name the ids according to the items they represent. So I would suggest to Name the column id in your table restaurant Restaurant_id. This might help you Keep the general view in your relations.
Start from your smallest part: Your table meals. You have to combine this table with your Meal_types table since you have a 1:1 relationship between both (each meal has exactly one type, so just add another colum meal_type to meals).
Second you have your restaurants.
Now you just need an Information about the meals a Restaurant offers. This Translation table is your table menue and holds columns like this:
Restaurant_id,
Meal_id
To get the menue of a certain Restaurant you would query
SELECT
meals.meal_type, meals.name, meals.price
FROM
restaurant, menue, meals
WHERE
Restaurant.restaurant_id = menue.restaurant_id
AND menue.meal_id = meals.meal_id
AND Restaurant.name = 'Mc Donald's'
I have a table called 'recipes' that lists, in a SET type called 'ingredients', all of the foods used in a recipe (recipes are restricted to using a small number of specific ingredients).
recipes table
id (int)
recipeName (text)
ingredients (SET)
I have another table called 'food_types'. Each record has a 'name' field (e.g. 'fruit', 'poultry', 'spices', 'meat', ...) and a SET type called 'items' that holds the ingredients belonging to that food type. E.g. for the 'fruit' record the SET might be 'apple,orange,pear,banana'.
'
food_types table
id (int)
category (text)
items (SET)
My problem is how to create a MySQL query that selects all records from the 'recipes' table that has one or more item from the given 'items' set in the 'ingredients' set.
So in English my query might be:
'Select all recipes where the ingredients contains one or more fruit'
Can anyone suggest a good way to do this with a MySQL query?
NOTE: I appreciate that there are better ways to structure the tables for a recipes/ingredients problem but I am looking for a MySQL query solution that does this with SETs and the tables described above.
This is an inappropriate use of a SET field - you can't add a food_type without having to do a schema modification, always a good indicator that your structure is wrong. Use a join table to represent a many:many relationship. In this case, create a table ingredient_type that refers to both tables with ingredient_id and food_type_id. This also allows you to have ingredients with multiple types, like a SET does, but much more flexible.
ingredient (ingredient):
apple
orange
chicken
ginger
potato
food_type (type):
fruit
vegetable
meat
poultry
spice
ingredient_type (ingredient, type)
apple, fruit
orange, fruit
chicken, meat
chicken, poultry
ginger, vegetable
ginger, spice
potato, vegetable
recipe_ingredient (recipe, ingredient):
recipe1, chicken
recipe1, potato
recipe2, orange
recipe2, ginger
With that structure, you can do queries like this:
SELECT DISTINCT(recipe) FROM recipe_ingredient WHERE ingredient = 'orange';
That will show you all recipes that use oranges. Slightly more complex:
SELECT DISTINCT(recipe) FROM recipe_ingredient JOIN ingredient_type on recipe_ingredient.ingredient = ingredient_type.ingredient WHERE ingredient_type.type = 'fruit';
That will find all recipes that use any ingredient that is a fruit.
EDIT: On using SETs.
You can do this by making use of the binary nature of SET storage. Where there is an overlap between recipe ingredients and category members, the number of set bits in a bitwise AND (using the & operator) will be > 0:
SELECT DISTINCT recipeName FROM recipes JOIN food_types ON BIT_COUNT(food_types.items & recipes.ingredients) > 0 WHERE food_types.category = 'fruit';
This will only work if the ingredients and items set definitions are absolutely identical.
There are other problems with SETs:
they can only contain up to 64 items
field values are always in the same order as the set
most queries other than simple matches (for example to match on a single item within a set) are not indexed
unless definitions are identical, SETs can't be compared
There is nowhere to put additional metadata about each item
This approach defeats most of the point of using a relational database. This is textbook first-normal-form database stuff. The only purpose I can see for a structure like this is to use as an example of how not to do it.
I'm trying to create a site related to recipes where users can add custom meals, and within meals they can add foods and then list the ingredients of the foods. For example:
Meal = Dinner
Food = Hamburger
Ingredient = Tomato
Users can select foods from a pre-filled database of food items. They can also add ingredients from a central ingredients database. So the same food and ingredient ids can be used by any user of the site.
The challenge I have is ensuring the food_has_ingredients table is linked to the user somehow. In the diagram below the meal is associated to the user. Since the foods and ingredients ids can be re-used by multiple users I can't rely on those ids to determine which user adds ingredients to a food.
Take this example:
User 1 adds the food "hamburger" (id 10) to the meal_has_food table, then associates the ingredients "ketchup" (id 20) to the food_has_ingredients table.
User 2 adds the food "hamburger" (id 10) to the meal_has_food table, then associates the ingredients "pickles" (id 21) to the food_has_ingredients table.
If someone does a search get all ingredients from the meal hamburger (10) and limit it to one row, it would always give the first person's entry. So I've associated the meal id as a foreign key that way I can say get the ingredient for hamburger that belongs to meal id X.
I'd like to know if that makes sense or if there's a better way.
Here's a rough db schema:
My client is wanting to add new functionality to their site, they deal with actors/models and want to be able to create a credit history for each of the clients (much like a CV or Resume).
They have some criteria that I must adhere too, and because of this I cannot get my head around it.
A credit can be one of two things, it can be a 4 column credit, or a single column credit. The credit however must have a category, and these categories can be one of the following, TV, Film, Advert, Radio or something of their own making.
The second criteria is that the categories are orderable, so for example if they are entering an actors credit, he may have television and film credits, they may from time to time, to film above television.
The third criteria is that the credits with each category are orderable, so film1 credit does not have to be at the top.
Here is what I have devised so far.
CANDIDATES | CREDITS
---------- -------
candidate_id^ credit_id*
credit_category
credit_heading
credit_title
credit_role
credit_director
credit_position
candidates_candidate_id^^
^ - Primary Key
^^ - Foreign Key
My confusion comes from using this table structure there is no way to alter what order the categories are in, as if I added a credit_category_position.
For example if the user has a credit in the category film, and I want to add another, when I insert the data through my form, how do I keep the credit_category_position consistent for all that clients film entries?
I hope this makes sense to someone.
I've just glanced through your question and I'm not sure I exactly understand it, but one thing just pops to my mind:
why don't you have a many-to-many relationship between candidates and credits?
CANDIDATES | CREDITS
---------- -------
candidate_id^ credit_id*
credit_category
credit_heading
credit_title
credit_role
credit_director
CANDIDATE_CREDIT_REL
--------
rel_id*
credit_id^^
candidate_id^^
credit_position
A credit can be one of two things, it can be a 4 column credit, or a
single column credit.
I'm going to skip this one, because I don't understand it and because there isn't anything in your description that helps me with it. Feel free to edit your question.
The second criteria is that the categories are orderable
You need an additional table for that, because each candidate can have multiple credits in, say, film.
create table credit_category_order (
candidate_id integer not null,
credit_category <whatever> not null,
category_order float not null, -- lets you reorder by splitting the difference,
-- but there are other ways.
primary key (candidate_id, credit_category),
foreign key (candidate_id, credit_category)
references credits (candidate_id, credit_category)
);
The third criteria is that the credits with each category are
orderable
Add a column to credits. When you query, join credit_category_order, and ORDER BY credit_category_order.category_order, credits.credit_order.
Credit_Category
----------
id, category_name, details
Actor_cc_order //For each actor, have an entry for every category
----------
id, id_actor, id_cc, ord_number
Actor_credit
------------
id, id_actor, id_cc, credit_details
view of credits
SELECT a.*, b.category_name, c.ord_number FROM
Actor_credit a
JOIN Credit_category b ON b.id = a.id_cc
JOIN Actor_cc_order c ON c.id_actor = a.id_actor AND c.id_cc = b.id
SORT BY a.id_actor, c.order_number