I'm writing an online application form and one of the pieces of data I'm trying to collect is the users education history; school name, school city, years they were there and achievements while there. This data could be collected for anywhere between zero to about four different schools and the user will be dynamically adding more fields if required.
My question is how would this be captured within the database since I don't know how many schools the user will be adding? Can it be done in one table, or do I need to create multiple tables and capture the education data in a separate table?
Any help or pointers would be greatly appreciated.
Thanks.
You need separate tables. One with a row per person, and another with a row per school. Schools can be related to people using either a personID in the school table, or an additional table representing the "person attended school" relationship.
Any association of one entity (person) with multiple other entities (schools) requires more than one table.A general rule to follow is one table per "entity", plus additional tables for many-to-many relationships.
Make a separate table for education, and model an "education" in php as a separate class.
The term for this is Normalization. You want to structure multiple tables such that one row of data is succinct, and as little information is repeated as possible. This can be done by figuring out in your head which pieces of information will be repeated, and making a table for that information.
Data between tables is linked via primary keys (hopefully auto increment integers). In this case, you would have two tables: Student and Education. The Student table would have 'StudentID, Name, etc' while the Education table would have 'EducationID, StudentID, SchoolName, SchoolCity, SchoolStudentID, etc'.
Note that Student.StudentID would match with Education.StudentID and can be acquired with the following query:
select Student.*, Education.* from Student left join Education on Student.StudentID = Education.StudentID
In fact, this will have 'SchoolName' and 'SchoolCity' repeating over and over, so this is not fully normalized. To continue, you need to change it a little bit again:
Student: StudentID, Name, etc
School: SchoolID, SchoolName, SchoolCity, etc
Education: StudentID, SchoolID, StudentSchoolID, etc
Note: each student has a different student id at each school they attend - don't confuse this with the auto increment integer 'StudentID'. To get a list in this case:
select Student.*, Education.StudentSchoolID, School.* from
Student left join Education on Student.StudentID = Education.StudentID
left join School on Education.SchoolID = School.SchoolID
Related
A subset of my database contains 3 tables
users
user_details
tasks
The main column of the user table is:
id {PK}
The user details table contains information about the users (only some users have these details, students). Thus the main columns of the users details table are:
user_id{PK,FK} | supervisor_id {FK}
The supervisor_id is an id in the users table. Each student has one supervisor.
Lastly, there is the tasks table where only students create tasks and the main columns of the tasks table are:
task_id{PK} | user_id{FK}
The problem I am having is getting a proper query for, if a supervisor wants to see all his students tasks. I know you can query all the students in the user_details table who have the supervisor's id. Then create another query where you select all the tasks whose user_id matches that of the first query performed.
This does not seem like a very efficient was to go about achieving this result. Are there better alternatives?
select ud.supervisor_id, ud.user_id, t.task_id
from user_details ud, users u
where ud.user_id = t.user_id
What you are looking for is a join. Instead of writing two separate queries to get the information, a join will allow you to connect the tables that you have in one query and get the information you need much faster.
Select *
From user_details ud
join tasks t
on ud.user_id = t.user_id
Where ud.supervisor_id = ?
The join essentially allows you to create one big table out of all of the columns of the tables you are using. The on keyword tells sql which values go together, so that you know all of the tasks belong to the student whose id matches the id that the supervisor has. Then, you can select whatever columns you like out of either table (as well as a lot of other fancy things).
I have two tables:
teachers(teacher_id, name, ... )
teach_subjects(teacher_id, subject_id, ... )
In teach_subject the teacher_id is not unique since a teacher can teach more than one subject.
What I want, is to select a list of teachers together with the subjects they teach.
The SQL-query I use is
SELECT teacher_id, GROUP_CONCAT(DISTINCT subject_id SEPARATOR ',') AS subjects, [select some more here]
FROM teachers LEFT JOIN teach_subjects USING(teacher_id)
GROUP BY teacher_id;
This selects the ids of subjects as a string for each teacher.
In PHP I use explode to get an array of subject ids for each teacher. This works fine, but I am wonder if this is really the way I have to go?
I only see one other way: Split the query into two queries (one for selecting the teacher data, one for selecting the subjects)
Edit: Not grouping the data and so getting the rows multiple times with only different subject_id is not an option, due to I'm selecting some other big data, so I get a too big overhead.
Notice: This is just a simplification of the queries I really use. I just want to keep it simple for now.
I'm trying to set up a high scores board for a game I'm making. On the board, I'm using foreign keys to two other boards, players and weapons. Each score stores the four weapons the player used on that run. The tables are set up like this:
Scores
id|playerid|score|weapon0id|weapon1id|weapon2id|weapon3id
Players
id|name
Weapons
id|name
I want to select multiple rows from the scores table with ids replaced by the appropriate names. I'm able to get the correct player name and one weapon using this statement:
SELECT scoreID, Players.playerName, scoreVal,
Weapons.weaponLabel, scoreW1, scoreW2, scoreW3
FROM Scores, Players, Weapons
WHERE Players.playerID = scorePlayer AND Weapons.weaponID = scoreW0
Everywhere I've looked shows that to be the best way to get a value from a row referred to by a foreign key. It works fine for the player name, but there seems to be no way to expand this to fill in multiple weapon names at once. Using an OR with the remaining weapons or using weaponID IN (w0,w1,w2,w3) seems to get one row for each weapon, not one row with each weapon in the appropriate spot.
Is there any way to get the correct weapon names just using the select statement? Or will I need to have extra code loop through and replace each weapon id with the correct name?
This design is questionable: weapon0..n will likely lead to nothing but difficult queries like this. The queries will also have to be de-normalized - e.g. one join per weapon0..n.
Anyway, the query is wrong and will return many more rows than desired because it uses the form FROM a,b which implies a CROSS JOIN between a and b and there is not appropriate selectors in the WHERE to make it an equi-join. Try to use a normal (INNER) JOIN and ON to make each join more apparent:
SELECT s.scoreID, p.playerName, s.scoreVal,
w0.weaponLabel as w0Label,
w1.weaponLabel as w1Label
-- etc
FROM Scores s
JOIN Players p ON p.id = s.playerID
JOIN Weapons w0 ON w0.weaponID = s.scoreW0
JOIN Weapons w1 ON w1.weaponID = s.scoreW1
-- etc, ick!!!
By now it should become apparent why the de-normalized data is icky!
Each column must be joined with a different relation (w0, w1, etc).
I usually have to create a looping procedure to get all the denormalized columns in one row per unique set, in your case player, weaponlabel.
I have a mySQL table entitled users. It has a UID, rating, username, password, etcetera.
My goal is to make a system (tribes) similar to Facebook's friends list. Each user will be able to view the profile of users and add them to their tribe. Each user is the chief of only one tribe, but can be the villager of as many tribes as he/she wants to be. The rating system would take into account all of the tribe's members ratings.
After doing some research on relational database tables and grouping, I am not clear about how I should go about setting up the tables, or the PHP code that would go along with that.
If someone can get me pointed in the right direction, it'd be much appreciated!
EDIT: One of the big problems I foresee is accepting to be in a tribe. I'm not sure how you account for this.
similar to Facebook's friends list. Each user will be able to view the profile of users and add them to their tribe. Each user is the chief of only one tribe
Okay, so you have a User table, and also will need a Tribe table.
Then the relations you're describing are a chief-of, which is one-to-one (one user can be chief of one tribe; one tribe has only one chief), therefore you can either store this within User (chief_of: Tribe) or within Tribe (chief: User).
CREATE TABLE User ...
chief_of integer
Here, chief_of might be a foreign key so that if you delete a tribe, the relevant tuple will have its chief_of set to NULL (a user can't be chief of a no longer existing tribe).
The membership is a bit more complicated because one user can belong to several tribes, and a tribe will have more than one member.
This is a many-to-many relationship and is usually done with a table holding key pairs:
CREATE TABLE member_of (
user_id integer,
tribe_id integer
);
Both fields are natural candidates for foreign keys. Here you can find a similar implementation using Authors and Books.
To indicate that Bob is a member of the Clan of the Cave Bear, you retrieve the ids of Bob and Bears, and insert a tuple in member_of.
To retrieve all members of the clan, you can use a JOIN:
SELECT Users.* FROM Users
JOIN member_of ON (Users.user_id = member_of.user_id)
WHERE member_of.tribe_id =
(SELECT tribe_id FROM Tribes WHERE tribe_name = 'Clan of the Cave Bear');
I think that a shorter version of that ON in MySQL is USING(user_id) (meaning that both tables have an identical column identically named), but in my opinion the ON is clearer.
You can also retrieve a virtual "is_chief" column:
SELECT Users.*, chief_of = tribe_id AS is_chief FROM Users
JOIN member_of ON (Users.user_id = member_of.user_id)
WHERE member_of.tribe_id =
(SELECT tribe_id FROM Tribes WHERE tribe_name = 'Clan of the Cave Bear');
The one user whose chief_of attribute is equal to the tribe id will have is_chief set to TRUE, which is equal to 1, so
SELECT Users.*, chief_of = tribe_id AS is_chief FROM Users
JOIN member_of ON (Users.user_id = member_of.user_id)
WHERE member_of.tribe_id =
(SELECT tribe_id FROM Tribes WHERE tribe_name = 'Clan of the Cave Bear')
ORDER BY (chief_of = tribe_id) DESC, user_name;
will retrieve the users in alphabetical order, except the chief, who, if present, will come first.
As for the acceptance into the tribe, this identifies three states: a user is not in a tribe, a user is in the tribe, a user asked to be in a tribe. The first two are actually two faces of the same attribute member_of. So naturally we might create a new attribute and call it wants_in. It would map to a table identical to member_of.
A chief could retrieve all tuples of wants_in whose tribe_id is equal to his own chief_of (so if it's NULL, meaning he's not a chief, this will automatically return nothing). Then he might see this as a table of checkboxes with user names. When he approves the join, for each approval you delete the tuple from wants_in and put it into member_of.
Or you might decide that "membership" is a state in itself, so that you have a more complex join table
user_id,
tribe_id,
status
where status could be, say,
nothing (there's no (U, T, ?) tuple): user U and tribe T are unknown to each other
100: user U is full member of tribe T
-1 : tribe T has decided that U is not a member and cannot even ask to be.
0: user U wants to be member of T
1-99: user U is a probationary (apprentice) member.
It sounds like you'll need two classes: Villager and Tribe.
Then, maybe a database field called chief_of or something that is null if the person is not a chief, and contains the name of their tribe that they are the chief.
In the classes you can have a getChiefOf() method that can be tested to see if the user is a chief or not.
Alternatively, you could have a table of chiefs, indexed by UID, with the same column that says which tribe they're chief of. A little less efficient but a better structure. A drawback that jumps to mind is if the UID for some reason is changed, two tables would have to be updated, so maybe the first one's better.
I have the following entities:
EMPLOYEE (id, fname, lname, ...)
PROJECT (id, ...)
EMPLOYEE_PROJECT (employee_id, project_id, ...)
An employee can work on more than one project, and a project can be worked on by more than one employee, so the EMPLOYEE_PROJECT table is used to relate them.
I would like to generate in my application (PHP/MySQL) a form for a given PROJECT ID with a checkbox next to each employee name, that will display as checked if an entity for that employee id exists for that project id in the EMPLOYEE_PROJECT table, and that will allow the user to check or uncheck any employee and then click an "update" button to add/remove the selected/unselected employees from the project by adding/removing the entries on the EMPLOYEE_PROJECT table.
I am able to use an IF statement to display "checked" on any entity that exists in the EMPLOYEE_PROJECT table. My problems are:
Properly forming the query to get the list. If I get the list of employees by a JOIN of EMPLOYEE ON EMPLOYEE_PROJECT, it only shows employees who have been involved in a project, not all employees. I can do a LEFT JOIN to show all, but that would repeat names of employees who have multiple projects, and I only want to show each name once. If I add a GROUP BY employee.id, I can get a proper list of only one of each employee, however the results don't necessarily match the correct project_id. If I add a HAVING with the project_id, I lose employees who are not on the project (keep in mind I want to list ALL employees, but only show a check next to the ones that are currently on the project).
Assuming I figure out #1 and can list all employees and display a check next to the ones which exist as an EMPLOYEE_PROJECT entity for the specified project, how can I allow the check boxes to be modified by the user, and then submitted and have the one submit action add/remove the relevant entities from the EMPLOYEE_PROJECT table?
Thanks so much!!
#zzarbi is correct for #2. For #1 you need a sub-query:
SELECT employee.*, (SELECT count(id) FROM employee_project
WHERE project_id=<project_id> AND employee_id=employee.id)
AS in_project
FROM employee
For #1 "list ALL employees, but only show a check next to the ones that are currently on the project". (That work only if you show only one project at the time)
SELECT * from employee as e
LEFT JOIN EMPLOYEE_PROJECT as ep on ep.employee_id = e.id
WHERE ep.project_id = <project id>
in php :
if("employee_id" IS NOT NULL/empty){
echo "check";
}
For #2, usually my middle table project_employee has nothing else than two id (employeeId, projectId). So i generally just delete every employee for this projectId and re-add them.
If you have other data in the same table, this might not work. And you will have to compare old data store to new data to be stored.