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.
Related
I am developing a personal proyect for academic books. I have some tables with +30.000 rows each for works, editions, authors and so on. All the information of the books —genres, subjects, authors, publishers, etc— is spread over a lot of tables with different types of relations.
I have a query for the main page that works, but the site takes six seconds to load. A lot of time… I was wondering which would be the proper approach for obtaining all the data I need with temporary tables.
What I want to do now is to join the temporary table _work with the related data of another table, say «genre». But the relationship between «work» and «genre» is done with the temporary table «work_has_genre».I know how to do that with normal tables in a single query:
SELECT *
FROM work a
LEFT JOIN (
SELECT GROUP_CONCAT(f_a.id SEPARATOR '|') AS genre_id, GROUP_CONCAT(f_a.genre SEPARATOR '|') AS genre_name, f_b.work_id AS _work_id
FROM genre f_a
INNER JOIN (
SELECT *
FROM work_has_genre f_b_a
) f_b
ON f_a.id=f_b.genre_id
GROUP BY f_b.work_id
) f
ON a.id=f._work_id
WHERE a.id=13
I suppose the idea would be to break this actions in parts, but I don't know how. Could someone help me with a bit of pseudocode? Or maybe this is not the best approach. Any idea will be very welcomed!
A.
As I said in comments, I would first suggest reworking/flattening the subqueries as much as possible first, but once you get to semi-independent aggregations temp tables can be helpful.
Generally, the pattern is to put each such aggregation subquery's results into it's own temp table (with an index on the field the subquery was joined to the main query on) even if that means adding tables (and the main query's WHERE) to the original subquery, and then joining to the temp table in the main query.
I have one student table links to two scores tables which are exactly the same structures. The only different is that one table will stores high scores, the other table stores low scores. Now I need to query both high and low score tables, it will list all subjects scores with student name if it is from high score table and scores with name is student if its from low score table, and I need to order the result by time.
SELECT u.student_name,
a.subject1_score,
a.subject2_score,
a.subject3_score,
a.subject4_score,
a.subject5_score,
a.exam_date
FROM Student u
INNER JOIN High_Score_Table a
On u.student_id = a.student_id
ORDER BY a.exame_date = time
Then for low_score_Table I will have almost same query except that the student name will equal to Student by default.
Then I will need to put this together in a list and order by time. How could I do that shorter and better ?
Btw, I can merge two tables low and high_score into one, and add a column called "flag" into that, whenever the flag value equal to "show" then I show student name with all score records, else "hidden" I will just show "Student" and all score records. How could I do that in one query ?
It sounds like you need a UNION, because you are concatenating two distinct result sets - one from High_Score_Table and one from (presumably) Low_Score_Table:
select s.student_name,
h.subject1_score,
h.subject2_score,
h.subject3_score,
h.subject4_score,
h.subject5_score,
h.exam_date
from High_Score_Table h
join Student s on h.student_id = u.student_id
union all
select 'student' as student_name,
l.subject1_score,
l.subject2_score,
l.subject3_score,
l.subject4_score,
l.subject5_score,
l.exam_date
from Low_Score_Table l
order by exam_date
The takeaway here is an ORDER BY clause in a union sorts over the entire result set - which is in this case exactly what you want.
Sorry for my bad english
i have a forms table in which i have columns like competition_name and team_name
secondly i have members table
each member has columns mem_name, mem_university , mem_team
means from has team_name and members also have team_name
one team can have several(upto 5) team members, i need to fetch all forms with their respective members in single query, and it should come into one array so that i can generate a table or a list, any idea?
which join should i use in this case, any dummy query for this scenario will be helpful
May be like this
SELECT forms.*,members.*
FROM forms LEFT JOIN members ON forms.form_id=members.form_id
GROUP BY forms.form_id
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'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