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?
Related
I'm new to sql and do not understand what this join statement is doing. Does this statement ON people.state_code=states.state_abbrev mean that people.state_code and states.state_abbrev are now one?
SELECT people.first_name,
people.state_code, states.division
FROM people
JOIN states ON people.state_code=states.state_abbrev;
It will take the columns first_name and state_code from the table people and the column division from the table states and put them together in a join table where the entries in the state_code and state_abbrev columns match. The join table is produced only for display in response to this query; the underlying tables with the data entries are not amended.
In this case the '=' means equal (like values are equal) and is part of the join condition based on which data is retrieved by the select statement. You are 'linking' the two tables based on a condition so you can retrieve related data...
Relational data base - there are relations between tables and between data.
For example:
table_1
PERSON_ID FIRST_NAME LAST_NAME ADDRESS_ID
1 |John |Doe |2
table_2
ADRESS_ID STREET
1 | 5th Avenue
2 | 1st Street
SELECT FIRST_NAME, STREET
FROM table_1 t1
JOIN table_2 t2 ON t1.ADDRESS_ID = t2.ADDRESS_ID;
will return
John, 1st Street
Does this statement ON people.state_code=states.state_abbrev mean that people.state_code and states.state_abbrev are now one?
Answer: NO. people.state_code and states.state_abbrev should be the same value on the respective tables.
Let me give you an example taken from https://www.mysqltutorial.org/mysql-join/
Suppose you have below tables:
CREATE TABLE members (
member_id INT AUTO_INCREMENT,
members_name VARCHAR(100),
PRIMARY KEY (member_id)
);
CREATE TABLE committees (
committee_id INT AUTO_INCREMENT,
committees_name VARCHAR(100),
PRIMARY KEY (committee_id)
);
Some data examples:
+-----------+--------+
| member_id | members_name |
+-----------+--------+
| 1 | John |
| 2 | Jane |
| 3 | Mary |
| 4 | David |
| 5 | Amelia |
+-----------+--------+
+--------------+--------+
| committee_id | committees_name |
+--------------+--------+
| 1 | John |
| 2 | Mary |
| 3 | Amelia |
| 4 | Joe |
+--------------+--------+
To do the INNER JOIN we can use members_name and committees_name not the id because they are auto_increment and the data are not related.
So the query would be:
SELECT
m.member_id,
m.members_name AS member,
c.committee_id,
c.committees_name AS committee
FROM members m
INNER JOIN committees c ON c.name = m.name;
Giving below result:
+-----------+--------+--------------+-----------+
| member_id | member | committee_id | committee |
+-----------+--------+--------------+-----------+
| 1 | John | 1 | John |
| 3 | Mary | 2 | Mary |
| 5 | Amelia | 3 | Amelia |
+-----------+--------+--------------+-----------+
Conclusion: The values of the columns are equaly the same
I have a MySQL problem which i can't figure out the solution.
I have 2 tables
Table 1-[Book] table
book id | categories | title |
1 | 1,3,5 | Book 1 |
2 | 2,4 | Book 2 |
3 | 1,4 | Book 3 |
Table 2-[Category] table
category id | category name
1 | Technology
2 | Accounting
3 | Science
4 | Math
5 | Chemistry
I need the result to show up like this
RESULT
book id | categories | title | category name
1 | 1,3,5 | Book 1 | Technology,Science,Chemistry
2 | 2,4 | Book 2 | Accounting,Math
3 | 1,4 | Book 3 | Technology,Math
I tried the below query but i'm not sure what's wrong with it.
SELECT DISTINCT t1.*,(SELECT GROUP_CONCAT(t2.categoryName) FROM `tbl_category` t2 WHERE t2.id IN (t1.categories)) catColumn FROM tbl_books t1 ORDER BY t1.id DESC
If I execute the below query, it is returning the correct values that I need:
SELECT GROUP_CONCAT(categoryName) FROM `tbl_category` t2 WHERE t2.id IN (1,3,5)
RESULT:
Technology,Science,Chemistry
Your first effort should go into fixing your schema. You should have a separate table to store the book/category relations, with each tuple in a separate table row. Storing delimited lists in a database table is bad design, and should be always be avoided: see this famous SO question for more details.
For your current set up though, I would recommend a correlated subquery with find_in_set():
select
b.*,
(
select group_concat(c.category_name)
from category c
where find_in_set(c.id, b.categories)
) category_names
from book b
To echo others, the first thing I would do is make a minor change to your table structure and de-couple books from categories, like this:
TABLE BOOK
book id | title |
1 | Book 1 |
2 | Book 2 |
3 | Book 3 |
TABLE CATEGORY
category id | category name
1 | Technology
2 | Accounting
3 | Science
4 | Math
5 | Chemistry
TABLE BOOK_CATEGORY
id | book_id | category_id
1 | 1 | 1
2 | 1 | 3
3 | 1 | 5
4 | 2 | 2
5 | 2 | 4
6 | 3 | 1
7 | 3 | 4
Finally, to achieve your desired result, execute the following query:
SELECT
b.book_id,
group_concat(bc.category_id order by bc.category_id) AS category_id,
b.title,
group_concat(c.category_name) AS category_name
FROM
book b
INNER JOIN
book_category bc ON b.book_id = bc.book_id
INNER JOIN
category c ON c.category_id = bc.category_id
GROUP BY
book_id;
I have a table of students with temporary test values like this:
Table students
+----+-------------+-------+-----------+
| id | section_id | age | name |
+----+-------------+-------+-----------+
| 1 | 1 | 18 | Justin |
+----+-------------+-------+-----------+
| 2 | 2 | 14 | Jillian |
+----+-------------+-------+-----------+
| 3 | 2 | 16 | Cherry |
+----+-------------+-------+-----------+
| 4 | 3 | 19 | Ronald |
+----+-------------+-------+-----------+
| 5 | 3 | 21 | Marie |
+----+-------------+-------+-----------+
| 6 | 3 | 21 | Arthur |
+----+-------------+-------+-----------+
I want to query the table such that I want to get all the maximum ages of each section. However, if two students have the same age, the table produced will return the student with smallest id.
Return:
+----+------------+-----+--------+
| id | section_id | age | name |
+----+------------+-----+--------+
| 1 | 1 | 18 | Justin |
+----+------------+-----+--------+
| 3 | 2 | 16 | Cherry |
+----+------------+-----+--------+
| 5 | 3 | 21 | Marie |
+----+------------+-----+--------+
I tried this query:
SELECT ANY_VALUE(id), ANY_VALUE(section_id), MAX(age), ANY_VALUE(name) FROM
(SELECT id, section_id, age, name FROM students ORDER BY id) as X
GROUP BY section_id
Unfortunately, there are instances that id does not match the age and name.
I have on my end:
sql_mode = only_full_group_by
and I don't have a privilege to edit that, hence the any_value function but I have no idea how to use it.
This will do what you want.
It starts by finding the maximum age per section (including duplicates).
Then it joins those results with the minimum id per section (to eliminate duplicates).
And finally, select all fields for the matching id and section combinations.
SELECT s3.*
FROM students s3
INNER JOIN (
SELECT MIN(s2.id) AS id, s2.section_id
FROM students s2
INNER JOIN (
SELECT s1.section_id, MAX(s1.age) AS age
FROM students s1
GROUP BY s1.section_id
) s1 USING (section_id, age)
GROUP BY s2.section_id
) s2 USING (id, section_id);
Working SQL fiddle: https://www.db-fiddle.com/f/aezgAYM6A5KnXykceB7At1/0
I would simply use a correlated subquery:
select s.*
from students s
where s.id = (select s2.id
from students s2
where s2.section_id = s.section_id
order by s2.age desc, s2.id asc
limit 1
);
This is pretty much the simplest way to express the logic. And with an index on students(section, age, id), it should be the most performant as well.
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'
I have two mysql tables as follows:
contacts
---------------
id | name | email
---------------
1 | Jack | jack#test.com
2 | John | john#test.com
3 | Liz | liz#test.com
5 | Jack | jack#test.com
6 | Liz | liz#test.com
7 | Mike | mike#test.com
8 | Jack | jack#test.com
purchases
-------------------
id | contact_id | paid
-------------------
1 | 3 | true
2 | 5 | true
I need unique contact_ids that made purchase and other unique contact_ids that don't have made purchases.
So the final result will be as:
-------------------
id | name | email
------------------
2 | John | john#test.com
3 | Liz | liz#test.com
5 | Jack | jack#test.com
7 | Mike | mike#test.com
I tried the query as:
SELECT * FROM contacts LEFT JOIN purchases ON contacts.id = purchases.user_id
But this is not giving me unique rows as required. I tried several combination of DISTINCT, but I am not getting the result as required.
did you try this?
SELECT COALESCE(purchases.contact_id, contacts.id) as id, name, email
FROM contacts
LEFT JOIN purchases ON contacts.id = purchases.user_id
GROUP BY name
SQL FIDDLE
Something like that should work, but its performance is "?".
SELECT * FROM contacts WHERE id IN (
SELECT DISTINCT id as i FROM purchases
UNION
SELECT DISTINCT contact_id as i FROM purchases
)
GROUP BY id