MySQL rating database structure - mysql

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)

Related

How do I get a row of the same type from one table or another table along with the information about from which table it was

Let's say I have tables:
create table people (
human_id bigint auto_increment primary key,
birthday datetime );
create table students (
id bigint auto_increment primary key,
human_id bigint unique key not null,
group_id bigint not null );
create table teachers (
id bigint auto_increment primary key,
human_id bigint unique key not null,
academic_degree varchar(20) );
create table library_access (
access_id bigint auto_increment primary key,
human_id bigint not null,
accessed_on datetime );
Now I want to display information about a library access, along with the information whether it was a student or a teacher (and then the id corresponding to the table) (let's say I want something like SELECT access_id,id,true_if_student_false_if_teacher FROM library_access), in an idiomatic way.
How do I form the query (in case such database was already deployed) and what are better and more idiomatic ways to solve that problem (in case it wasn't deployed so far).
MariaDB 5.5, database accessed by Go and nothing else.
Thanks in advance.
You said you need to know which table the data comes from. You can use union all for this:
select la.access_id, s.id, 'Students' as source_table
from library_access la
join students s on la.human_id = s.human_id
union all
select la.access_id, t.id, 'Teachers' as source_table
from library_access la
join teachers t on la.human_id = t.human_id
Without looking at your tables or any idea as to what you want returned in the select statement:
SELECT *
FROM people a,
students b,
teachers c,
library_access d
WHERE a.human_id = b.human_id
AND a.human_id = c.human_id
AND a.human_id = d.human_id

Determining who owns a transferrable item in MySQL

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.

Better Database design for hibernatre?

I am new to database design. I am using MySQL
I have a Form table which contains a field Country
Since the values in this columns can be one from the country list only, so I made this column int instead of varchar and put the list in a new table. shown as below:
Instead of having country column like
country varchar(255) NOT NULL
I made it countryCode with datatype INT and added a Foreign Key to table CountryMaster which is shown as below
...
countryCode INT NOT NULL,
FOREIGN KEY (countryCode) REFERENCES CountryMaster (countryCode),
...
create table CountryMaster(
countryCode INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255)
);
It is working fine, but the issue is that the same table contains many more column like formStatus (with possible values Draft, Saved, Approved), gender (Male, Female), bloodGroup (A, B, O), rHFactor (+ve, -ve), occupation (Service, Business, Student, Unemployed) etc. If I will make separate table for each such column I will end up in 20 to 25 Master tables.
I also tried to put all such Master Date in a single table i.e. CommonMaster with columns type, key and value shown as below:
create table CommonMaster(
commonCode INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
type VARCHAR(255) NOT NULL,
key INT NOT NULL,
value VARCHAR(255) NOT NULL
);
with values like:
code type key value
7 BLOOD_GROUP 1 A
8 BLOOD_GROUP 2 B
9 BLOOD_GROUP 3 O
10 RH_FACTOR 1 +ve
11 RH_FACTOR 2 -ve
12 OCCUPATION 1 Service
13 OCCUPATION 2 Profession
14 OCCUPATION 3 Business
15 OCCUPATION 4 Student
16 OCCUPATION 5 Unemployeed
I want to know that what is a better approach and why. I also have to use hibernate orm in the project. So the design should be pertaining to that.
I prefer a hundreds master tables than a common table with all the types; more structure, more organization, more adaptive for add, delete and update the value of then, also for reports.

correct way to place data in table OneToONe

I am confused about the correct/most efficient way to place data in my dababase table when there is a OneToOne relationship.
For example, I have a users table.
I now wish for each user to be able to state his current country location.
i then want to be able to search the datatable for users by current location.
The way that I have done this is to create 3 separate tables. i.e
table one - users : just contains the user information:
CREATE TABLE users(
id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
firstName VARCHAR(30) NOT NULL,
lastName VARCHAR(40) NOT NULL,
);
Table two country list: a list of countries and respective Ids for each country
PHP Code:
CREATE TABLE countrylist(
country_id MEDIUMINT UNSIGNED NOT NULL,
country VARCHAR(60) NOT NULL,
INDEX country_id ( country_id, country ),
INDEX countrylist (country, country_id ),
UNIQUE KEY (country)
);
Table 3; contains the userId and the countryId he lives in:
PHP Code:
CREATE TABLE user_countrylocation(
country_id VARCHAR(60) NOT NULL,
id MEDIUMINT UNSIGNED NOT NULL,
INDEX country_id (country_id, id ),
INDEX user_id (id, country_id )
);
Alternatively, should I place the countryId in the users table and completely get rid of the user_countrylocation. i.e in each user column, I will place a country_id for the country he lives in.
The problem is that I have over 20 similar tables as above that give details on users; i.e languages spoken, age-group, nationality etc.
My concerns is that if I place this unique information in each users column in the user table, then what would be the most efficient way to search the database: that is why I opted for the style above.
So, I really request for some advice on the most efficient/correct way to plan the database.
If you are going to have a huge data then you should keep the same approach and use the following method to keep the one to one constraint satisfied
if you don't have a huge data then you should keep the look up tables like country and use the reference for user in a column. but then you may need to allow them nulls that is make such optional information columns nullable.
The most efficient and exactly correct way is to first delete the data from the third table "user_countrylocation" for the user to be updated. Then insert the new location for the user. don't forget to use transaction.
your table 3 should have
country_id MEDIUMINT UNSIGNED NOT NULL,
instead of
country_id VARCHAR(60) NOT NULL,
and also change tyhe column name from id to user_id in all tables.
if you are using a stored procedure it would be like
create procedure sp_UpdateUserCurrentCountry (
#userID MEDIUMINT UNSIGNED,
#CountryID MEDIUMINT UNSIGNED)
begin
as
delete from user_countrylocation
where user_id = #userID
insert into user_countrylocation
(
country_id,
user_id
)
values
(
#CountryID,
#userID
)
END
One to One relations are usually mapped via Foreign Keys linking the two tables together. A third mapping table is only required for Many to Many relationships. So, you should ideally have a Foreign Key Country_ID in your Users table.
Your SELECT query would then look like
SELECT * FROM Users
WHERE Country_ID = (
SELECT Country_ID FROM Countries
WHERE Country_Name = 'USA'
);

Database Design for a status update system involving companies and users

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)