I am trying to make a payment/transaction database for a pretend online store (just trying to learn). 1 payment can purchase 1 to many items. 1 payment can only have 1 payment method.
To keep the example simple, there are 2 payment methods, PayPal and Bitcoin. Each payment method has different attributes, hence they must be different tables.
I have my payments table which tells me what transaction bought what item/s. However, you can see that if the paypal_idis NULL then the bitcoin_id column is not. This means there are a lot of NULL's which I think is not a good design. How can I have good design in a case like this?
paypal table
paypal_id | txn_id | buyer_email | amount
1 | 3sd7fgudf23sdf34 | john#mail.com | 50.00
2 | 45shfik45345fg2s | mike#gmail.com | 100.00
bitcoin table
bitcoin_id | txn_id | amount
1 | 34327yhujndreygdiusfsdf324 | 0.19203
2 | sdfgurjibdsfhubhsdfinjo332 | 0.04123
items table
item_id | item name | price
1 | ball | 50.00
2 | shirt | 50.00
payments
payment_id | item_id | paypal_id | bitcoin_id
1 | 1 | 1 | NULL
2 | 1 | 2 | NULL
3 | 2 | 2 | NULL
4 | 1 | NULL | 1
5 | 1 | NULL | 1
6 | 1 | NULL | 2
Your design is fine. But you might want to consider an alternative where you have a payment_transactions table and then related tables that use the same primary key:
create table payment_transactions (
payments_transactions_id int auto_increment primary key,
type varchar(255),
payment_datetime datetime, -- probably common to all payment methods
. . . other columns if you like,
unique (type, payments_transactions_id) -- this will be used for foreign key references
);
create table bitcoin_payments (
bitcoin_payments_transaction_id int primary key,
type varchar(255) generated always as ('bitcoin'),
. . . , -- columns specific to bitcoins
foreign key (type, bitcoin_payments_transaction_id) references payments (type, payments_transactions_id)
);
-- similar for paypal
Then your payments table can have a foreign key to payments.
This handles much of the data modeling issue;
You have proper declared foreign key relationships.
Only one column is needed in payments regardless of the number of types.
You can easily introduce new types.
This guarantees one type per payment (via the inclusion of type in the foreign key reference).
One downside is that you need to insert each transaction twice. First into the payment_transactions table and then into the proper table.
Payments are actually more complicated than you present. A more realistic data model would handle:
Transaction status.
Retries.
Partial payments.
Once you get the basic structure down, you might want to try adding in new capabilities.
Related
I have this table structure and simple relationships:
and sample data in the table:
Company
Company names are unique and should not be repeated:
+------------+---------------+
| Company_ID | Company_name |
+------------+---------------+
| 1 | Company_name1 |
+------------+---------------+
| 2 | Company_name2 |
+------------+---------------+
Location
(Locations should be assigned to a specific company):
+-------------+------------+-------------------------+
| Location_ID | Company_ID | Location_name |
+-------------+------------+-------------------------+
| 1 | 1 | Company1_Location_name1 |
+-------------+------------+-------------------------+
| 2 | 1 | Company1_Location_name2 |
+-------------+------------+-------------------------+
| 3 | 2 | Company2_Location_name1 |
+-------------+------------+-------------------------+
| 4 | 2 | Company2_Location_name2 |
+-------------+------------+-------------------------+
Data
The data in the table should depend on the selected company, and the locations should be only those that occur in the company:
+---------+-------------+------------+------------+------+
| Data_ID | Location_ID | Company_ID | data_value | date |
+---------+-------------+------------+------------+------+
| 1 | 1 | 1 | 5 | date |
+---------+-------------+------------+------------+------+
| 2 | 2 | 1 | 2 | date |
+---------+-------------+------------+------------+------+
| 3 | 3 | 2 | 3 | date |
+---------+-------------+------------+------------+------+
| 4 | 2 | 1 | 1 | date |
+---------+-------------+------------+------------+------+
| 5 | 4 | 2 | 6 | date |
+---------+-------------+------------+------------+------+
| 6 | 4 | 2 | 7 | date |
+---------+-------------+------------+------------+------+
The main dependencies that should be met:
Company names should be unique and attempts to add the same company should be blocked
Location names should be assigned to a specific company, but they may repeat and a location may appear in several companies but have a different Location_ID
Adding values to the date table should depend on:
company (we choose a specific company for which we add values)
locations (locations must depend on company)
For example:
When adding values for a company with Company_ID = 1, I should only be able to add Location_ID that occur under that company.
If I want to add a value in the data table for Company_name1 then the only available values for the Location_ID column in the data table, should be: Company1_Location_name1 and Company1_Location_name2 and I can't have values there from another company (i.e. Company2_Location_name1 and Company2_Location_name2)
At the moment it works badly:
when adding values to the data table I can select a company, but then I have locations available and I can add values that do not make sense - for Company_name1 I can add a location from Company_name2 but it should be blocked.
How can I solve such a problem? Add some additional table which will be responsible for particular pairing?
Depends what database you use.
A simpler way would be to just create a unique constraint on the table field, this will also enforce it for updates too and remove the need for a trigger. Just do:
Example for MSSQL:
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [Company_name] UNIQUE NONCLUSTERED
(
[CompanyID], [Company_name]
)
and then you'll be in business. You will be not able to add 2 company with the same name.
You can find another examples here : Trigger to prevent Insertion for duplicate data of two columns
This is exacly what you are looking for :)
#EDIT 1
OK so if you want example for MARIADB here we go :
Create unique Contraint - Using a CREATE TABLE statement
The syntax for creating a unique constraint using a CREATE TABLE statement in MariaDB is:
CREATE TABLE table_name
(
column1 datatype [ NULL | NOT NULL ],
column2 datatype [ NULL | NOT NULL ],
...
CONSTRAINT constraint_name UNIQUE (uc_col1, uc_col2, ... uc_col_n)
);
table_name
The name of the table that you wish to create.
column1, column2
The columns that you wish to create in the table.
constraint_name
The name of the unique constraint.
uc_col1, uc_col2, ... uc_col_n
The columns that make up the unique constraint.
In your example :
CREATE TABLE Company
( Company_ID INT(11) PRIMARY KEY AUTO_INCREMENT,
Company_name VARCHAR(250) NOT NULL,
CONSTRAINT company_name_unique UNIQUE (Company_name)
);
In this example, we've created a unique constraint on the Company table called company_name_unique. It consists of only one field - the Company_name field.
Suppose we have two tables
A table called people with people linked to a bank account balances
| id | name | account_id |
--------------------------
| 1 | bob | 11 |
--------------------------
| 2 | sam | 22
A table called accounts with bank account balances
| id | value |
--------------
| 11 | 200 |
--------------
| 22 | 500 |
In order to link the two tables you can do
SELECT a.value as account_balance
FROM people p
WHERE p.name="bob"
LEFT JOIN accounts a ON p.account_id = a.id`
This would return
id => 1
name => bob
account_balance => 200
That's cool - but I am wondering if there is a more implicit way to do this via SQL linkage (foreign keys or otherwise). Can we in MySQL add links in some other way so that when we do a SELECT, it already knows to return value instead of **account_id **?
I'm asking this because I am creating a system where my users can create lookup tables and link them to other tables - but it must be do-able without any programming. The only other way I can think of is to set the name of account_id for example to accounts.value and treat that as a foreign key when doing a SELECT.
I would have to get the column structure and analyze and then determine that there is a foreign key and then return the appropriate foreign column by looking at the column name.
I have a forum, where users can subscribe to receiving notifications when new threads are created. They can subscribe independently for each category in the forum. It should also be possible to subscribe to all categories, so that they do not have to specify each category if they want to receive notifications for all categories. (And also so that they will automatically receive notifications for any new categories that might be created in the future.) It is also possible to subscribe at different levels. For example one can subscribe to notifications o new threads only, or, in addition to the notifications, automatically subscribe to the new threads, with the effect of receiving notifications on new posts in the threads as well.
My first idea was to create a table containing UserId, CategoryId, and LevelId, having (UserId, CategoryId) as PRIMARY KEY. If CategoryId is NULL, it means all categories. This would allow for a base subscription to all categories, and then one could change the subscription level independently for specific categories.
+--------+------------+---------+
| UserId | CategoryId | LevelId |
+--------+------------+---------+
| 1 | 1 | 2 |
| 1 | 2 | 2 |
| 1 | NULL | 1 |
| 2 | NULL | 2 |
+--------+------------+---------+
The problem is that columns part of a PRIMARY KEY does not allow NULL values. I could of course use a separate value, such as -1 as "all categories", but this would break the FOREIGN KEY on CategoryId. Another idea is to use a UNIQUE KEY, which allows NULL values, instead of a PRIMARY KEY, but then there could be several rows with CategoryId = NULL for each user, since NULL != NULL in MySQL.
A different idea is to create a table using only UserId as PRIMARY KEY, and Categories as a bitmask for which categories the user is subscribing to. If Categories is NULL, this could mean all categories. This approach does not allow for a base subscription, overloaded by individual subscriptions. Also, the same subscription level is required for all categories, and there cannot be a FOREIGN KEY on Categories.
+--------+------------+---------+
| UserId | Categories | LevelId |
+--------+------------+---------+
| 1 | 3 | 2 |
| 2 | NULL | 1 |
+--------+------------+---------+
Are there any better suggestions for how to solve this issue?
I want to build a "check in" service like FourSquare or Untappd.
How do I design a suitable database schema for storing check-ins?
For example, suppose I'm developing "CheeseSquare" to help people keep track of the delicious cheeses they've tried.
The table for the items into which one can check in is fairly simple and would look like
+----+---------+---------+-------------+--------+
| ID | Name | Country | Style | Colour |
+----+---------+---------+-------------+--------+
| 1 | Brie | France | Soft | White |
| 2 | Cheddar | UK | Traditional | Yellow |
+----+---------+---------+-------------+--------+
I would also have a table for the users, say
+-----+------+---------------+----------------+
| ID | Name | Twitter Token | Facebook Token |
+-----+------+---------------+----------------+
| 345 | Anne | qwerty | poiuyt |
| 678 | Bob | asdfg | mnbvc |
+-----+------+---------------+----------------+
What's the best way of recording that a user has checked in to a particular cheese?
For example, I want to record how many French cheeses Anne has checked-in. Which cheeses Bob has checked into etc. If Cersei has eaten Camembert more than 5 times etc.
Am I best putting this information in the user's table? E.g.
+-----+------+------+--------+------+------+---------+---------+
| ID | Name | Blue | Yellow | Soft | Brie | Cheddar | Stilton |
+-----+------+------+--------+------+------+---------+---------+
| 345 | Anne | 1 | 0 | 2 | 1 | 0 | 5 |
| 678 | Bob | 3 | 1 | 1 | 1 | 1 | 2 |
+-----+------+------+--------+------+------+---------+---------+
That looks rather ungainly and hard to maintain. So should I have separate tables for recordings check in?
No, don't put it into the users table. That information is better stored in a join table which represents a many-to-many relationship between users and cheeses.
The join table (we'll call cheeses_users) must have at least two columns (user_ID, cheese_ID), but a third (a timestamp) would be useful too. If you default the timestamp column to CURRENT_TIMESTAMP, you need only insert the user_ID, cheese_ID into the table to log a checkin.
cheeses (ID) ⇒ (cheese_ID) cheeses_users (user_ID) ⇐ users (ID)
Created as:
CREATE TABLE cheeses_users
cheese_ID INT NOT NULL,
user_ID INT NOT NULL,
-- timestamp defaults to current time
checkin_time DATETIME DEFAULT CURRENT_TIMESTAMP,
-- (add any other column *specific to* this checkin (user+cheese+time))
--The primary key is the combination of all 3
-- It becomes impossible for the same user to log the same cheese
-- at the same second in time...
PRIMARY KEY (cheese_ID, user_ID, checkin_time),
-- FOREIGN KEYs to your other tables
FOREIGN KEY (cheese_ID) REFERENCES cheeses (ID),
FOREIGN KEY (user_ID) REFERENCES users (ID),
) ENGINE=InnoDB; -- InnoDB is necessary for the FK's to be honored and useful
To log a checkin for Bob & Cheddar, insert with:
INSERT INTO cheeses_users (cheese_ID, user_ID) VALUES (2, 678);
To query them, you join through this table. For example, to see the number of each cheese type for each user, you might use:
SELECT
u.Name AS username,
c.Name AS cheesename,
COUNT(*) AS num_checkins
FROM
users u
JOIN cheeses_users cu ON u.ID = cu.user_ID
JOIN cheeses c ON cu.cheese_ID = c.ID
GROUP BY
u.Name,
c.Name
To get the 5 most recent checkins for a given user, something like:
SELECT
c.Name AS cheesename,
cu.checkin_time
FROM
cheeses_users cu
JOIN cheeses c ON cu.cheese_ID = c.ID
WHERE
-- Limit to Anne's checkins...
cu.user_ID = 345
ORDER BY checkin_time DESC
LIMIT 5
Let's define more clearly, so you can tell me if I'm wrong:
Cheese instances exist and aren't divisible ("Cheddar/UK/Traditional/Yellow" is a valid checkinable cheese, but "Cheddar" isn't, nor is "Yellow" or "Cheddar/France/...)
Users check into a single cheese instance at a given time
Users can re-check into the same cheese instance at a later date.
If this is the case, then to store fully normalized data, and to be able to retrieve that data's history, you need a third relational table linking the two existing tables.
+-----+------------+---------------------+
| uid | cheese_id | timestamp |
+----+-------------+---------------------+
| 345 | 1 | 2014-05-04 19:04:38 |
| 345 | 2 | 2014-05-08 19:04:38 |
| 678 | 1 | 2014-05-09 19:04:38 |
+-----+------------+---------------------+
etc. You can add extra columns to correspond to the cheese data, but strictly speaking you don't need to.
By putting all this in a third table, you potentially improve both performance and flexibility. You can always reconstruct the additions to the users table you mooted, using aggregate queries.
If you really decide you don't need the timestamps, then you'd replace them with basically the equivalent of a COUNT(*) field:
+-----+------------+--------------+
| uid | cheese_id | num_checkins |
+----+-------------+--------------+
| 345 | 1 | 15 |
| 345 | 2 | 3 |
| 678 | 1 | 8 |
+-----+------------+--------------+
That would dramatically reduce the size of your joining table, although obviously there's less of a "paper trail", should you need to reconstruct your data (and possibly say to a user "oh, yeah, we forgot to record your checkin on such-a-date.")
The entities 'User' and 'Cheese' have a many-to-many relationship. A user can have multiple cheeses he checked into, and a cheese can have multiple people that checked into it.
The only right way to design this in a relational database is to store it into a separate table. There are many reasons why storing it into the user table for instance, is a very bad idea. Read up on normalizing databases for more info on this.
Your table should look something like this:
CheckIns(CheeseId, UserId, (etc...))
Other useful columns might include date or rating, or whatever you want to store about a particular relationship between a user and a cheese.
I have a list of tables i.e. student, teacher, staff, dept. and so on and each of these tables have comments specific to them. Now one record in a table can have one or many comments that shows it's a one to many relation from any table to comments table. I don't know what the best way is to relate comments table to each of these. If I put foreign key from comments table to each of these tables, it will be like 40-50 fields depending on no. of tables. Secondly if I add foreign key from each of these tables to remarks table, it will be like repeating whole row just for the second remarks foreign key? Similarly if I use just one field in each table as comments, I will be actually storing rows in just one text field. Any suggestions on how to achieve efficient solution?
Lets assume that your tables (student, teacher, staff, dept) all have a int primary key named Id.
For your comments table you could create a table.
Id int
CommentType enum (student, teacher, staff, dept)
LinkId int
Comment
A row in Comments might look like this
1,'Student',347,'text'
As this is a many-to-many relation, you migth might want to have a look at using an associative table.
Using your example, it might look something like this:
Your tables that can have comments:
+----------+------------+
| student | student_id |
+----------+------------+
| Steve | 12 |
| Larry | 57 |
| Sunshine | 88 |
+----------+------------+
+--------+---------+
| dept | dept_id |
+--------+---------+
| Math | 2 |
| P.E. | 5 |
| Drama | 12 |
+--------+---------+
Then you need to keep track of the actual comments:
+-----------------------+------------+
| comment | comment_id |
+-----------------------+------------+
| I love Math! | 3 |
| Larry is my hero... | 5 |
| Sunshine <3 Me! | 6 |
+-----------------------+------------+
Now, you need an association between these tables, this is where your associative table comes into play. You now associate what student or dept has what comments, like this:
+------------+------------+
| student_id | comment_id |
+------------+------------+
| 57 | 5 |
| 57 | 6 |
+------------+------------+
+---------+------------+
| dept_id | comment_id |
+---------+------------+
| 2 | 3 |
+---------+------------+
This is both effective and elegant. Give it a shot!
(And to save you another question perhaps)
You could of course use just one association table if you are concerned about having so many association tables, but I would advice against it since it is not as neat and removes some possibilities for referential integrity checks that you can have with the first solution:
+-----------+------------+---------+
| entity_id | comment_id | entity |
+-----------+------------+---------+
| 57 | 5 | student |
| 57 | 6 | student |
| 2 | 3 | dept |
+-----------+------------+---------+
(Which in turn should prompt you to add a lookup table for those entities... but let's not go there)
You could use intermediate "many-to-many" tables. Each base table (student, professor, etc.) would have an alter ego storing one foreign key to the base table (e.g. student_id) and one foreign key to the commments table. You practically double your number of tables but you don't need to modify existing tables and you get full flexibility.
If you want to keep the foreign-key constraint, you need to have a table that handles the mapping for each and every table that will have comment-childs.
Meaning, Comment will have a primary key, with a foreign key constraint to each and every table that handles the mapping.
then, in the mapping-table, you have comment_id and ????_id with a foreign key constraint to the approriate table.
Your comments table could look as follows:
CommentID (int) - Primary Key
TableName (varchar(250)) - Table the comment is related to
RecordID (int) - the ID of the record in the table referred to
Comment (text) - the actual comment
You could of course add optional fields like a timestamp, which would let you select comments in the order they were entered.
Now you can use this table to store comments for any table, and you can select them by filtering on table name and record ID.
is a student or teacher or staff not just a type of person..
so you have a person and a person can have many comments? so you have a personscomments table with a relation to that person and why have a remarks table..is a remark not just a type of comment..
its hard to see without a more in-depth schema
My 50 cents: Zoredache solution is definitely good, but I discourage usage of enums; they are not very smart in mysql: if you specify an unknown value, the error is represented with an empty string - even if some default is specified. Also, it's crazily long to ALTER if you want to modify or add a new type. unsigned tinyint should be enough for most of your needs ...