I currently have a courses table that has a layout like below.
course_title, course_subject, course_number, instructor, course_id
I need to create a new table and then push over the instructor_id, so I can then access the table via a foreign key id for instructor, but I'm not sure how I would go about getting the data from the courses table over to the new tables such as instructor and also creating the instructor_id fields in the courses table. I hope this question makes sense. I'm importing a flat file from microsoft excel.
Let's first define the schemas for our output tables. The courses table should be something like:
title varchar(100) not null
subject varchar(100) not null
[number] varchar(5) not null unique
id long primary key
instructor_id long not null references instructors (id)
And the instructors table should be:
id autoincrement primary key
[name] varchar(100) not null unique
Let's call your current table courses_old. I may be off by a bit but I'm sure you can adjust for your needs.
So the problem is to get from courses_old to courses and instructors. First we fill up the instructors table:
insert into instructors ([name])
select distinct instructor
from courses_old
Then we fill up courses with matching instructors from instructors:
insert into courses (
title
, subject
, [number]
, id
, instructor_id
) select
co.course_title
, co.course_subject
, co.course_number
, co.course_id
, i.id
from courses_old as co
inner join instructors as i
on co.instructor = i.[name]
That should do it. But obviously you need to verify. Finally, drop the courses_old table and compact the DB file!
Optionally, you can make courses.id an autoincrement field if you're willing to just give them all new IDs. That should be a simple adjustment to the above.
Related
So I have a MySQL database I use for a software I have made where teachers can upload assignments, students can deliver their assignments and teachers can evaluate these deliveries. Now I want to list a overview over which assignment has the most comments on the deliveries in descending order.
All assignments are stored in a table called module where module_id is PK. All deliveries are stored in a table called delivery where delivery_id is PK and both user_id and module_id is FK. Last but not least we have the student_comment table where all comments are stored. In that table Student_comment_id is PK and delivery_id is FK. If my explanation was horrible, I`ve listed under how I made the tables. There are obviously more tables but I think they are irrelevant to this question.
create table module(
module_id int not null auto_increment,
module_name varchar(50) not null,
description varchar(1000),
rights int,
primary key(module_id)
);
create table delivery(
delivery_id int not null auto_increment,
module_id int,
users_id int,
delivery_status int,
date_delivered date,
date_approved date,
primary key(delivery_id),
foreign key(module_id) references module(module_id),
foreign key(users_id) references users(users_id)
);
create table student_comment(
student_comment_id int not null auto_increment primary key,
s_content varchar(100),
delivery_id int,
foreign key(delivery_id) references delivery(delivery_id));
So again, I want to list a overview over which module have the most comments on the deliveries and I want to sort it in a descending order. I have tried this for a while now but cant really figure out how to do this. Any help is really appreciated!
Also, I have just started with SQL so if there is something you think is wrong or you have a great source of information which can help me, feel free to comment.
An aggregate query should produce what you're asking for. When you say "which module have the most comments on the deliveries," I interpret that to mean that you want to know the number of comments on all of the deliveries. The following SQL should produce that for you.
select
m.module_id,
count(c.student_comment_id) as comment_count
from
module as m
inner join delivery as d on d.module_id=m.module_id
inner join student_comment as c on c.delivery_id=d.delivery_id
group by
m.module_id
order by
count(c.student_comment_id) desc;
I am trying to insert some data into the table User. The User table has 2 foreign keys: StudentID and StaffID.
I want to be able to enter either StaffID or StudentID which should link to the relevant table(that already has either the StudentID or StaffID). The table User can only have StaffID or StudentID. Can Anyone help?
INSERT INTO `User` (`UserName`, `Email`, `StudentID`,`StaffID`,`Paasword`)
VALUES ('phill', 'ph#lms.com', '', '2201','654321');
Ok, so prepare yourself. This answer is long, but thorough.
Short Answer:
As referenced in this answer by #HLGEM you can accomplish what you're asking by making the primary keys in the STAFF and STUDENT table, presumably the values StaffID and StudentID NULLABLE. Here is the relevant snippet from his answer:
To allow nulls in an FK generally all you have to do is allow nulls on the field that has the FK. The null value is separate from the idea of it being an FK.
You can do this in your table definition adding NULL to your create statement similar to the following by specifying:
CREATE TABLE STUDENT (
StudentID INT UNSIGNED NULL PRIMARY KEY AUTO_INCREMENT
...
)
The above is an example of how to apply this to the STUDENT table. The STAFF table would have a similar approach. Note, the extra values are there as a suggestion based on general configuration that is common when configuring ID fields.
LONG ANSWER:
As #HLGEM mentioned in his answer, there are some times when it is appropriate to have a foreign key constraint that can be NULL. However, in your case, it suggestions that the data is not fully normalized. The need for a NULL foreign key can be eliminated in your case with a little table refactoring. Let's explore an additional possibility when designing your database tables:
Case Study:
Let's begin with the following assumption. Since you said in your question:
I want to be able to enter either StaffID or StudentID which should link to the relevant table(that already has either the StudentID or StaffID). The table User can only have StaffID or StudentID
It is probably a safe assumption to say that a user has to be a staff member or a student but not both. That assumption makes a strong use case for having a UserType table. Let's alter the USER definition to support a UserTypeId and create the USER_TYPE table:
# USER TYPE Table Definition
CREATE TABLE USER_TYPES (
UserTypeId TINYINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
UserType VARCHAR(25) NOT NULL
) ENGINE=INNODB CHARSET=UTF8;
# USER TABLE Definition
CREATE TABLE USERS (
UserId INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
Name VARCHAR(25) NOT NULL,
Email VARCHAR(50) NOT NULL,
Password VARCHAR(100) NOT NULL,
UserTypeId TINYINT UNSIGNED NOT NULL,
FOREIGN KEY(UserTypeId) REFERENCES USER_TYPES(UserTypeId)
) ENGINE=INNODB CHARSET=UTF8;
Notice that in this new schema, we no longer have a reference to StudentID or StaffID, but instead we have a UserTypeId. There are two benefits to this approach:
USERS.UserTypeId is a foreign key reference to USER_TYPES.UserTypeId but no longer has to be NULLABLE.
You can have more user types than just Student or Staff.
For our case study here, let's create two user types Student and Employee (I'll explain why I'm not using Staff here in a little). Let's also go ahead and populate the USERS table with the initial values for Phill, your employee you mentioned in your question.
INSERT INTO USER_TYPES(UserType)
VALUES("Employee"),("Student");
INSERT INTO USERS(Name,Email,Password,UserTypeId)
VALUES("phill","ph#lms.com","654321",1);
Excellent! Our new design is coming together quickly. Now let's create two additional tables, one called STUDENTS and one called EMPLOYEES. In this case, I chose to go with EMPLOYEES instead of STAFF because it allows you to be more flexible in how you define an employee. As you'll see in the definition, you can further define a user type of Employee with an ENUM value of either Faculty, Staff or Administrative. Think of this as a subtype of the general type Employee. Note that you could also create another join table like we did for USER_TYPES, for instance one called EMPLOYEE_TYPES. Either method is appropriate. I chose to use an ENUM instead of another foreign key to demonstrate an additional concept you could use if you only have a handful of choices.
So on to those last two table definitions:
# STUDENTS Table Definition
CREATE TABLE STUDENTS(
StudentId INT UNSIGNED PRIMARY KEY,
Year ENUM('Freshman','Sophmore','Junior','Senior') NOT NULL,
FOREIGN KEY(StudentId) REFERENCES USERS(UserId)
) ENGINE=INNODB CHARSET=UTF8;
# EMPLOYEES Table Definition
CREATE TABLE EMPLOYEES (
EmployeeId INT UNSIGNED PRIMARY KEY,
EmployeeType ENUM('Faculty','Staff','Administrative') NOT NULL,
FOREIGN KEY(EmployeeId) REFERENCES USERS(UserId)
) ENGINE=INNODB CHARSET=UTF8;
Notice that these two tables do not have their own Id column, but instead, reference the Id from the USERS table as a foreign key constraint. This makes sense because you have to be a user before you can be a student or an employee.
Finally, let's add some employee data for Phill:
INSERT INTO EMPLOYEES(EmployeeId,EmployeeType)
VALUES(1,"Faculty");
That was a lot, but now you'll begin to reap the benefits. Everything above was foundational in that it offers a fully normalized approach to your database layout with additional flexibility and without the need for a NULLABLE foreign key.
Retrieving data in this instance is easy and we don't even have to know if Phill is an employee or a student. Let's look at an example query:
SELECT
u.UserId,
u.Name,
u.Email,
ut.UserType,
s.*,
e.*
FROM USERS AS u
INNER JOIN USER_TYPES AS ut ON u.UserTypeId = ut.UserTypeId
LEFT JOIN STUDENTS AS s ON u.UserId = s.StudentId
LEFT JOIN EMPLOYEES AS e ON u.UserId = e.EmployeeId
WHERE u.Email = "ph#lms.com";
which returns:
+--------+--------+-------------+-----------+-----------+--------+------------+--------------+
| UserId | Name | Email | UserType | StudentId | Year | EmployeeId | EmployeeType |
+--------+--------+-------------+-----------+-----------+--------+------------+--------------+
| 1 | phill | ph#lms.com | Employee | (null) | (null) | 1 | Faculty |
+--------+--------+-------------+-----------+-----------+--------+------------+--------------+
Conclusion:
Live Example: sqlfiddle
So there you have it. By performing a LEFT JOIN between STUDENTS, EMPLOYEES and the USERS table, you'll pull back all values that exist in either of the two tables and all the default user values. From here, it's a simple as checking for NULL on StudentId or EmployeeId to determine which user type you're working with.
I have a conceptual question regarding how best to organise my database.
Currently I have four core tables users, teachers, students and notifications. However both the teachers and students tables inherit from the users table so contain the foreign key user_id.
The notifications table as you might have guessed refers to notifications. These need to appear for all users that belong to an employee group i.e. under the employment of another.
Both students and teachers can employ other users.
So the crux is I need an eloquent way of modelling this. The basic workflow of the code would be the below:
getCurrentUser->getAllEmployer(s)->getNotifications
This is the Laravel Eloquent I'm used to $user->employers()->notifications;
Unfortunately it's not as simple as that as in this case an employer can refer to two tables.
So my choices are as follows.
Create an Eloquent Relationship for both the student and teacher
relationship as employers. The shortfall being I need to write if
tests to check if the current user belongs to either and this code
would be repeated frequently.
Add a teacher_id and student_id to
the users table. However one would obviously be redundant in each
record. The chance of needing to add other columns is very likely as
well due to the emergence of new employer entities.
Create an employer_employee table that contains two columns both referencing a user_id. A SQL query would LEFT JOIN both student and
teacher tables with the employer_employee table and then a JOIN
with notifications would return all those relevant. However would
so many joins reduce the speed of the query when compared with the
other options.
Something I haven't considered.
I'm really looking for the most efficient, scalable solution.
Any help is appreciated. If you could clarify why your answer is the most efficient scalable solution as well that would be superb.
There is a similar question here using a Media supertype and adding subtypes of CD, VCR, DVD, etc.
This is scalable in that in creating, say, a BluRay subtype, you create the table to contain the BluRay-specific data and add an entry to the MediaTypes table. No changes needed for existing data or code -- except, of course, to add the code that will work with BluRay data.
In your case, Users would be the supertype table with Teachers and Students the subtype tables.
create table Users(
ID int not null auto_generating,
Type char( 1 ) check( Type in( 'T', 'S' )),
-- other data common to all users,
constraint PK_Users primary key( ID ),
constraint UQ_UserType unique( ID, Type ),
constraint FK_UserTypes foreign key( Type )
references UserTypes( ID )
);
create table Teachers(
TeacherID int not null,
TeacherType char( 1 ) check( TeacherType = 'T' )),
-- other data common to all teachers...,
constraint PK_Teachers primary key( TeacherID ),
constraint FK_TeacherUser foreign key( TeacherID, TeacherType )
references Users( ID, Types )
);
The makeup of the Students table would be similar to the Teachers table.
Since both teachers and students may employ other teachers and students, the table that contains this relationship would refer to the Users table.
create table Employment(
EmployerID int not null,
EmployeeID int not null,
-- other data concerning the employment...,
constraint CK_EmploymentDupes check( EmployerID <> EmployeeID ),
constraint PK_Employment primary key( EmployerID, EmployeeID ),
constraint FK_EmploymentEmployer foreign key( EmployerID )
references Users( ID ),
constraint FK_EmploymentEmployee foreign key( EmployeeID )
references Users( ID )
);
As I understand it, Notifications are grouped by employer:
create table Notifications(
EmployerID int not null
NotificationDate date,
NotificationData varchar( 500 ),
-- other notification data...,
constraint FK_NotificationsEmployer foreign key( EmployerID )
references Users( ID )
);
The queries should be simple enough. For example, if a user wanted to see all the notifications from his employer(s):
select e.EmployerID, n.NotificationDate, n.NotificationData
from Employment e
join Notifications n
on n.EmployerID = e.EmployerID
where e.EmployeeID = :UserID;
This is an initial sketch, of course. Refinements are possible. But to your numbered points:
The Employment table relates employers to employees. The only check if to make user employers cannot employee themselves, but otherwise any user can be both an employee and employer.
The Users table forces each user to be either a teacher ('T') or student ('S'). Only users defined as 'T' can be placed in the Teachers table and only users defined as 'S' can be placed in the Students table.
The Employment table joins only to the Users table, not to both the Teachers and Students tables. But this is because both teachers and students can be both employers and employees, not for any performance reason. In general, don't worry about performance during the initial design. Your primary concern at this point is data integrity. Relational databases are very good with joins. If a performance issue should crop up, then fix it. Don't restructure your data to solve problems that do not yet exist and may never exist.
Well, give this a try and see how it works.
I'm trying to make 2 "one to many" relationships on the same table, as I have employees managed by TLs supervised by SPVs.
Employees table consists of (ID -PK- , name , hire_date ,,,,,) it's the same data for TL and SPV also
on the ERD I've made it as a one to many on the same table but I've no idea how to make it on SQL (I can't detect what should be refer to who)
I've thought about giving up the one to many relationship idea and add a "type" as 1 if employee and 2 if TL and 3 if SPV but i need to know every TL's employees for example (TL#1 have the employees John , Paul, Smith ,... in his team) and so on
**Note: I'm not sure if it's the right thing to make it as a one to many relationship , if there's any other way I'd really appreciate it :)
sql? just put the same table in the REFERENCES clause.
if you can't do it, do it in a separate SQL sentence.
CREATE TABLE EMPLOYEE (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
spv INTEGER UNSIGNED NULL,
...,
PRIMARY KEY(id)
);
ALTER TABLE employee ADD FOREIGN KEY ( spv ) REFERENCES employee (id) ON DELETE RESTRICT ON UPDATE CASCADE
If this question is a little vague just let me know and I will provide more info.
I have written a query that gets data from multiple tables but it isn't working how I expected it too and I am completely stumped.
Here is my code:
SELECT students.student_fname, students.student_lname
FROM students, enrolments
WHERE enrolments.courseID = 'C001';
but this just returns all of the students first and last names in the students table and these names are displayed twice.
Here is the code for the two tables:
CREATE TABLE students
(
studentID CHAR(10) NOT NULL,
student_fname VARCHAR(15) NOT NULL,
student_lname VARCHAR(15) NOT NULL,
DOB VARCHAR(10) NOT NULL,
CONSTRAINT pk_students PRIMARY KEY (studentID)
);
CREATE TABLE enrolments
(
enrolmentNo int NOT NULL AUTO_INCREMENT,
studentID CHAR(10) NOT NULL,
courseID CHAR(4) NOT NULL,
CONSTRAINT pk_enrolments PRIMARY KEY (enrolmentno),
FOREIGN KEY (studentID) REFERENCES students (studentID),
FOREIGN KEY (courseID) REFERENCES courses (courseID)
)ENGINE = INNODB;
This is because you've not defined how students relate to enrolments.
You either need to use an inner join or add a where clause that show how they relate.
For example:
FROM Students
INNER JOIN enrolments on Students.ID = enrolments.studentID
Or
FROM students, enrolements
WHERE enrolments.studentID = students.ID
The first method is newer and preferred by many; but the legacy method is also supported.
To gain a better understanding of table joins take a look at this most excellent article: http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html
Otherwise you get what is called a Cartesian product of the two tables all data relates to all data.
If you do not have a unique index on enrolements (a student can only have 1 class enrolment) then a select Distinct field names or where... group by fields will also limit your results to close to what you're looking for as well.
============================ in response to follow-up comment===================
SELECT S.student_fname, S.student_lname
FROM students S
INNER JOIN enrolments E
ON S.StudentID = E.StudentID
WHERE e.courseID = 'C001'
GROUP BY S.Student_Fname, S.Student_lname
ORDER BY S.Student_LName, S.Student_FName
The group by eliminates duplicates for the same course.
The "ON statement tells the database how students relates to enrolments.
The order by is just to provide a reasonable order to results.
You cannot fetch data from two tables in this way. You have to join tables.