Retrieve Columns from Relational Database at Once - mysql

In order to simplify the problem, let's assume this is for a school database, and we have 3 tables. In this school the student can participate in multiple courses which have multiple lessons, and we store in the DB the information about the available courses, each course lessons and which courses and lessons the student is currently studying.
Table courses has 2 columns, cs_id and cs_name. Table lessons has 3 columns, ls_course which stores the ID of the course the lesson is from, ls_number —a sequential number that identifies the lesson within the course— and ls_name. The last table students stores the student ID, st_id, the course ID and current lesson in that course, st_course and st_lesson. Some sample data:
courses
+-------+----------+
| cs_id | cs_name |
+-------+----------+
| 1 | Course A |
| 2 | Course B |
+-------+----------+
lessons
+-----------+-----------+--------------+
| ls_course | ls_number | ls_name |
+-----------+-----------+--------------+
| 1 | 1 | Lesson One |
| 1 | 2 | Lesson Two |
| 1 | 3 | Lesson Three |
| 2 | 1 | Lesson One |
| 2 | 2 | Lesson Two |
| 2 | 3 | Lesson Three |
+-----------+-----------+--------------+
students
+-------+-----------+-----------+
| st_id | st_course | st_lesson |
+-------+-----------+-----------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 2 | 2 | 2 |
+-------+-----------+-----------+
As you can see we currently have 2 students, and the student with ID 2 is currently taking 2 courses at once. In Course A, she is in the third lesson and in Course B, in Lesson Two. What I want is to retrieve the listing for the courses one student is currently in. But not as:
SELECT * FROM students WHERE st_id = 2;
This returns the filtered rows, but I'd like to retrieve the course and lesson ID and names for them. To get the course name column I'd do this:
SELECT s.*, c.cs_name
FROM students s, courses c
WHERE s.st_id = 2 AND s.st_course = c.cs_id
Which results in:
+-------+-----------+-----------+----------+
| st_id | st_course | st_lesson | cs_name |
+-------+-----------+-----------+----------+
| 2 | 1 | 3 | Course A |
| 2 | 2 | 2 | Course B |
+-------+-----------+-----------+----------+
The far I can get is:
SELECT s.*, c.cs_name, l.ls_name
FROM students s, courses c, lessons l
WHERE
s.st_id = 2 AND
s.st_course = c.cs_id AND
s.st_lesson = l.ls_number
But how can I retrieve the lesson ID and chapter to check for a match in the lessons table, is it possible at all? The result I want is:
+-------+-----------+-----------+----------+--------------+
| st_id | st_course | st_lesson | cs_name | ls_name |
+-------+-----------+-----------+----------+--------------+
| 2 | 1 | 3 | Course A | Lesson Three |
| 2 | 2 | 2 | Course B | Lesson Two |
+-------+-----------+-----------+----------+--------------+

This query is wrong:
SELECT s.*, c.cs_name, l.ls_name
FROM students s, courses c, lessons l
WHERE
s.st_id = 2 AND
s.st_course = c.cs_id AND
s.st_lesson = l.ls_number
You need also to match st_course to ls_course
SELECT s.*, c.cs_name, l.ls_name
FROM students s, courses c, lessons l
WHERE
s.st_id = 2 AND
s.st_course = c.cs_id AND
(s.st_lesson = l.ls_number AND st.st_course=l.ls_course)
I would also recommend using the current JOIN syntax, i.e.
SELECT s.*, c.cs_name, l.ls_name
FROM students s
JOIN courses c ON c.cs_id=s.st_course
JOIN lessons l ON (s.st_lesson=l.ls_number AND st.st_course=l.ls_course)
WHERE s.st_id = 2

Related

MySQL - join multiple mapped tables and count records with different mapping conditions

It's the 3rd day I'm trying to write a MySQL query. Did lots of search, but it still doesn't work as expected. I'll try to simplify tables as much as possible
System has tkr_restaurants table:
restaurant_id | restaurant_name
1 | AA
2 | BB
3 | CC
Each restaurant has a division assigned (tkr_divisions table):
division_id | restaurant_id | division_name
1 | 1 | AA-1
2 | 1 | AA-2
3 | 2 | BB-1
Then there are meals in tkr_meals_to_restaurants_divisions table, where each meal can be assigned (mapped) to whole restaurant(s) and/or specific division(s). If meal is mapped to restaurant, all restaurant's divisions should see it. If meal is mapped to division(s), only specific division(s) should see it.
meal_id | mapped_restaurant_id | mapped_division_id
1 | 1 | NULL
2 | NULL | 1
3 | NULL | 2
I need to display a list of restaurants and number of meals mapped to it depending on user permissions.
Example 1: if user has permissions to access whole restaurant_id 1 and restaurant_3 (and no specific divisions), then list should be:
AA | 3
CC | 0
(because user can access meals mapped to restaurant 1 + all its division, and restaurant 3 + all its divisions (even if restaurant 3 has no divisions/meals mapped))
Example 2: if user has permissions to access only division_id 1, then list should be:
AA | 1
(because user can only access meals mapped to division 1).
The closest query I could get is:
Example 1:
SELECT *,
(SELECT COUNT(DISTINCT meal_id)
FROM
tkr_meals_to_restaurants_divisions
WHERE
tkr_meals_to_restaurants_divisions.mapped_restaurant_id=tkr_restaurants.restaurant_id
OR tkr_meals_to_restaurants_divisions.mapped_division_id=tkr_divisions.division_id)AS total_meals
FROM
tkr_restaurants
LEFT JOIN
tkr_divisions
ON tkr_restaurants.restaurant_id=tkr_divisions.restaurant_id
WHERE
tkr_restaurants.restaurant_id IN (1, 3)
OR tkr_restaurants.restaurant_id IN (
SELECT restaurant_id
FROM tkr_divisions
WHERE division_id IN (NULL)
)
GROUP BY
tkr_restaurants.restaurant_id
ORDER BY
tkr_restaurants.restaurant_name
However, result was:
AA | 2
CC | 0
I believe I'm greatly over-complicating this query, but all the simpler queries I wrote produced even more inaccurate results.
What about this query:
SELECT
FROM tkr_restaurants AS a
JOIN tkr_divisions AS b
ON a.restaurant_id = b.restaurant_id
LEFT OUTER JOIN tkr_meals_to_restaurants_divisions AS c
ON (c.mapped_restaurant_id = a.restaurant_id OR c.mapped_division_id = b.division_id)
As a Base four your further work. It combine all information into one table. If you add e.g. this:
WHERE a.restaurant_id IN (1, 3)
the result will be
| restaurant_id | restaurant_name | division_id | restaurant_id | division_name | meal_id | mapped_restaurant_id | mapped_division_id |
|---------------|-----------------|-------------|---------------|---------------|---------|----------------------|--------------------|
| 1 | AA | 1 | 1 | AA-1 | 1 | 1 | (null) |
| 1 | AA | 2 | 1 | AA-2 | 1 | 1 | (null) |
| 1 | AA | 1 | 1 | AA-1 | 2 | (null) | 1 |
| 1 | AA | 2 | 1 | AA-2 | 3 | (null) | 2 |
just count the distinct meal ids with COUNT(DISTINCT c.meal_id) and take the restaurant name to get AA: 3 for your example 2
I used a sqlfiddle: http://sqlfiddle.com/#!9/fa2b78/18/0
[EDIT]
Change JOIN tkr_divisions AS b to LEFT OUTER JOIN tkr_divisions AS b
Change SELECT * to SELECT a.restaurant_name, COUNT(DISTINCT c.meal_id)
Add a GROUP BY a.restaurant_name at the end.
Update the SQL Fiddle (new link)

Getting multiple rows as a single row in MySQL

I have a basic multi-school management system that I am working on for fun in order to learn querying from a MySQL database.
I am doing a number of joins from tables such as students, schools,student_grade, grade_school etc. I now want to get a list of all subjects the student has been assigned. Since there are multiple possible rows, I have a student_subject table to hold the student_id and then the subject_id values.
The following query gets me the student's name, and their grade:
SELECT a.id,a.firstnames,a.surname,d.gradename
FROM students a
LEFT JOIN student_grade b ON a.id=b.studentid
LEFT JOIN grade_school c ON c.id=b.gradeid
LEFT JOIN grades d ON c.gradeid=d.id
WHERE a.schoolid=? AND b.gradeid=? ORDER by a.surname ASC
This results in each row returned to be the student's name and their grade, like this:
ID | Firstname | Surname | Grade
1 | John | Doe | 4
5 | Sarah | Marshall | 7
How can I get all the subjects the student is assigned and put them all in a column "subject_ids" to get a result as follows:
ID | Firstname | Surname | Grade | Subjects
1 | John | Doe | 4 | 5,54,2,56
5 | Sarah | Marshall | 7 | 2,4,12,17
My table structures are as follows:
Table `students`
id | firstnames | surname | schoolid
Table `student_grade`
id | studentid | gradeid
Table `grade_school`
id | gradeid| schoolid
Table `grades`
id | gradename|
Table `student_subject`
id | studentid| subjectid
4 | 1 | 5
5 | 1 | 54
6 | 1 | 2
7 | 1 | 56
8 | 5 | 2
9 | 5 | 4
10 | 5 | 12
11 | 5 | 17
Try this:
SELECT a.id,a.firstnames,a.surname,d.gradename, GROUP_CONCAT(s.subjectid)
FROM students a
LEFT JOIN student_grade b ON a.id=b.studentid
LEFT JOIN grade_school c ON c.id=b.gradeid
LEFT JOIN grades d ON c.gradeid=d.id
LEFT JOIN student_subject s ON s.studentid=a.id
WHERE a.schoolid=? AND b.gradeid=? ORDER by a.surname ASC
also check this or this
The GROUP_CONCAT() function will do what you want here.
This question is a dup of Can I concatenate multiple MySQL rows into one field?

mysql: query multiple records in 3 tables?

Good day fellow programmers. I have 3 tables, with following sample records.
tbl_members has:
mem_id | mem_fname | mem_lname
1 | Ryan | Layos
2 | Dhave | Sebastian
3 | Staven | Siegal
4 | Ma Ethel | Yocop
5 | Kelvin | Salvador
6 | Herbert | Ares
tbl_member_status has:
status_id | mem_id | leader_id | process_id
1 | 2 | 1 | 2
2 | 3 | 5 | 3
3 | 4 | 6 | 4
4 | 5 | 1 | 4
5 | 1 | 6 | 4
(tbl_member_status.mem_id is foreign keyed to tbl_members.mem_id, and leader_id is also foreign keyed to tbl_members.mem_id because in my case a member can be a leader. 1 member 1 leader)
tbl_process has:
process_id | process_type
1 | CONSOLIDATION
2 | PRE-ENCOUNTER
3 | ENCOUNTER
4 | POST-ENCOUNTER
(a member has a process to take which i used enum with values: CONSOLIDATION, PRE-ENCOUNTER, ENCOUNTER, POST-ENCOUNTER, etc.)
My question now is the proper sql query in getting the desired output query like this.
tbl_query_result
mem_id | member_fname | member_lname | leader_fname | leader_lname | process_type
2 | dhave | sebastian | Ryan | Layos | PRE-ENCOUNTER
5 | Kelvin | Salvador | Ryan | Layos | POST-ENCOUNTER
do remember that two columns of tbl_member_status is referring to one column of tbl_members that is mem_id.
UPDATE:
what i have done so far:
SELECT member.mem_fname, member.mem_lname, leader.mem_fname, leader.mem_lname, tbl_process.process_type
FROM
tbl_member_status as mem_stats
INNER JOIN
tbl_members as member
INNER JOIN
tbl_members as leader
INNER JOIN
tbl_members ON mem_stats.member_id = member.mem_id
INNER JOIN
tbl_process ON tbl_process.process_id = mem_stats.process_id
WHERE
leader.mem_fname = 'Ryan'
This query gets all record even if the leader.mem_fname is not equal to 'Ryan'
Because when you query. The number of rows in the result matters. Like: if your result is for fname = ryan but then the match for mem.id in table memberstatus is two and then in process table is again two. Inshort you will have 2 rows in final output.
Can you try this :
Select M.member_fname, M.mem_lname P.process_type from tbl_members M, tbl_member_status MS, tbl_process P where M.mem_id = MS.mem_id and MS.process_id = P.process_id and where M.member_fname = 'ryan'
Okay i misunderstood your question at first. I have a solution for you which will improve your database schema. If one member has only one leader and a single leader has many memebers. Then why not create a different table called leader and connect to members table directly? So it will be a one to one relation. Which will make querying much simpler. So now you have 4 tables.
Select M.member_fname, M.mem_lname, L.fname, L.lname, P.process_type
from tbl_members M, tbl_member_status MS, tbl_process P, tbl_leader L
where M.leader_id = L.id and M.mem_id = MS.mem_id and MS.process_id = P.process_id and where M.member_fname = 'ryan'

Getting all results from 3 tables without repetition

I have 3 tables: sports, venues and instructors, they all have an id and a name. Instead of having 3 separate SELECT * FROM table I want to fetch the results with a single query so that the result is something like:
+----------+------------+----------+------------+---------------+-----------------+
| sport_id | sport_name | venue_id | venue_name | instructor_id | instructor_name |
+----------+------------+----------+------------+---------------+-----------------+
| 1 | Sport1 | 1 | Venue1 | 1 | Instructor1 |
| 2 | Sport2 | 2 | Venue2 | 2 | Instructor2 |
| 3 | Sport3 | | | 3 | Instructor3 |
| 4 | Sport4 | | | | |
+----------+------------+----------+------------+---------------+-----------------+
I tried
SELECT * FROM sports, venues, instructors
However there are at ton of repetitions.
EDIT: The 3 tables are not related to each other in any way.
Although I see limited use for getting all the values in one query, you could alway use UNION ALL to get all the values;
SELECT sport_id id, sport_name name, 'sport' type FROM sports
UNION ALL
SELECT venue_id id, venue_name name, 'venue' type FROM venues
UNION ALL
SELECT instructor_id id, instructor_name name, 'instructor' type FROM instructors
This will give output in the format;
id name type
-------------------------------
1 sport1 sport
2 sport2 sport
3 sport3 sport
4 sport4 sport
1 venue1 venue
2 venue2 venue
1 instructor1 instructor
2 instructor2 instructor
3 instructor3 instructor

mysql: remove rows with repeated values with condition (repeated columns)

I have a table "player" as follow
where:
ID is primary key.
date = date they play (just for 1 month, so could from 1 to 31)
Name = name of the players
Sport = sport they play and there can be many sports in the list; but i only focus on the one who play "football" and play more than 2 games in one day
This is the table "player".
+----+------------+-------+-------------+
| ID | Date | Name | Sport |
+----+------------+-------+-------------+
| 1 | 1 | A | football |
| 2 | 1 | A | soccer |
| 3 | 3 | A | tennis |
| 4 | 2 | B | tennis |
| 5 | 2 | B | football |
| 6 | 1 | C | basketball |
| 7 | 1 | C | tennis |
| 8 | 1 | C | fishing |
| 9 | 4 | D | football |
+----+------------+-------+-------------+
I want to find list of the people (name and sport) who DO NOT:
practice "football" + other sport(s) in one day.
note: if someone who play "football" + other game(s) in one day, we remove him from the list for that day. only remove him for that particular day.
So the result should be like this,
+----+------+------+-----------+
| ID | Date | Name | Sport |
+----+------+------+-----------+
| 3 | 3 | A | tennis |
| 6 | 1 | C | basketball|
| 7 | 1 | C | tennis |
| 8 | 1 | C | fishing |
| 9 | 4 | D | football |
+----+------+------+-----------+
This is the follow up problem listed
mysql: find rows with repeated values plus condition
thank you for helping !
You should be looking for this:
Here, we are omitting the key record values (date + name) those match in an intersection of key record values (date + name) who played football and key record values (date + name) who did not play football
SELECT
p1.*
FROM
player p1
LEFT JOIN
(SELECT
pnfb.*
FROM
(SELECT
date, name
FROM
player
WHERE
sport <> 'football') pnfb
JOIN (SELECT
date, name
FROM
player
WHERE
sport = 'football') pfb ON (pnfb.date = pfb.date
AND pnfb.name = pfb.name)) p2 ON (p1.date = p2.date AND p1.name = p2.name)
WHERE
p2.date IS NULL;
If I've understood correctly, you want to obtain the dates and names of players who on that date either played only one sport or did not play football:
SELECT Date, Name
FROM player
GROUP BY Date, Name
HAVING COUNT(DISTINCT Sport) = 1
OR NOT SUM(Sport='football')
See it on sqlfiddle.
If you want to see which sports they did play and/or obtain the ID of the relevant records, you can join the above back to your player table:
SELECT * FROM player NATURAL JOIN (
SELECT Date, Name
FROM player
GROUP BY Date, Name
HAVING COUNT(DISTINCT Sport) = 1
OR NOT SUM(Sport='football')
) t
See it on sqlfiddle.