I am making a database of students in one school.Here is what I have so far:
If you don't like reading jump to the "In short" part
The problem is that I'm not happy with this design. I want the combination of grade, subgrade and id_class to be unique and to serve as a primary key for the students table.
I can remove the student_id and make a composite key from the 3 but I don't want that either. Maybe I should make another table lets say combination_id where grade, subgrade and id_class are foreign keys and there is one extra column comb_id that serves as ID for the table. And all the columns will be Primary Keys. But the problem is that those 3 columns can still repeat because of that extra column (comb_id). For example I can have the same grade, subgrade and class_id but different comb_id which will make the row valid because of the composite key of the 4 columns of the table (combination_id).
In short I want students_id to remain the only primary key of the table but to be a foreign key to another table which is somehow unique combination of grades, subgrade and class_id.
If I was not clear enough ask in the comments below and thank you in advance.
PS I'm sorry for the indescriptive title but I'm bad at naming
EDIT 1:
To be more clear:
grade can be 1 to 12
subgrade can be a to j
id_class can be 1 to 30 and it is your number in class
So a student can be from 7b class and his number in class - 5
Don't mix the concepts of unique keys and primary keys. You can very well add a unique key spanning the three columns grades, subgrade and class_id. That way, no two rows could have the same values for these three columns. As you write that you don't want to have these three as a composite primary key, I'm not sure whether a composite unique supplemental key would be any better. If not, you'll have to clarify when composite keys are acceptable.
To create a unique key, you can use the following SQL statement:
ALTER TABLE students ADD UNIQUE gsc (grades, subgrade, class_id);
The word gsc there is just a name I made up from the initials of the key columns; use whatever name you want, as it hardly matters unless you want to identify the key in some EXPLAIN output or similar.
I'm not totally clear on why you want what you have described, but I would look at the model in the following way...
You have Students
- They are distinct entities, not a composite of other entities
- They have their own properties; name, date of birth, etc
You have classes
- These are groups of students
- Each accademic year the same "class" has different students in it
- They also have their own properties; grade, sub-grade, etc
You have an extra property in your model that I would not normally use
- If a class has 20 students, each of them is identified with a secondary id from 1 to 20
This would give me the following Dimension tables
Student Class Grade SubGrade
----------------------- ------------------------ ----------------- -----------------
id INT PK id INT PK id INT PK id INT PK
first_name VARCHAR(45) name VARCHAR(45) name VARCHAR(45) name VARCHAR(45)
last_name VARCHAR(45) grade_id INT FK desc VARCHAR(45) desc VARCHAR(45)
etc, etc subgrade_id INT FK etc, etc etc, etc
The Class table would have a unique constraint on (grade_id, subgrade_id) so that only one class could ever be 7b.
Then you need to relate the students to their classes using a fact table...
Class_Membership
-----------------------
id INT PK
student_id INT FK
class_id INT FK
academic_year INT
If a student should only ever be in one class in any academic year, you would put a unique constraint on (student_id, academic_year).
Alternatively, you could have the academic year in the Class table. This would mean that you would have the same class repeated for every year, but that in some years class 7g may not exist (as there are less students that year, for example).
Equally, you could have students who move from 7b to 7c mid-year. In which case the Class_Membership table could have a start_date field and possibly an end_date field.
None of that, however, directly creates the id_class field (1-20 for a class with 20 students). Personally I would not have such a field, the id field from the Class_Membership table can serve most of the same functionality, and probably additional functionality. Where it is necessary, however, you could simply add it to the Class_Membership table...
Class_Membership
-----------------------
id INT PK
student_id INT FK
class_id INT FK
academic_year INT
class_member_id INT
Then you could also have a unique constraint on (academic_year, class_id, class_member_id).
There is quite a lot of flexibility here, depending on your exact real-world-model and your particular needs. But hopefully this example is a good start for you; Dimension tables listing Entities, and a Fact table (or tables) relating these entities together and/or further describing the Entities.
alter table <TableName> add constraint <ConstraintName> unique(<col1>, <col2>)
Modified the answer due to syntax mistakes. Mentioned above is correct.
Related
Lets says that I have an order table and item table :
CREATE TABLE if not exists ORDERS (
ORDERID INTEGER AUTO_INCREMENT,
ORDERTYPE VARCHAR (20) NOT NULL,
ShippedTime VARCHAR(40),
ORDERDATE DATE,
PRIMARY KEY (ORDERID),
);
CREATE TABLE if not exists ITEM(
ITEMID INTEGER AUTO_INCREMENT,
NAME VARCHAR (20) NOT NULL,
PRICE INTEGER NOT NULL CHECK (PRICE > 0),
PRIMARY KEY (ITEMID)
);
and the relation between the both tables will be existof :
CREATE TABLE if not exists EXISTOF (
ORDERID INTEGER NOT NULL,
ITEMID INTEGER NOT NULL,
FOREIGN KEY (ORDERID) REFERENCES ORDERS(ORDERID) ON DELETE CASCADE,
FOREIGN KEY (ITEMID) REFERENCES ITEM(ITEMID) ON DELETE CASCADE,
PRIMARY KEY (ORDERID,ITEMID)
);
The explanation should be for each order has multiple item and each item belongs to many orders.
If I do like this it will not be work because the ids are primary keys and I can't insert for specific order multiple item and also it can not items belongs to multiple order.
Does anyone have any recommendation how to do that?
Your Existof Table is not flexible enough. The way most order processing systems deal with this situation is to add a column, which we can call Quantity, to the Existof table. The default value is 1, but other quantities can be put in as well.
So if a given order wants to order say 5 reams of paper,and ream of paper in a product, the entry for this item in Existof will have a quantity of 5.
This assumes that all 5 reams are interchangeable, and therefore described by the same data. If some of the paper reams are of different colors, than they ought to be different products.
Create an intermediate table OrderItems with foreign keys item_id and order_id. There are other options but this is the easiest way I find to break down many-many relationships!
"... have to be ..." -- no. FOREIGN KEYs are never "required".
A FK provides three things:
A dynamic check that there is a matching element. This is useful as an integrity check on the data, but is not mandatory.
An INDEX to make the above check significantly faster. Manually specifying an INDEX is just as good. Anyway, a PRIMARY KEY is an index.
"Casscading delete, etc". This is an option that few schemas use, or even need.
There are 3 main types of "relations" between tables:
1:1 -- But why bother having two tables? The columns could simply be in a single table. (There are exceptions.)
1:many -- (This sounds like "many items in one order"??) That is implemented by simply having order_id in the Items table. (And index that column.) Optionally, it can be a FK. Others call the table OrderItems. And it links to a Products table.
many:many -- This is when you need an extra table with (usually) exactly two columns, namely ids into the other two tables. (Eg, Student vs class) Each column could be an FK, but the optimal indexes are PRIMARY KEY(a_id, b_id) and INDEX(b_id, a_id). The FKs would see that you already have indexes starting with a_id and b_id, so it would not create an extra index. Do not have "a unique junction table ID"; it is less efficient than the PK I suggest here. (More discussion: http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table)
Back to your proposed design. I suggest that "item" implies the product and quantity of that product and the price charged at that time. Hence it needs to be 1:many. And that "product" is what you are thinking of. Please change the table name so I am not confused.
Now, another issue... Price. Is the price fixed forever? Or is the price going to be different for today's Orders than for yesterday's? Again, the Item and Price are tied to one Order. There may be a Price on the Product table, and that may be "current_price", which gets used when creating new Orders.
ShippedTime VARCHAR(40) -- Perhaps should be DATETIME?
Here is a gross oversimplification of an intense setup I am working with. table_1 and table_2 both have auto-increment surrogate primary keys as the ID. info is a table that contains information about both table_1 and table_2.
table_1 (id, field)
table_2 (id, field, field)
info ( ???, field)
I am trying to decided if I should make the primary key of info a composite of the IDs from table_1 and table_2. If I were to do this, which of these makes most sense?
( in this example I am combining ID 11209 with ID 437 )
INT(9) 11209437 (i can imagine why this is bad)
VARCHAR (10) 11209-437
DECIMAL (10,4) 11209.437
Or something else?
Would this be fine to use this as the Primary Key on a MYSQL MYISAM DB?
I would use a composite (multi-column) key.
CREATE TABLE INFO (
t1ID INT,
t2ID INT,
PRIMARY KEY (t1ID, t2ID)
)
This way you can have t1ID and t2ID as foreign keys pointing to their respective tables as well.
I would not make the primary key of the "info" table a composite of the two values from other tables.
Others can articulate the reasons better, but it feels wrong to have a column that is really made up of two pieces of information. What if you want to sort on the ID from the second table for some reason? What if you want to count the number of times a value from either table is present?
I would always keep these as two distinct columns. You could use a two-column primay key in mysql ...PRIMARY KEY(id_a, id_b)... but I prefer using a two-column unique index, and having an auto-increment primary key field.
the syntax is CONSTRAINT constraint_name PRIMARY KEY(col1,col2,col3) for example ::
CONSTRAINT pk_PersonID PRIMARY KEY (P_Id,LastName)
the above example will work if you are writting it while you are creating the table for example ::
CREATE TABLE person (
P_Id int ,
............,
............,
CONSTRAINT pk_PersonID PRIMARY KEY (P_Id,LastName)
);
to add this constraint to an existing table you need to follow the following syntax
ALTER TABLE table_name ADD CONSTRAINT constraint_name PRIMARY KEY (P_Id,LastName)
Suppose you have already created a table now you can use this query to make composite primary key
alter table employee add primary key(emp_id,emp_name);
Aside from personal design preferences, there are cases where one wants to make use of composite primary keys. Tables may have two or more fields that provide a unique combination, and not necessarily by way of foreign keys.
As an example, each US state has a set of unique Congressional districts. While many states may individually have a CD-5, there will never be more than one CD-5 in any of the 50 states, and vice versa. Therefore, creating an autonumber field for Massachusetts CD-5 would be redundant.
If the database drives a dynamic web page, writing code to query on a two-field combination could be much simpler than extracting/resubmitting an autonumbered key.
So while I'm not answering the original question, I certainly appreciate Adam's direct answer.
Composite primary keys are what you want where you want to create a many to many relationship with a fact table. For example, you might have a holiday rental package that includes a number of properties in it. On the other hand, the property could also be available as a part of a number of rental packages, either on its own or with other properties. In this scenario, you establish the relationship between the property and the rental package with a property/package fact table. The association between a property and a package will be unique, you will only ever join using property_id with the property table and/or package_id with the package table. Each relationship is unique and an auto_increment key is redundant as it won't feature in any other table. Hence defining the composite key is the answer.
CREATE TABLE `mom`.`sec_subsection` (
`idsec_sub` INT(11) NOT NULL ,
`idSubSections` INT(11) NOT NULL ,
PRIMARY KEY (`idsec_sub`, `idSubSections`)
);
#AlexCuse I wanted to add this as comment to your answer but gave up after making multiple failed attempt to add newlines in comments.
That said, t1ID is unique in table_1 but that doesn't makes it unique in INFO table as well.
For example:
Table_1 has:
Id Field
1 A
2 B
Table_2 has:
Id Field
1 X
2 Y
INFO then can have:
t1ID t2ID field
1 1 some
1 2 data
2 1 in-each
2 2 row
So in INFO table to uniquely identify a row you need both t1ID and t2ID
This table should contain foreign keys to allow sensible links to be made with the other two tables, together with the dates of each exam.
So what am i being asked to do exactly?
All the other tables have been populated.
This looks like a classic Many To Many relational problem.
One student can study more than one subject
A subject can be studied by more than student
This cannot be readily modelled relationally using just the two tables you have given.
The traditional way to model this is to introduce a third table which contains the details of which students are studying which subjects.
The table would generally contain the id's of the tables that participate in the many to many relationship, so in your case it would have student_id and subjects_id. These would be defined with foreign key constraints back to the student and subjects tables.
This page may go a long way to helping you understand this.
By the way - the primary key constraint on the subjects table looks a bit suspect to me ...
First of all, your PRIMARY KEY of the subject table should be subjects_id, because you may have 2 final exams the same day.
CREATE TABLE IF NOT EXISTS entries(
entry_id INT UNSIGNED NOT NULL AUTO_INCREMENT,( PK)
student_id INT UNSIGNED NOT NULL,(FK From students table)
subject_id INT UNSIGNED NOT NULL,(FK From subjects table )
);
You need to make (student_id,subject_id) combination unique since it shouldn't be duplicated.This is rough draft.Assume that this table is a bridge which connects students and subjects.
DBLint is used to check database status. There are 46 rules. At www.dblint.org there are some simple explainations of each rule, but the Rule 31 which is described as below:
Defined Primary Key is not a Minimal Key:
A primary key is a minimal superkey. If the defined primary key is not a minimal superkey, it means that it is possible to identify a row with fewer attributes. Using a superkey instead of a primary key is even less attractive when other tables need to reference it. Each of the referencing tables will need to hold more information than actual needed, resulting in using more space and less efficient indices.
which is not quite clearify for me. If someone could explain this, Thank You!
A primary key is a super key if as subset of the columns in the primary key can also be used to uniquely identify a row.
Example:
Let's say you have a table containing all employees in a company. Each person gets an id but you also have a requirement that each person have a unique alias consistent of three letters for login name, email etc. This table could be:
create table employee
(
empID int not null auto_increment,
alias nvarchar(3) not null unique,
forname nvarchar(256) not null,
lastname nvarchar(256) not null,
constraint employee_pk primary key (empID, alias)
)
In this table the primary key is the combination of the empID and the persons three letter alias, but both the empID and the alias is required to be unique hence the primary key is a super key. It would be enough to only use a subset of the columns, e.g., the empID as a primary key to uniquely identify a single row, hence that will be a minimal super key.
say that i have a one to many relations where there are two tables, a Person table and a Belonging table. Now, each Person has ONLY ONE favorite belonging and a specific belonging cannot belong to another person as well.
My question is, where would that information be better kept ? In the Person table as a favorite_belonging_id or in the Belonging table as an is_favorite entry ? To my eyes, the first choice seems to be the better version, but I would like to hear what sql knowledgeable people have to say about it.
EDIT : A Person has many belongings but only ONE favorite belonging and each belonging can only belong to one person. It's a one to many association.
I'd be tempted to go with your first suggestion (a favourite_belonging_id column in the Person table), as one can then create a foreign key reference from (person_id, favourite_belonging_id) to (owner_id, belonging_id) in the Belonging table.
If one were to go the other route of creating a is_favourite flag in the Belonging table, there is no obvious way of ensuring the 1:1 nature of person-favourite belonging relationships (a composite UNIQUE index over (owner_id, is_favourite) would fail when a person has multiple belongings that are not their favourite).
That said, it doesn't feel like this information really belongs in the Person table, as it isn't really a property of the person but rather it's a property of the Belonging. If you feel strongly about it, you could create a Favourites table that has a UNIQUE (or PRIMARY) index over person_id.
to me it does NOT belong in the person table since it has nothing to do with the base person.
if you have only the belonging table - which i also assume has a person_id in it, then this is where you are expressing the relationship between the belonging and the person, and it is where the qualifier should also go.
another option is to have a third table in the middle linking the two - in this case, the favorite flag goes there.
edit:
my preference in design would be the third table option - here you can put a begin date and end date as well as the favorite flag - this would allow you to theoretically trade a belonging to another person at some point in time and still know what happened.
I see that pretty much all the different options have already been laid out in different answers, but instead of commenting on all to give you my impression on what I think you should do, I'll just create an answer myself.
Just to be clear on how I understand how the system works: All users can have multiple belongings, but any belonging can only be help by one person.
In this case, it makes the most sense to have a user_id in the belongings table that can tie a belonging to a person. Once a user_id is set, nobody else can claim it anymore.
Now, as to the 'favorite' part, there are several things you can do. What truly is the best way to do it strongly depends on the queries you plan on running on it. Some consider adding a JOIN table, but honestly this is a lot of additional data that is rather pointless; there is likely going to be the exact amount of rows in it as the user table and by putting it in a separate table, there is a lot you can't do (for example, see how many people DON'T have a favorite). Likewise, a JOIN table would make no sense for the user_belonging relationship, as there is a 1:1 relationship between the belonging and the amount of people who can have it.
So I believe there are two viable options: either add a field (/switch) in the belongings table to indicate of a user's belonging is his/ her favorite, or add a field to the user table to indicate which belonging is the user's favorite. I would personally think that the latter holds the most merit, but depending on the queries you run, it might make more sense to to the former. Overall, the biggest difference is whether you want to process things pre-insert or post-select; e.g. in the latter situation, you will have to run an independent query to figure out if the user already has a favorite (in the former case this won't be necessary as you would put a unique index on the field in the user table), whereas in a post-select situation you will have to do cross reference which of the selected belongings from the belonging table is the user's favorite.
Please let me know if I explained myself clearly, or if you have any further questions.
The following may not be the best options because it offers a somewhat unconventional method of flagging the favourite belonging. The advantage, though, is that this way you'll have just two tables with no circular references and every person will be guaranteed to have no more than one favourite belonging.
So, it's two tables, people (or persons) and belongings. The people table has this structure:
person_id INT AUTO_INCREMENT,
other columns as necessary,
PRIMARY KEY (person_id)
The belongings table is created like this:
belonging_id INT AUTO_INCREMENT,
person_id INT NOT NULL,
is_favourite enum ('1'),
other columns as necessary,
PRIMARY KEY (belonging_id),
FOREIGN KEY (person_id) REFERENCING people (person_id),
UNIQUE (person_id, is_favourite)
The key element is declaring is_favourite as a nullable enum with a single possible value. This way, when you declare a unique constraint on the pair of (person_id, is_favourite), you are allowed to have as many rows with the same person_id and empty (null) is_favourite as possible, because unique constraints ignore rows where at least one member is null. And you won't be able to create more than one person_id with is_favourite = '1', because that would violate the unique constraint.
Neither. My suggestion is to add another table person_favourite_belonging, like this:
CREATE TABLE person
( person_id INTEGER NOT NULL
--- various other columns about Persons
, PRIMARY KEY (person_id)
) ;
CREATE TABLE belonging
( belonging_id INTEGER NOT NULL
, person_id INTEGER NOT NULL
--- various other columns about Belongings
, PRIMARY KEY (belonging_id)
, UNIQUE KEY (person_id, belonging_id) --- this Unique constraint is needed
, FOREIGN KEY (person_id)
REFERENCES person (person_id)
) ;
CREATE TABLE person_favourite_belonging
( person_id INTEGER NOT NULL
, belonging_id INTEGER NOT NULL
, PRIMARY KEY (person_id)
, FOREIGN KEY (person_id, belonging_id) --- for this Foreign Key constraint
REFERENCES belonging (person_id, belonging_id)
) ;
This is just my preferred way of doing this. There are alternatives and all have their pros and cons. The pros with this approach are:
No circular path in the Foreign Key constraints (and therefore):
No chicken and egg problems when inserting, deleting or updating Persons, Belongings or Favourite Belongings.
All foreign key columns can be defined as NOT NULL.
The integrity can be enforced at the database level.
If your requirements change and you want to have 2 (or more) favourites per person, you only change appropriately the constraints at the Favourite table.
Check also my answer in this question (with an almost identical problem): In SQL, is it OK for two tables to refer to each other?
favourite_thing is a FK to the belonging table (if that table exists, otherwise it could be a domain) , but in an additional constraint, you can force belonging_id in the persons table to be unique.
UPDATE:
DROP table belonging;
CREATE table belonging
( id INTEGER PRIMARY KEY
, description varchar
);
DROP table person;
CREATE table person
( id INTEGER PRIMARY KEY
, description varchar
, favourite_thing INTEGER REFERENCES belonging (id)
);
-- Now add the unique constraint
-- NOTE: favourite_thing can still be NULL
ALTER TABLE person
ADD CONSTRAINT must_be_unique UNIQUE (favourite_thing)
;
UPDATE 2: if every belonging belongs to exactly one person, you could add an owner field to belongings:
CREATE table belonging
( id INTEGER PRIMARY KEY
, owner_id INTEGER NOT NULL REFERENCES person(id)
, description varchar
);
DROP table person CASCADE;
CREATE table person
( id INTEGER PRIMARY KEY
, description varchar
, favourite_thing INTEGER REFERENCES belonging (id)
);
ALTER TABLE person
ADD CONSTRAINT must_be_unique UNIQUE (favourite_thing)
;
Actually you present a one-to-one relation.
So you can:
1. Hold it in Person table.
2. Hold it in Belonging table.
3. Hold it in both.
4. Hold it in separate table.