Get min value based on foreign key - mysql

I am stuck on how to get a student's minimum score from marks table and student info from student table.
EDIT: sorry for not mentioning, but i need result for user 1, st1
student
id name
1 st1
2 st2
marks
student_id course_name marks
1 C++ 55
1 OOP 65
1 AI 45 //need this lowest result for st1
2 C++ 82
2 STATS 74
2 OS 20 // lowest marks are these for st2 or overall
Edit forgot to enter name st1:
Desired Output:
id name course_name MinMarks
1 st1 AI 45
I tried this query but it gives wrong course_name (C++) 1st row's course name
SELECT s.id, s.name, m.course_name, MIN(m.marks) FROM student s
JOIN marks m
ON s.id = m.student_id
Where s.id = 1
Output:
id name course_name MinMarks
1 st1 C++ 45

You can use a subquery:
select *
from marks
where marks = (select min(marks) from marks)
or left join:
select m.*
from marks m
left join (select min(marks) as marks from marks) m_min on m.marks = m_min.marks
where m_min.marks is not null
It guarantees you that if there is more then one student with the minimum mark it displays all of them.
left join can possibly improve performance, you can check your execution plan to be sure.
P.S.: If you also need to retrieve name from student table (is not stated in your desired output) you can do the join operation you did in your query:
using subquery:
select m.student_id
, s.name
, m.course_name
, m.marks
from student s
join marks m on s.id = m.student_id
where m.marks = (select min(marks) from marks)
using left join:
select m.student_id
, s.name
, m.course_name
, m.marks
from student s
join marks m on s.id = m.student_id
left join (select min(marks) as marks from marks) m_min on m.marks = m_min.marks
where m_min.marks is not null
Edit
As it turns out, OP needs a minimum marks per student's id (for the id = 1), so :
select m.student_id
, s.name
, m.course_name
, m.marks
from student s
join marks m on s.id = m.student_id
left join (select student_id, min(marks) as marks from marks group by student_id) p on s.id = p.student_id and p.marks = m.marks
where s.id = 1 and p.student_id is not null

Try something like
select
s.id,
s.name,
m.course_name,
m.marks
from
student s
inner join marks m on
m.student_id = is.id
inner join (
select
student_id,
min(marks) min_mark
from
marks m
group by
student_id
) min_marks on
min_marks.student_id = s.id and
min_marks.student_id = m.student_id and
min_marks.min_mark = m.marks

If you are looking for only one student with the minimum mark, I would suggest order by and limit:
SELECT s.id, s.name, m.course_name, m.marks
FROM student s JOIN
marks m
ON s.id = m.user_id
ORDER BY m.marks ASC
LIMIT 1;
This solution will not work if you are looking for all students that have the same minimum mark.

try this:
Select s.id, s.name, m.course_name, m.marks From student s
right join marks m
on s.id = m.user_id where m.marks = MIN(m.marks);

SELECT s.id, s.name, m.course_name, m.marks
FROM marks m
JOIN student s
ON m.student_id = s.id
WHERE m.student_id=1 AND m.marks = (SELECT MIN(marks) FROM marks WHERE student_id=1)

Related

symmetric pair in sql

Given the tables student, mathematics_marks, science_marks
student
student_id (Primary Key) | smallint
student_name | varchar(30)
mathematics_marks
student_id (Primary Key) | smallint
score | float (5,2)
science_marks
student_id (Primary Key) | smallint
score | float (5,2)
A student is called as being a part of a symmetric pair if the marks obtained by that student in science is equal to the marks obtained by some other student in mathematics and the marks obtained in mathematics are the same as marks obtained by the other student in science.
Write a SQL query to print the name of the students who are a part of the symmetric pair. Order the output in order of student name.
with cte as (
select m.student_id, m.score as math_score, s.score as science_score
from mathematics_marks m inner join science_marks s
on s.student_id = m.student_id
)
select s1.student_name
from cte c1 inner join cte c2
on c2.student_id > c1.student_id and c2.math_score = c1.science_score and c1.math_score = c2.science_score
inner join student s1 on s1.student_id = c1.student_id and s1.student_id = c2.student_id
need to get the pair in two different rows?
EDITED:
answer to this
with cte as (
select m.student_id, m.score as math_score, s.score as science_score
from mathematics_marks m inner join science_marks s
on s.student_id = m.student_id
),
T as (select c1.student_id, c1.math_score, c1.science_score, s.student_name as name
from cte c1 inner join student s
on c1.student_id = s.student_id )
select t1.name from T t1 inner join T t2
on t1.math_score = t2.science_score and
t1.science_score = t2.math_score
order by t1.name
A properly designed schema would have a single table for student, subject, and mark. The first step then is to emulate that table, which could be as follows:
SELECT student_id
, 'mathematics' subject
, score
FROM mathematics_marks
UNION
SELECT student_id
, 'science'
, score
FROM science_marks
For further help, see: Why should I provide an MCRE for what seems to me to be a very simple SQL query?
It's always easy to answer if the post is accompanied by sample data and expected output . Are you looking for somthing like this
Select student.student_id
,maths.score maths_socer
,sci.score science_score
from
(
Select 1 student_id union all
Select 2 union all
Select 3
) student
Join
(
Select 1 student_id, 20 score union all
Select 2, 30 union all
Select 3, 40
) maths
on maths.student_id=student.student_id
Join
(
Select 1 student_id , 30 score union all
Select 2,20 union all
Select 3,40
) sci on sci.student_id !=student.student_id
Where sci.score = maths.score

SQL : Calculating Percentage by joining a sub table to another

I've the above dataset, I need to report for each year the percentage of movies in that year with only female actors, and the total number of movies made that year. For example, one answer will be: 1990 31.81 13522 meaning that in 1990 there were 13,522 movies, and 31.81%
In order to get the moves with only female actors, wrote the following code:
SELECT a.year as Year, COUNT(a.title) AS Female_Movies, a.title
FROM Movie a
WHERE a.title NOT IN (
SELECT b.title from Movie b
Inner Join M_cast c
on TRIM(c.MID) = b.MID
Inner Join Person d
on TRIM(c.PID) = d.PID
WHERE d.Gender='Male'
GROUP BY b.title
)
GROUP BY a.year,a.title
Order By a.year asc
The total movies in each year , can be found using the following:
SELECT a.year, count(a.title) AS Total_Movies
FROM Movie a
GROUP BY a.year
ORDER BY COUNT(a.title) DESC
Combinig the both I wrote, the following code:
SELECT z.year as Year, count(z.title) AS Total_Movies, count(x.title) as Female_movies, count(z.title)/ count(x.title) As percentage
FROM Movie z
Inner Join (
SELECT a.year as Year, COUNT(a.title) AS Female_Movies, a.title
FROM Movie a
WHERE a.title NOT IN (
SELECT b.title from Movie b
Inner Join M_cast c
on TRIM(c.MID) = b.MID
Inner Join Person d
on TRIM(c.PID) = d.PID
WHERE d.Gender='Male'
GROUP BY b.title
)
GROUP BY a.year,a.title
Order By a.year asc
)x
on x.year = z.year
GROUP BY z.year
ORDER BY COUNT(z.title) DESC
However, in th output I'm seeing the years with only female movies correctly, but the count of total movies is equal to female_movies so I'm getting 1%, I tried debugging the code, but not sure where this is going wrong. Any insights would be appreciated.
You assume that your 'z' contains all movies but since you do an inner join on the female movies, they'll also only contain female movies. You could fix that with a 'left join'.
Assuming your two queries are correct, you can join on them with a 'WITH' like this:
WITH allmovies (year, cnt) as
(SELECT a.year, count(a.title) AS Total_Movies
FROM Movie a
GROUP BY a.year
ORDER BY COUNT(a.title) DESC)
,
femalemovies (year, cnt, title) as
(SELECT a.year as Year, COUNT(a.title) AS Female_Movies, a.title
FROM Movie a
WHERE a.title NOT IN (
SELECT b.title from Movie b
Inner Join M_cast c
on TRIM(c.MID) = b.MID
Inner Join Person d
on TRIM(c.PID) = d.PID
WHERE d.Gender='Male'
GROUP BY b.title
)
GROUP BY a.year,a.title
Order By a.year asc)
select * from allmovies left join femalemovies on allmovies.year = femalemovies.year
You can use conditional aggregation. In a CASE expression check if no cast member that isn't female exists with a correlated subquery. If the check is successful, return something not NULL and count() that to get the number of movies with only female cast members (or none at all).
SELECT m.year,
count(*) count_all,
count(CASE
WHEN NOT EXISTS (SELECT *
FROM m_cast c
INNER JOIN person p
ON p.pid = c.pid
WHERE c.mid = m.mid
AND p.gender <> 'Female') THEN
1
END)
/
count(*)
*
100 percentage_only_female
FROM movie m
GROUP BY m.year;
Since in MySQL Boolean expressions in numerical context evaluate to 1 if true and to 0 otherwise, you could also use a sum() over the NOT EXISTS.
SELECT m.year,
count(*) count_all,
sum(NOT EXISTS (SELECT *
FROM m_cast c
INNER JOIN person p
ON p.pid = c.pid
WHERE c.mid = m.mid
AND p.gender <> 'Female'))
/
count(*)
*
100 percentage_only_female
FROM movie m
GROUP BY m.year;
That however isn't compatible with most other DBMS in contrast to the first one.
I would use two levels of aggregation:
SELECT m.MID, m.title, m.year,
COUNT(*) as num_actors,
SUM(gender = 'Female') as num_female_actors
FROM Movie m JOIN
M_cast c
ON c.MID = b.MID JOIN
Person p
ON p.PID = c.PID
GROUP BY m.MID, m.title, m.year;
Then a simple outer aggregation:
SELECT year,
COUNT(*) as num_movies,
SUM( num_actors = num_female_actors ) as num_female_only,
AVG( num_actors = num_female_actors ) as female_only_ratio
FROM (SELECT m.MID, m.title, m.year,
COUNT(*) as num_actors,
SUM(gender = 'Female') as num_female_actors
FROM Movie m JOIN
M_cast c
ON c.MID = b.MID JOIN
Person p
ON p.PID = c.PID
GROUP BY m.MID, m.title, m.year
) m
GROUP BY year;
Notes:
Use meaningful table aliases, rather than arbitrary letters. You'll note that the table aliases are abbreviations for the table names.
Do not use functions when filtering or JOINing unless necessary. I removed the TRIM(). If you need it use it. Or better yet, fix the data.
SELECT m.Year,COUNT(m.Year),x.t,
(COUNT(m.Year)*1.0/x.t*1.0)*100
FROM Movie m LEFT JOIN
(SELECT Year,COUNT(Year) AS t FROM Movie GROUP BY year) AS x
ON m.Year=x.Year
WHERE m.MID IN
(SELECT MID FROM M_Cast WHERE PID in
(SELECT PID FROM Person WHERE Gender='Female')
AND m.MID NOT IN
(SELECT MID FROM M_Cast WHERE PID in
(SELECT PID FROM Person WHERE Gender='Male'))) GROUP BY m.year
Check if this is what you're looking for.
select movie.year, count(movie.mid) as Year_Wise_Movie_Count,cast(x.Female_Cast_Only as real) / count(movie.mid) As Percentage_of_Female_Cast from movie
inner join
(
SELECT Movie.year as Year, COUNT(Movie.mid) AS Female_Cast_Only
FROM Movie
WHERE Movie.MID NOT IN (
SELECT Movie.MID from Movie
Inner Join M_cast
on TRIM(M_cast.MID) = Movie.MID
Inner Join Person
on TRIM(M_cast.PID) = Person.PID
WHERE Person.Gender!='Female'
GROUP BY Movie.MID
)
GROUP BY Movie.year
Order By Movie.year asc
) x
on x.year = movie.year
GROUP BY movie.year
ORDER BY movie.year
Output:
year Year_Wise_Movie_Count Percentage_of_Female_Cast
---- --------------------- -------------------------
1939 2 0.5
1999 66 0.0151515151515152
2000 64 0.015625
2018 104 0.00961538461538462
Note:
This was executed in SQLIte3

SQL query to print the name of the students who are a part of the symmetric pair

Given the tables student, mathematics_marks, science_marks
student
student_id (Primary Key) | smallint
student_name | varchar(30)
mathematics_marks
student_id (Primary Key) | smallint
score | float (5,2)
science_marks
student_id (Primary Key) | smallint
score | float (5,2)
A student is called as being a part of a symmetric pair if the marks obtained by that student in science is equal to the marks obtained by some other student in mathematics and the marks obtained in mathematics are the same as marks obtained by the other student in science.
I am trying to solve the above problem with the following query:
SELECT s.student_name
FROM student s
LEFT JOIN (mathematics_marks m CROSS JOIN science_marks sc)
ON (s.student_id = m.student_id AND m.student_id = sc.student_id)
WHERE EXISTS(SELECT * FROM mathematics_marks m
WHERE sc.score=m.score
AND m.score=sc.score)
ORDER BY student_name;
I am not getting correct output. Can anyone help me out where i am going wrong?
In a very simple way
select s.student_name 'student_name'
from student s
inner join mathematics_marks m
on m.student_id = s.student_id
inner join science_marks sc
on sc.student_id = s.student_id
where m.score in ( select score from science_marks ) and
sc.score in ( select score from mathematics_marks )
ORDER BY student_name;
I would do something like this:
SELECT s.student_name
FROM student s
LEFT JOIN mathematics_marks m ON m.student_id = s.student_id
LEFT JOIN science_marks sc ON sc.student_id = s.student_id
WHERE m.score IN (SELECT score FROM science_marks) OR sc.score IN (SELECT score FROM mathematics_marks)
Cross Join all students and then compare the marks for each student with other students.
select student1.student_name, student2.student_name
from (
select s1.student_id, s1.student_name, m1.score "m_score", sc1.score "sc_score"
from student s1
join mathematics_marks m1 on s1.student_id = m1.student_id
join science_marks sc1 on s1.student_id = sc1.student_id) student1,
(
select s2.student_id, s2.student_name, m2.score "m_score", sc2.score "sc_score"
from student s2
join mathematics_marks m2 on s2.student_id = m2.student_id
join science_marks sc2 on s2.student_id = sc2.student_id) student2
where student1.student_id<>student2.student_id
and student1.m_score = student2.sc_score
and student1.sc_score = student2.m_score;
For this requirement you need multiple joins of the 2 marks tables and finally to get the names of the students you need to join the table student twice:
select st1.student_name, st2.student_name
from mathematics_marks m1
inner join science_marks s1 on s1.student_id > m1.student_id and s1.score = m1.score
inner join mathematics_marks m2 on m2.student_id = s1.student_id
inner join science_marks s2 on s2.student_id < m2.student_id and s2.score = m2.score
inner join student st1 on st1.student_id = m1.student_id
inner join student st2 on st2.student_id = m2.student_id;
See the demo.
For simplicity and get all score under single table let's create a view.
create view student_marks as select mm.student_id,mm.score as m_score, sm.score as s_score
from mathematics_marks mm
inner join science_marks sm on mm.student_id=sm.student_id;
now we have all marks under single table.
so we need to do self join of above view and find students who satisfy the symmetric condition
select distinct s.*
from student_marks sm1
inner join student_marks sm2 on sm1.m_score=sm2.s_score and sm2.m_score=sm1.s_score
left join student s on s.student_id = sm1.student_id or s.student_id=sm2.student_id;
If you don't want to create view, you can simply replace student_marks with the view query.

Symmetric Pair in SQL with JOIN

I am trying to find the name of students where a symmetric pair exists. There are 3 tables:
**student**
student_id (Primary Key) | smallint
student_name | varchar(30)
**mathematics_marks**
student_id (Primary Key) | smallint
score | float (5,2)
**science_marks**
student_id (Primary Key) | smallint
score | float (5,2)
with Functions as (
select s.student_name as name, mm.score as math_score, sm.score as science_score
from student s
join mathematics_marks mm
on mm.student_id = s.student_id
join science_marks sm
on sm.student_id = s.student_id)
select t1.name
from Functions t1
join Functions t2
on t1.math_score = t2.science_score
and t1.science_score = t2.math_score
where t1.math_score < t1.science_score
Edit from your comment: A student is called as being a part of a symmetric pair if the marks obtained by that student in science is equal to the marks obtained by some other student in mathematics and the marks obtained in mathematics are the same as marks obtained by the other student in science.
Given the structure of the data, I would assume that students could have multiple marks in each subject. Otherwise, why store the values in separate tables?
To solve this problem, I would preaggregate the marks:
with mm as (
select student_id, group_concat(score order by score desc) as math_scores
from mathematics_marks
group by student_id
),
sm as (
select student_id, group_concat(score order by score desc) as science_scores
from science_marks
group by student_id
),
sms as (
select *
from mm join
sm
using (student_id)
)
select sms.student_id, sms2.student_id
from sms join
sms sms2
on sms.math_scores = sms2.science_scores and
sms.science_scores = sms2.math_scores and
sms.student_id < sms2.student_id;
This returns the matching ids. You need an additional join if you want to bring in the names.
Note: You have stored the values as floats. This is quite dangerous. You should be storing the values as decimal/numeric. Two values that look the same might actually be different.
This is how I would write the code for this requirement:
with cte as (
select m.student_id, m.score as math_score, s.score as science_score
from mathematics_marks m inner join science_marks s
on s.student_id = m.student_id
)
select s1.student_name, s2.student_name
from cte c1 inner join cte cte2
on c2.student_id > c1.student_id and c2.math_score = c1.science_score and c1.math_score = c2.science_score
inner join student s1 on s1.student_id = c1.student_id
inner join student s2 on s2.student_id = c2.student_id
Try this
select s.student_name as name, mm.score as math_score, sm.score as science_score
from student s
join mathematics_marks
mm
on mm.student_id
<s.student_id
join science_marks sm
on sm.student_id <s.student_id and
mm.math_score
=sm.science_score

How to find all rows that have all values which has another row

My database contains students and marks tables, which you can see visiting http://www.sqlfiddle.com/#!2/817367/1. Query should return all studends whose got all marks which has gotten student 'c' (1, 2, 3), that is - b, e, f.
I have solved problem partly getting all students.name which has any of marks.score where students.name = 'c', but I can't figure out how to force to check all marks.score-es of 'c'.
...Hopefully question is clear.
Thanks in advance.
the other solutions are good for this time, but if you want a solution without using aggregate functions like "COUNT" then consider this.
http://www.sqlfiddle.com/#!2/817367/39
SELECT `name`
FROM students
JOIN (SELECT DISTINCT s_id
FROM marks AS marks1
WHERE marks1.`s_id` NOT IN(
SELECT DISTINCT marks2.s_id
FROM (SELECT score
FROM marks
JOIN students ON marks.`s_id` = students.`id`
AND students.`name` = 'c') AS c_scores
CROSS JOIN marks AS marks2
ON marks2.`s_id` NOT IN (
SELECT s_id
FROM marks
JOIN students ON marks.`s_id` = students.`id`
AND students.`name` = 'c')
LEFT JOIN marks AS marks3 ON marks3.`s_id` = marks2.s_id
AND marks3.`score` = c_scores.score
WHERE marks3.`s_id` IS NULL
)) AS good_ids
ON students.`id` = good_ids.s_id
WHERE `name` != 'c'
Another similiar solution:
SELECT s2.name
FROM marks m1
JOIN marks m2 ON m2.score = m1.score
JOIN students s1 ON s1.id = m1.s_id
JOIN students s2 ON s2.id = m2.s_id
WHERE s1.name = 'c' AND s2.name != 'c'
GROUP BY s2.id
HAVING COUNT(DISTINCT m1.score)
= (SELECT COUNT(DISTINCT m.score)
FROM marks m
JOIN students s ON s.id = m.s_id
WHERE s.name = 'c')
got it ...phew, your requirements are weird as hell haha but this should give you what you need with a single input 'c'
maybe there are room to improve but at this point you're on your own.
somehow i can't update the fiddle so here it is
select t1.name
from
(select s1.name,count(*) as count
from marks m1
inner join marks m2 using (score)
inner join students s1 on (s1.id=m2.s_id)
inner join students s2 on (m1.s_id=s2.id)
where s2.name='c'
group by m2.s_id
) as t1
JOIN
(select count(*) as min_count
from marks m
inner join students s on (m.s_id=s.id)
where s.name='c'
) as t2
where
t1.name != 'c'
and t1.count >= t2.min_count