How can I deal with Coupon database design? - mysql

I have create a simple e-commerce via Laravel, and have some questions about COUPON database design, what I need as follows:
1- I need a normal coupon for checkout (when the user adds all products that he needs then put the coupon this will discount from the total price ). I'm done with this:
Schema::create('coupons', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('code');
$table->enum('type', ['percentage', 'numeric']);
$table->integer('value');
$table->integer('count')->nullable();
$table->date('expired_at');
$table->timestamps();
});
2- I need to put a discount on products of a single category (to put a discount on the whole products that belong to the T-Shirt category).
How the design structure of this point?
3- I need to put a discount on a specific product or products that I want to put on it.
How the design structure of this point?
Could anyone pls help me with this? So confused!
Update:
products table:
id - name - price - quantity - category_id - brand_id - created_at
categories table
id - category_name - created_at
Orders table
id - status - user_id - address_id - coupon_id - created_at
order_product pivot table
order_id - product_id - quantity

Very interesting! I think may be a good approach should be a pivot table with a polymorphic relation. Somthing like this:
id - couponable_type - couponable_id - coupon_id - "whatever_data"
------------------------------------------------------------------
1 - category - 1 - 1 - ....
2 - order - 1 - 2 - ....
3 - product - 1 - 1 - ....

Since the COUPON ** has a relation by default with **PRODUCTS, it will be great to make some relationship, like so:
Does the product will have one coupon or many?
Does the coupon will belong to one product or many?
I think it's good to give product ability to use many **coupons and the coupon FOR SURE will belong to many products. In that case, you should add Many To Many Relationship between coupons and products.
The same thing with products and categories, the product should have many categories and the later should have or belong to many **products.
If you go with that approach you can achieve the Points 01 and 02 easily. You can then create coupons then attach them to specific product or products, or a product category.
To list the coupons of a product or category, you can do something like this:
$product_coupons = Product::findOrFail($id)->coupons;
$category_coupons = ProductCategory::findOrFail($id)->coupons;
In Blade you access the coupons like this:
#foreach($product->coupons as $coupon)
{{ $coupon->code }}
#endforeach
But #Nahuelsgk approach is a good one, advanced but it's clean, I think it's better to use polymorphic

Related

How to design a MySQL database for storing sell items

I have been trying to design a MySQL table o store the items of the store purchased by the costumers. I am stuck with what approach should I take to design a good table.
My first option is:
id
bill_id_fk
item1_id
item2_id
item3_id
item4_id
In this approach, I'll create may be 20 columns for items (assuming that a costumer may buy a maximum of 20 items at a time). ID of the items will be stored in the item(n)_id columns for that specific bill_id_fk.
My concern with this approach is that it would be difficult to query later for a specific item, like how many times a specific item has been sold.
My second opinion is:
id
bill_id_fk
item_id
1
1
23
2
1
29
3
2
23
In this approach, I'll just create 3 columns and for each item I'll create a rows with the bill_id_fk for a specific bill.
In this approach, it is easier to query for a counts of the sell of a specific item. But my concern is creating thousands and thousands of rows when the app will be used and how will that affect the performance of the app over time?
I'd like to have your opinion on what is the best practice for designing such database. Or is there any other approach should I take?
There's no chance that you will go with the first choice, the second is the best approach for your case.
it will not affect your performance if you indexed the right columns.
When it comes to items can add a column to your bills table that holds item numbers, for example:
bills (id - total_price - user_id - item_counts)
bill_items (id - bill_id - item_id - item_price)

Client Management DB Design - Track credit based purchases

My reservation system allows us to purchase credits for clients in terms of pre defined packages. I'm struggling with how I record and calculate available credits.
Let's say we're talking about a car wash service. A client can have multiple cars and can purchase the following services, 'Wash and Wax' and 'Detailing'.
Client 1 has two cars, Car A and Car B. He brings them both in and purchases:
Car A - 1 Wash and Wax
Car A - 1 Detailing
Car B - 10 Wash and Wax
Car B - 1 Detailing
This generates 4 rows in my Purchases table, one for each service purchased.
In my DB I have two related tables tracking purchases and reservations. Table 1 Purchases, Table 2 Reservations.
In Purchases I have the following fields of note:
id
client_id
car_id
service_id
credits_purchased
credits_scheduled
credits_used
cart_id
Then in my Reservation table I have the following fields of note:
id
client_id
car_id
service_id
reservation_date
completed_datetime
car_in_datetime
car_out_datetime
purchase_id
I track the credits available by updating the Purchases table fields credits_used and credits_on_schedule as events happen.
For example, when the client makes a reservation the system adds a new record in the Reservations table, once this happens the system also runs an update query and adds +1 to the related Purchases table credits_on_schedule. When the Reservation is updated to complete the system also updates the Purchases table and adds -1 to credits_on_schedule and +1 to credits used. Simple math between credits_purchased, credits_used, and credits_on_schedule derive what credits are available for a client to use.
I feel like this isn't a good way to track the credits. My question is what is a better implementation? Should I just track credits_purchased then use count queries on the Reservation table to calculate credits_used and credits_on_schedule? Should I be using a pivot table to track? I can't seem to wrap my head around what is the cleanest design.
It looks to me that the design is ok in general.
A reservation can only have one purchased related to it so purchase_id field is a foreign key in Reservation table.
Nevertheless, my advise to you is to create a log system of all these record updates.
As you mentioned, as events are fired the system updates the calculated fields.
What if for some reason the system fails at a certain point? You should be able to track these events.
One way to avoid this is, as you mentioned, calculate credit_used by a count query on all completed reservations.

Normalisation / 3rd Normal Form In Tables?

I'm creating a small news website and someone suggested that I look at normalisation, which I did and although I understand it I don't quite know if it's relevant to ALL tables in a database. For instance, I have this "Articles" table consisting of:
ID - 10001
Featured - 0 or 1
Category - Category Name
Title - Title For The Article
Article - This is the article.....
Photo Description - Photo to go with blog 10001
Photo Name - John Smith
Photo Link - www.johnsmith.com
Author - myname#gmail.com
Keywords - keyword, keyword, keyword, ...
Added - 2014-07-27 10:41
Views - 600
Is there anything wrong with leaving this table as it is or does it need to be converted to 3rd normal form?
EDIT:
What if I had:
**Authors**
ID
email
name
avatar
bio
website_link
facebook_link
twitter_link
**Articles**
ID - 10001
Featured - 0 or 1
Title - Title For The Article
Article - This is the article.....
Photo - 10001.jpg
Photo Description - Acts as alt tag
Photo Name - Crediting photographer
Photo Link - Link to credited photographer
Author - Author ID
Added - 2014-07-27 10:41
Views - 600
**categories**
ID
category
**article_categories**
ID
article_id
category_id
I'm still finding it hard to grasp the reasons why having so many tables is such a great thing though as now there are lots of joins that need to be made. Why isn't it easier to use php to say
select * from articles where category == $category
or
select * from articles where featured == 0
or
select * from articles where author == $author_id, etc, etc
Make seperate table for news,category,photo,authors and keywords
News
ID - 10001
Featured - 0 or 1
category_id - //belongs to category table
Title - Title For The Article
Article - This is the article.....
Added - 2014-07-27 10:41
Views - 600
categories
ID
name
Photos
ID
Photo Description - Photo to go with blog 10001
Photo Name - John Smith
Photo Link - www.johnsmith.com
Authors
ID
Author - myname#gmail.com
keywords
ID - 10001
Keywords - keyword
One news may have multiple photos so make one bridge table news_photos
news_photos
id
news_id
photo_id
Similarly One news may have multiple authors so make one bridge table news_authors
news_authors
id
news_id
author_id
Also One news may have multiple keywords so make one bridge table news_keywords
news_keywords
id
news_id
keyword_id
You duplicate much strings that describe category name. Much easier store integer value, instead category name each time.
I reccomend you at least to move the Category and the Keywords in separate tables.
This will help you to write more effective SQL queries when you will have to search Articles by keywords or category.
On the other hand, you will have to write more code to do this, referring to data insert form, etc., but it will be better and more clear and absolutely better.
You should then:
Create a Categories table (Id, Description)
Create a Keywords table (Article_Id, Keyword)
Substitute your Articles.Category field with Articles.Category_Id and remove your Articles.Keywords field.
How you choose to normalize largely depends on your business case, don't just normalise for the sake of it. That's why your design approach is crucial, starting off with an ERD (Top-Down approach) helps me in deciding how best to normalize.

MySQL Query Customers Who Have Not Bought

Background:
We are setting up a promotions system to give away free products to registered customers. We're trying to design a database which is flexible enough to handle multiple products, and giveaways. The requirements are that products may be given away on a drip basis on a first come basis to qualified customers.
Example:
Apple wants to give away 1000 ipads in March.
They want to give away maximum of 1 per hour.
They want to give it to customers who are in California or New York.
They want to limit how many free ipads a customer can get (limit 1 per 15 days).
Data Structure:
Products - 1 entry per unique product. e.g. Apple iPad
ProductGiveAways
ProductID: AppleIpad
Quantity:1000
StartDate: 03/01/2014
End Date 03/31/2014
CustomerState: California,NewYork
PurchaseLimitDays: 15
Problem:
With the above structure we are able to do a query against our customers table and find out which are qualified for the promotion.
What I cannot figure out is the best way to:
Query customers in California or New York (is this a good use case for a join and another table?)
When a customer logs in to see what free items are not available to him, how can I exclude the Apple iPad if the customer has already gotten this freebie?
In other words:
Say amazon.com wants to show me DVDs which I have not already bought. What is the proper way to query that?
Is the right approach to first get a list of previously bought products and then Query with a NOT clause?
I'm assuming you'll have a table for what has been given away. In this table I would include a column for recipient id which can map back to the customer table. You can then create queries to find eligible recipients by searching for customers who have not met disqualifying conditions.
select customerid
from customer
where customerid not in (
select recipientid
from givenaway
where ..... and ....
)
Because there's not a definitive data structure defined, I'm going to use the following which you can tailor to whatever data structure you have designed yourself:
Product
ProductId - INTEGER (IDENTITY and PRIMARY KEY)
ProductName - VARCHAR
States
StateId - INTEGER (IDENTITY and PRIMARY KEY)
StateName - VARCHAR
Customer
CustomerId - INTEGER (IDENTITY and PRIMARY KEY)
StateId - INTEGER (FOREIGN KEY)
Promotion
PromotionId - INTEGER (IDENTITY and PRIMARY KEY)
ProductId - INTEGER (FOREIGN KEY)
Quantity - INTEGER
StartDate - DATETIME
End Date - DATETIME
PurchaseLimitDays - INTEGER
PromotionState
PromotionId - INTEGER (FOREIGN KEY)
StateId - INTEGER (FOREIGN KEY)
So in answer to your questions:
Query customers in California or New York (is this a good use case for a join and another table?)
Personally I would join to a centralized state table (PromotionState) in my above example, I'm sure there's a better way but you could do a condition such as:
WHERE
(SELECT COUNT * FROM PromotionState x WHERE x.PromotionId = p.PromotionId) = 0
OR NOT(ps.PromotionId IS NULL)
Alternatively you could do a GROUP BY and HAVING, using all the other columns as the items to GROUP BY and something like HAVING COUNT * = 0 OR HAVING SUM CASE WHEN (Conditions met) THEN 1 ELSE 0 END = 0
When a customer logs in to see what free items are not available to him, how can I exclude the Apple iPad if the customer has already gotten this freebie?
Say amazon.com wants to show me DVDs which I have not already bought. What is the proper way to query that?
As I've said you could use GROUP BY and HAVING to determine whether an item has been previously "won" by either using COUNT or SUM
Is the right approach to first get a list of previously bought products and then Query with a NOT clause?
There are probably better ways, sub queries can get very heavy and sluggish, I'd recommend trying some of the above techniques and then using a profiler to hopefully make it more efficient.
Some database design
First, when you set the CustomerState to California,NewYork you are violating the First Normal Form of database design.
So let's reorganize your domain model.
State - 1 Entry per unique state
...
Customer - 1 Entry per unique customer
StateId: (California|NewYork|...)
...
Product - 1 Entry per unique product
...
ProductGiveAways - Many entries per product
ProductID
Quantity
StartDate
End Date
PurchaseLimitDays
...
ProductGiveAways_State
ProductGiveAwaysId
StateId
...
Customer_Product - 1 Entry per bought product by customer
CustomerId
ProductId
PurchaseDate
...
Technical issues
When you want to query custoners in California or New York, all you have to do now is :
// This is just an example, you have to change the 'California', 'New York' with their ids
SELECT * FROM Customer WHERE StateId IN ('California', 'New York')
When a customer logs in to see what free items are available to him :
// It's not an accurate sql, just an example
SELECT Product.*
FROM Product
JOIN ProductGiveAways ON ProductId
JOIN ProductGiveAways_State ON ProductGiveAwaysId
WHERE ProductId NOT IN (
SELECT ProductId FROM Customer_Product JOIN ProductGiveAways ON ProductId
WHERE CustomerId = /* the customer id */
AND ( TO_DAYS(now()) - TO_DAYS(PurchaseDate) ) < PurchaseLimitDays
)
AND StateId = /* customer StateId */
AND StartDate < now() < End Date // Elligible ProductGiveAways
For Laravel We Use Something Like this, i hope you can relate to this query or you can use online laravel query converter for using it in mysql ( orator )
$user_id = auth()->user()->id;
Product::where('status', 'active')->whereNotIn('id', function($query) use ($user_id) { $query->select('product_id')->from(new OrderProduct->getTable())->where('user_id', $user_id)->where('status', 'delivered'); });

MYSQL Query matching column names to rows in another table

I have two tables in MYSQL, one contain a list of options a customer can pick from and the other has the cost of the options. I would like a view that returns the cost of each customers option set. For my example I'll use the analogy of buying a new car.
Customers table
customer - sunroof - mag_wheels - spoiler
--------------------------------------------------
John - true - true - false
Steve - false - true - false
Lucy - false - false - false
Options table
option - price
-----------------
sunroof - 100
mag_wheels - 150
spoiler - 75
Desired results
customer - cost
-----------------
John - 250
Steve - 150
Lucy - 0
or this would do, as I good easily multiply selected by price and then group by customer
customer - option - selected - price
------------------------------------------
John - sunroof - true - 100
John - mag_wheels - true - 150
John - spoiler - false - 75
Steve - sunroof - false - 100
Steve - mag_wheels - true - 150
Steve - spoiler - false - 75
Lucy - sunroof - false - 100
Lucy - mag_wheels - false - 150
Lucy - spoiler - false - 75
I've been puzzling over this for hours now and I can't even figure out where to start, a join seems out of the question as there are no common elements to match. I wonder if using UNION is the answer but I can't figure out how to combine row values with column headings.
If anyone could point me in the right direction I'd be ever so grateful, double points if you come up with a solution that dynamically picks up the different options so I could add more in the future without rewriting the query.
Many thanks in advance.
The reason I wanted to have all the options as a single row is I was hoping to use Access to make a form for picking the options and I couldn't figure out how a single form could create multiple rows.
This is a horrible data layout. You should have an association table, with one row per customer and option.
But, you can do it:
select c.customer, sum(o.cost) as cost
from customers c left outer join
options o
on (c.sunroof = true and o.option = 'sunroof' or
c.mag_wheels = true and o.option = 'mag_wheels' or
c.spoiler = true and o.option = 'spoiler'
)
group by c.customer;
EDIT:
You do not want all options in a single record. Instead, you need an association table:
create table customer_options (
customer_optionid unsigned auto_increment,
customer varchar(255) references customer(name),
option varchar(255) references option(option)
);
Actually you should really have integer primary keys for all the tables, and use them for the foreign key references. If you need data in the output in the question, then just write a query to return it in that format.
Looking at the table structure, even I think it will not be possible to write joins because as you mentioned, the table structure doesn't have a relation between them.
I am assume you have just started the project, so it's time you first re-visit your DB structure and correct it.
Ideally, you should have a customer table with a customer id.
Then you should have products table with product id.
One table which will have data on what products customers have purchased - something like customer_products. This will be a one to many relation. So customer 1 can have product 1,3 and 5. Which would means in customer_product there will be three entries.
And then when you want to do a sum total, you can first join the table customer, product based on customer_product and then do a sum of the price also to get the total amount for individual customer.
Bad design. You must a have a customers table like this:
custumer_id
customer_name
other fileds...
On the other hand you should have an accesories table, where you usually describe each item-
accesory_id
accesory_name
supplier_id
country_of_origin
other stuff
Also an accesory_price table, where prices are added due the fact that prices change.
accesory_id
price
active_price
date_price_added
An finally you should relate all in a customer_accesory table:
customer_id
accesory_id
By having this, you can join tables and select both customer basket size and customer preferences of accesories. Basket size, or the amount purchased by each customer can be summarized SUM, AVG, COUNT or you can pivot data using GROUP_CONCAT in order to generate high quality reports.