I am working on a game where users can buy virtual items with virtual currency, and also give those items away to other users. We need to be able to track the history of the item (who bought it, who it was gifted to, etc) and the current owner. I'm struggling with some of the table design.
My thinking is to have this (with table design simplified slightly and constraints/keys/etc omitted for space):
TABLE order
id INT NOT NULL AUTO_INCREMENT,
buyer_id INT NOT NULL,
paid INT UNSIGNED NOT NULL,
owned_item_id INT NOT NULL,
------------------------------------------------------
TABLE owned_item
id INT NOT NULL AUTO_INCREMENT,
store_product_id INT NOT NULL,
owner_id INT NOT NULL,
------------------------------------------------------
TABLE gift
id INT NOT NULL,
giver_id INT NOT NULL,
receiver_id INT NOT NULL,
owned_item_id INT NOT NULL,
With the idea being that when an item is purchased, both an order and an owned_item are created for that item. If the item is gifted, then a new entry in the gift table is created and the owner_id field is updated.
This approach makes it very easy to determine who currently owns a given item. However, it has redundant data and leaves room for problems with data integrity. If the 'owner_id' field was set incorrectly, we might end up with records that an item that was purchased by A and gifted to B but is now inexplicably owned by C.
How should such a structure be normalized? I've considered eliminating the owned_item table:
TABLE order
id INT NOT NULL AUTO_INCREMENT,
buyer_id INT NOT NULL,
paid INT UNSIGNED NOT NULL,
product_id INT NOT NULL,
------------------------------------------------------
TABLE gift
id INT NOT NULL,
giver_id INT NOT NULL,
receiver_id INT NOT NULL,
order_id INT NOT NULL,
I don't like this solution because finding all of the items that some person owns becomes a very complex operation (find every gift record X where the recipient is A and no later gift record exists for the same order, combined with find every order record Y where the buyer is A and no gift records exist for that order) but if that's the correct solution then I'll make do.
Something like this would be a good 3nf schema for what you're looking to do.
Entities and transactions are kept generic in order to allow all entity / transaction relationships to be simplified.
-- An Entity is a person or business, as a party in a transaction
CREATE TABLE entity (
id INT NOT NULL AUTO_INCREMENT,
entity_type ENUM('store', 'person') NOT NULL
name VARCHAR NOT NULL
);
-- Unique item types - items are each an instance of an item-type
-- e.g. the item "Steve's broom" may be an item of type "broom"
CREATE TABLE item_type (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR NOT NULL
);
-- Non-unique item, instance of an item-type owned by an entity
CREATE TABLE item (
id INT NOT NULL AUTO_INCREMENT,
-- optionally include owner_id as a quick-reference to the current owner of the item
owner_id INT NULL REFERENCES entity (id),
-- FK to unique item types, e.g. "broom"
item_type_id INT NOT NULL REFERENCES item_type (id),
-- possible description, e.g. "Steve's broom"
description VARCHAR NOT NULL
);
-- A transaction is a sale, gift, or other method of transferrence
-- of an item between entities. Transaction is a bad name, because
-- it's a reserved word. That's why it's encased in ticks.
-- You'd probably be better off choosing a different generic name
CREATE TABLE `transaction` (
id
-- transaction_type can be NULL in cases of origination with entity
transaction_type ENUM('sale', 'gift') NULL,
-- NULL in cases of origination with an entity
from_entity_id INT NULL REFERENCES entity (id),
to_entity_id INT NOT NULL REFERENCES entity (id),
-- amount can be 0 in cases of gifts
amount DECIMAL(9,2) UNSIGNED NOT NULL DEFAULT 0
);
A "gift" transaction would have an amount of 0 (or NULL, if you wanted to make it nullable).
An "origin" transaction (e.g. something was made, or found) would have no transaction_type, and 0 amount.
To know who the current owner of an item is, use a view that retrieves the last "to_entity_id" for that item in the transaction table, e.g.:
SELECT
e.name
FROM entity AS e
INNER JOIN `transaction` AS t ON e.id = t.to_entity_id
INNER JOIN
(SELECT MAX(id) AS id
FROM `transaction`
WHERE item_id = 5) AS tx ON tx.id = t.id
Alternatively, you could also store the owner_id in the item table (see note above in the schema). This would be a little redundant, and would require updating that table on each transaction, but would save a lot of expensive queries to know who owns what.
Related
lets say I have an account object in my application, which currently represented as:
CREATE TABLE Account (
accountId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
PRIMARY KEY (accountId)
);
Now, Account object need to also have Solution field...and Status have 4 different possible values:
Solution1, Solution2, Solution3, Solution4
What would be the right way to represent it in the database?
Account can have few statuses, and status can have few accounts...
So at first I thought create in the db table of Solutions and than have another table to hold the relationship, but its seems too complicated for a field that have only 4 possible values...
Create a junction table to represent the relationships between accounts and solutions:
CREATE TABLE account_solution (
accountId int NOT NULL,
solutionId int NOT NULL
PRIMARY KEY (accountId, solutionId)
)
For your solution table, since there are only 4 values, you might be able to take advantage of MySQL's enum type, e.g.
CREATE TABLE solution
solutionId int NOT NULL PRIMARY KEY,
status ENUM('Solution1', 'Solution2', 'Solution3', 'Solution4')
);
You can use set Mysql SET type
CREATE TABLE Account (
accountId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
status set('Solution1','Solution2','Solution3','Solution4') NOT NULL,
PRIMARY KEY (accountId)
);
And if you want to select a specific status
SELECT *
FROM `Account`
WHERE FIND_IN_SET( 'Solution2', `status` ) >0
So first up I'm not sure if this is a double post or not because I don't know how the exact approach or feature is called and if it even exist.
I know that MySQL has a feature called joins
My plan is to link two MySQL tables in relation 1:n one is t_user the other one t_event.
t_user:
CREATE TABLE t_user (
uId INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(25) NOT NULL,
...
)
t_event:
CREATE TABLE t_event (
eId INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(40) NOT NULL,
date DATETIME NOT NULL,
members ???,
...
)
I want the users to "subscribe" to the events and get stored in the members column as a list (?). This would be no problem if only one user would subscribe to one event. But I have no idea how to setup the t_event table to store more than one user and how to query for all the events a user has "subscribed" for.
This is usually done via third table:
CREATE TABLE t_eventsusers (
eId INT(6),
uId INT(6)
)
I am having an issue that involves a differentiation between whether a status update belongs to a user or a company. Let me explain: Individuals can post statuses as a user or as a company. A user can be a member/owner of a company and switch to a company in the dashboard much like Facebook. The problem is: how will we set in the database whether a status was posted by a company or by a user?
My solution was to have a company column that was a Boolean variable and when we query for each status to display we check if it was from a company. If so then we grab the company_id and look up the name and other relevant information in the database and display it on the site. Does this sound like the right approach? Additionally, does the schema below look correct?
**Company**
id int(11)
name varchar (255)
**Company_members**
company_id int(11) FK_Company
user_id int(11) FK_Users
owner BOOLEAN
**Users**
id int(11)
name varchar (255)
**Status**
id
date DATETIME
user_id FK_Users
company_id FK_Company
company BOOLEAN
Your schema looks good, but the company boolean in the Status table seems redundant. You can just set the company_id to NULL when the status is for an individual's account, and then query SELECT * from Status WHERE company_id IS NULL to get individuals' statuses, and SELECT * FROM Status WHERE company_id IS NOT NULL to get companies' statuses.
Use the Party Model. You need to use table inheritance. Single Table inheritance is simpler and faster but may use nulls.
create table party_type (
id int primary key,
description text not null unique
);
insert into party_type values
(1, 'Individual'),
(2, 'Organization');
create table party (
id serial primary key,
type int not null references party_type(id),
name text not null
);
create table status_update(
id serial primary key,
date datetime,
party_id not null references party(id)
);
(syntax is postgres but you should be able to translate to mysql easily)
I'm working on an application which tracks prices for certain items.
Each price has a reference to an item, a business that sells that item, and the location the item is being sold at. Now, normally, this would do just fine:
CREATE TABLE `price` (
`priceId` INT UNSIGNED NOT NULL AUTO_INCREMENT, -- PK
`businessId` INT UNSIGNED NOT NULL,
`itemId` INT UNSIGNED NOT NULL,
`locationId` INT UNSIGNED NOT NULL,
`figure` DECIMAL(19,2) UNSIGNED NOT NULL,
-- ...
)
But I have the following problem:
The application logic is such that one item at one business at one location can have multiple prices (at this point it's not really important why), and one of those prices can be an official price - an item doesn't have to have an official price, but if it does, there can be only one.
The question is; how to model this to ensure data integrity?
My initial idea was to create an additional table:
CREATE TABLE `official_price` (
`priceId` INT UNSIGNED NOT NULL -- PK + FK (references price.priceId),
-- ...
)
This table would hold priceId:s for prices that are official, and the PK/UNIQUE constraint would take care of the 'one-or-none' constraint.
This seems like a workable solution, but I'm still wondering if there's a better way to handle this situation?
You can use this dirty hack:
add a field is_official to price table, null as a value is possible in it
create an unique composite index priceId + is_official
for the official prices put 1 to is_official
for not official left it to be null
You could make the price table hold only official prices (with the figure possibly null), put a unique constraint on (businessId, itemId, locationId), and add another table of auxiliary prices referencing priceId.
I'm trying to create a database that stores the students grade for each homework assignment I want to be able to store the grade and how many times that student got a certin grade for example a student got an A+ 30 times, for each student, as well as how many grades a student got. And how much a grade is worth in points for example an A is worth 4.3.
So I was wondering what is the best way to build my MySQL database what do I need to add and what do I need to drop and how can I store numbers like 4.3 for example.
My database so far
CREATE TABLE grades (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
grade INT UNSIGNED NOT NULL,
student_work_id INT UNSIGNED NOT NULL,
student_id INT UNSIGNED NOT NULL,
date_created DATETIME NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE work (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
student_id INT UNSIGNED NOT NULL,
title TEXT NOT NULL,
content LONGTEXT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS student (
id int(8) NOT NULL auto_increment,
student varchar(20) NOT NULL,
PRIMARY KEY (`id`)
)
example of output.
student | grade | count
1 A 10
1 C 2
1 F 4
2 B 20
2 B- 3
2 C+ 10
student | grade | points
1 A 4.3
2 B+ 3.3
3 B- 2.7
4 D+ 1.3
student | total grades
1 90
2 80
3 100
4 1
I think a student - grade - assignment schema would be ok. You should count the number of grades of a student with a query and not maintain that information in your database.
At a conceptual level (not strict mysql), I would do something like this.
table student (
id int (primary key)
varchar name
... // other student attributes (e.g. phone number)
)
table grade (
id int (primary key)
name varchar // e.g. "A+", "B-", etc.
float points // grade value in points
)
table assignment (
id int (primary key)
name varchar // assignment name
student_id int (foreign key) // student that completed the particular assignment
grade_id int (foreign key) // grade achieved in particular assignment
... // other assignment attributes (e.g. due date)
)
Many would suggest that you have a student table, a grade table and a table that relates the student and grade table. You could then do counts based on the table that relates the two with a procedure. Many believe that having a grade and a student in the same table is poor relational design.
Unless you want to use a view, which is really not necessary in this case, you wouldn't store the amount of times a student got a certain grade; rather, you would make a query that gave you that information for each student when you needed it. One example using your current schema, to tell you how many times a particular student having id st_id got each grade, would be like
SELECT COUNT(1) FROM grades WHERE student_id = st_id GROUP BY grade
This will look at all the rows where student st_id got each grade and count them up separately.
As far as storing a mapping from letter grade to the amount of points that its worth, you could do one of two things - either have a table mapping from letter grade to point amount, or just store the point amounts directly. Personally, I think the former is a little bit cleaner as it allows you to change point amounts if the scale changes (not that that should happen often).
Other than that, I think you're doing pretty good. The only thing I would say is that if there is always a one-to-one mapping between grade and work, I might make grade part of the work table rather than separate them. That is, only if there isn't information in the grade table that doesn't belong in the work table.
Looking at your tables it appears that there's some normalizing that you can do, or I might be misunderstanding the objective. I would assume that students and work items are only connected by a grade, which would make your grades essentially a cross-reference table. As a result, you wouldn't reference the student_id column within the work table. Off the cuff, I would change it such that the tables look more like this:
CREATE TABLE student (
student_id int(8) NOT NULL auto_increment,
student varchar(20) NOT NULL,
PRIMARY KEY (student_id)
);
CREATE TABLE work (
work_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
title TEXT NOT NULL,
content LONGTEXT NOT NULL,
PRIMARY KEY (work_id)
);
CREATE TABLE grades (
grade_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
grade decimal(4,2) UNSIGNED NOT NULL,
work_id INT UNSIGNED NOT NULL,
student_id INT UNSIGNED NOT NULL,
date_created DATETIME NOT NULL,
PRIMARY KEY (id)
);
Note that I changed the grade column to a decimal (I'm a SQL Server guy, so MySQL might be different), but any type that can handle non-integer numbers works. With that design, you can then pull your queries as needed to get the types of output you need.
Josh
assuming -
there's more than one student
there's more than one assignment / piece of work to be graded against
more than one student can complete a specific set work/assignment
only one grade can be earned per assignment
I'd go with -
a table for the definition of each set piece of work / a specific assignment.
a table to define each student
a third table to record the result of each assignment per student, ie to store the grade per assignment.
eg -
CREATE TABLE work (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
student_id INT UNSIGNED NOT NULL,
assignment_id int unsigned not null,
grade DECIMAL(1,1)
gradedesc VARCHAR(1)
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS student (
id int(8) NOT NULL auto_increment,
student varchar(20) NOT NULL
PRIMARY KEY (`id`)
)
Create table assignment (
id int(8) not null auto_increment,
title TEXT NOT NULL,
content LONGTEXT NOT NULL
primary key(id)
Then, create some views to display the output in the format you want, eg -
Create view graded_students as
(select s.student,
w.gradedesc as grade,
w.grade as points,
w.title
from work w,
student s
where w.student_id = s.id
and w.grade is not null )
Create view aggregate_grades as
(select s.student,
sum(w.grade) as "total grades",
from work w,
student s
where w.student_id = s.id
and w.grade is not null
group by s.student)
create view grades_by_assignment as
(select s.student,
a.title,
w.gradedesc as grade
from student s,
work w,
assignment a
where a.id = w.assignment_id
and s.id = w.student_id )
I'm more of an oracle and sql server person, so this may not be 100% mysql compliant (and I haven't tested it) but something like that would do the trick I think.
If you want to normalize the data even further you could break out the grade description/value to another table too, or just display the grade description in your application based on the stored value (eg grade value 4.5 always displays as "A", etc)