SQL: elegant way to get first, second, and third degree associations - mysql

I have tables named course, student and students_in in a MySQL database. The tables look like this:
course
course_id name
3 Physics
12 English
19 Basket Weaving
4 Computer Science
212 Discrete Math
102 Biology
20 Chemistry
50 Robotics
7 Data Engineering
student
id name
2 Sally
1 Bob
17 Robert
9 Pierre
12 Sydney
41 James
22 William
5 Mary
3 Robert
92 Doris
6 Harry
students_in
course_id student_id grade
3 2 B
212 2 A
3 12 A
19 12 C
3 41 A
4 41 B
212 41 F
19 41 A
12 41 B
3 17 C
4 1 A
102 1 D
102 22 A
20 22 A
20 5 B
50 3 A
12 92 B
12 17 C
7 6 A
Here is a Fiddle: http://sqlfiddle.com/#!17/8d86ee/34
My goal is to get the id and name of the students who:
have taken a course with Sally (i.e. "first-degree" relationship), OR
have taken a course with someone who has taken a course with Sally (i.e. "second-degree" relationship), OR
have taken a course with someone who has taken a course with someone who has taken a course with Sally (i.e. "third-relationship" relationship)
Essentially, we're looking for first-, second-, and third-degree relationships to Sally.
Here is a depiction of what this looks like:
Since Sally took course IDs 3 and 212, the desired result would look like this (not the colorful table above, which I provided for illustration of the logic involved):
student_id student_name
12 Sydney <-- took course ID 3 with Sally
41 James <-- took course ID 3 and 212 with Sally
17 Robert <-- took course ID 3 with Sally
1 Bob <-- took course ID 4 with James
92 Doris <-- took course ID 12 with James and Robert
102 William <-- took course ID 102 with Bob
I tried to solve this problem by using a Common Table Expression (CTE) to query the first-degree relationships, and can probably use two additional CTEs to get the second-degree and third-degree relationships. But, this feels like a very inelegant way to do this.
Can someone please help with an elegant approach to this problem?
Thank you!

You can use a recursive cte:
with recursive cte(cid, sid, name, l) as (
select si.course_id, si.student_id, s.name, 1 from students_in si
join student s on s.id = si.student_id where si.course_id in (select si1.course_id
from students_in si1 join student s1 on s1.id = si1.student_id and s1.name = 'Sally') and s.name != 'Sally'
union all
select si.course_id, si.student_id, s.name, c.l + 1 from cte c
cross join students_in si
join student s on s.id = si.student_id
where si.course_id in (select si1.course_id
from students_in si1 where si1.student_id = c.sid) and si.course_id != c.cid and si.student_id != c.sid and c.l < 3
)
select distinct sid, name from cte where name != 'Sally'
See fiddle.

With Recursive coursemates As (
Select y.student_id, 1 as removal
From students_in x Inner Join students_in y
On x.course_id=y.course_id
Where x.student_id=2
UNION
Select y.student_id, r.removal+1
From coursemates r Inner Join students_in x
On r.student_id=x.student_id
Inner Join students_in y
On x.course_id=y.course_id
Where removal<=2
)
Select c.student_id, min(c.removal) as howfar, min(s.name) as student_name
From coursemates c Left Outer Join student s
On c.student_id=s.student_id
Where student_id <> 2
Group By c.student_id
Order by 2, 1
A little verbose, but also a little more generalized than your try in that you can control the depth.
A few defensive additions: 1. Left join on student table in case no R.I. there. 2. Filter out Sally from the result (don't care that Robert was with Sally and then Sally was with Robert)

Join's repeated as many times as needed, also good, but perhaps less elegant:)
with rel as ( --join student->student thru course
select t1.student_id s1Id, t2.student_id s2Id
from students_in t1 inner join students_in t2
on t2.course_id=t1.course_id
where t2.student_id<>t1.student_id
group by t1.student_id,t2.student_id
)
,four as(
select t1.s1Id as s1Id1 ,t2.s1Id s2Id1,t2.s2Id s2Id2 ,t3.s2Id s3Id2
from rel t1 left join rel t2 on t2.s1Id=t1.s2Id and t2.s2Id<>t1.s1Id
left join rel t3 on t3.s1Id=t2.s2Id and t2.s2Id<>t1.s1Id
and t3.s2Id<>t1.s1Id
where t1.s1Id=2
group by t1.s1Id ,t2.s1Id,t2.s2Id,t3.s2Id
)
select t1.s1Id1,t1,s3Id2,s1.name,s3.name,s4.name,s6.name
from four t1
inner join student s1 on t1.s1Id1=s1.id
inner join student s3 on t1.s2Id1=s3.id
inner join student s4 on t1.s2Id2=s4.id
inner join student s6 on t1.s3Id2=s6.id

Related

show who resit and passed and what course was it?

i need help find who fail in exam & resit and pass the exam only,
heres the code:
select STUDENT_ID,EXAM_ID,SCORE,PASS_THRESHOLD,s.NAME , c.NAME as Course_name, EXAM_DT,
case
when SCORE>=PASS_THRESHOLD then 'PASS'
else 'Fail'
end as Flag
from exam_submission es
left join student s on es.STUDENT_ID = s.ID
left join exam e on es.EXAM_ID = e.ID
left join course c on e.COURSE_ID = c.ID
heres the result:
STUDENT_ID EXAM_ID SCORE PASS_THRESHOLD NAME Course_name EXAM_DT Flag
1 3 88 65 Anthony Data Mining 2019-12-17 PASS
1 5 71 70 Anthony Statistic 2019-12-19 PASS
2 1 53 55 Sisca Machine Learning2019-12-17 Fail
2 3 77 65 Sisca Data Mining 2019-12-17 PASS
2 4 85 63 Sisca Data Science 2019-12-18 PASS
2 1 60 55 Sisca Machine Learning2020-01-08 PASS
I need find like this:
2 1 53 55 Sisca Machine Learning2019-12-17 Fail
2 1 60 55 Sisca Machine Learning2020-01-08 PASS
Possibly using a query like below.
this is using your query as input.
Also we have assumed that it is not possible to have a student have (PASS, FAIL) for a student on same exam on two years chronologically.
; with inputdata as
(
select STUDENT_ID,EXAM_ID,SCORE,PASS_THRESHOLD,s.NAME , c.NAME as Course_name, EXAM_DT,
case
when SCORE>=PASS_THRESHOLD then 'PASS'
else 'Fail'
end as Flag
from exam_submission es
left join student s on es.STUDENT_ID = s.ID
left join exam e on es.EXAM_ID = e.ID
left join course c on e.COURSE_ID = c.ID
)
select * from Inputdata I
join
( select student_id, exam_id from
inputdata
group by student_id, exam_id
having count(distinct flag)=2
)T on I.student_id=T.student_id and I.exam_id=T.exam_id
order by exam_dt asc

How to find all authors who have collaborated together in a paper?

How can I write an SQL query to form a three-column list of author A (author ID), author B (author ID), and the number of collaborated papers between authors A and B. I am using MySQL Server with MYSQL Workbench.
Schema:
Authors (
Author_ID,
Author_name,
Paper_ID
);
Sample Table Data:
Author_ID
Author_name
Paper_ID
1
Jack
313
2
Ray
313
2
Ray
458
3
Amy
458
1
Jack
458
2
Ray
419
3
Amy
419
2
Ray
619
3
Amy
619
Expected Result:
Author A
Author B
Joint Papers
Jack
Ray
2
Jack
Amy
1
Amy
Ray
3
Use a self join and aggregation:
SELECT a1.Author_name AuthorA,
a2.Author_name AuthorB,
COUNT(*) JointPapers
FROM Authors a1 INNER JOIN Authors a2
ON a1.Paper_ID = a2.Paper_ID AND a1.Author_ID < a2.Author_ID
GROUP BY a1.Author_name, a2.Author_name;
Or, if there is a case that 2 authors may have the same name:
SELECT a1.Author_ID IDA, a1.Author_name AuthorA,
a2.Author_ID IDB, a2.Author_name AuthorB,
COUNT(*) JointPapers
FROM Authors a1 INNER JOIN Authors a2
ON a1.Paper_ID = a2.Paper_ID AND a1.Author_ID < a2.Author_ID
GROUP BY a1.Author_ID, a1.Author_name, a2.Author_ID, a2.Author_name;
See the demo.

Give the name of all the teacher who does not teach math

I have two tables. One is the Course table and the second is the Teacher table. I want to get all Teacher who does not teach 'Math'. How can I do this?
Course Table
course_id course teacher_id marks
1 Physics 1 60
2 Math 1 60
3 Chemestry 1 60
4 English 2 60
5 Hindi 2 60
6 Physics 2 60
7 Chemestry 3 60
8 English 4 60
9 Math 5 60
10 Math 6 60
Teacher Table
teacher_id name salary gender
1 Teacher1 20 1
2 Teacher2 30 1
3 Teacher3 40 2
4 Teacher4 50 2
5 Teacher5 60 1
6 Teacher6 70 2
I want to get all teacher who does not teachs math.
You need to join both the tables on teacher_id and then filter out the rows based on the course.
SQL> SELECT DISTINCT t.name
2 FROM course c,
3 teacher t
4 WHERE c.teacher_id = t.teacher_id
5 AND c.course <> 'Math';
NAME
--------
Teacher2
Teacher1
Teacher4
Teacher3
SQL>
EDIT Since you have teachers teaching multiple courses, you need to filter out further:
SQL> WITH DATA AS
2 (SELECT c.*,
3 t.name
4 FROM course c,
5 teacher t
6 WHERE c.teacher_id = t.teacher_id
7 AND c.course <> 'Math'
8 )
9 SELECT DISTINCT name
10 FROM data
11 WHERE teacher_id NOT IN
12 (SELECT teacher_id FROM course WHERE course = 'Math'
13 )
14 /
NAME
--------
Teacher2
Teacher4
Teacher3
SQL>
NOTE Please keep in mind that the other solution using NOT EXISTS clause is better in terms of performance, since the table scans are less and even index scans. With proper indexes, the not exists query would be an optimal method.
select *
from teacher t
where not exists
(select 1 from course c where c.teacher_id = t.teacher_id and c.course = 'Math')
#LalitKumarB
Ben is absolutely right
inner join
select t.teacher_id, t.name
from teacher t, Course c
where c.course='math' and t.teacher_id=c.teacher_id;
EDIT
you can do it using join and subquery.
select * from course join teacher
on course.teacher_id=teacher.teacher_id
where teacher.teacher_id not in
(select distinct teacher_id from course where course = 'Math')
Select * from Teacher
join Course
on Teacher.teacher.id = Course.teacher.id
where Course.course != 'Math'
select
t.name
from teacher t
left join course c
on c.teacher_id = t.teacher_id
where c.course_id <> 2

Mysql select id from table1 and select count(id) from table2

I have two tables. I want to select id from table 1 and count the same from table2
Table 1
Id qId opt
1 30 Chris Christie
2 30 Hillary Clinton
3 30 Allan West
4 30 Joe Biden
5 31 Mark
6 31 Ben Johnson
Table2
poll_id qId ansId
201 30 1
202 30 2
204 31 8
The below query i tried, outputs only the ansId 1 and 2 since there is no 3 and 4 in Table2.
SELECT a.Id,
a.opt,
COUNT(b.ansId)
from Table1 a
INNER JOIN Table2 b ON a.Id = b.ansId
where a.qId =30
But i need all ansId 1,2,3,4 with count of 3 and 4 as 0 as given below.
Id opt COUNT(b.ansId)
1 Chris Christie 1
2 Hillary Clinton 1
3 Allan West 0
4 Joe Biden 0
First thing your are missing with group by ,count is an aggregate function and this needs to be grouped,second you need to use left join with an additional condition in on clause i.e and a.qId =30 so it will stil gives you the result if left id is not found in right table,using where clause will filter out the whole resultset while if you use additional condition in join this will only filter the records from the right table
SELECT a.Id,
a.opt,
COUNT(b.ansId) from Table1 a
LEFT JOIN Table2 b ON a.Id = b.ansId and a.qId =30
GROUP BY a.Id
Fiddle Demo
Edit after sample dataset is updated
SELECT a.Id,
a.opt,
COUNT(b.ansId) from Table1 a
LEFT JOIN Table2 b ON a.Id = b.ansId
WHERE a.qId =30
GROUP BY a.Id
Fiddle demo 2

how to use union resolve this full outer join problem under mySQL

Here is the table
stuid stuname subject grade
1 alex algo 99
1 alex dastr 100
2 bob algo 90
2 bob dastr 95
3 casy algo 100
4 Daisy dastr 100
case1: assuming there are only two subjects in the table
Following is the expected output
stuname algo dastr
alex 99 100
bob 90 95
casy 100 0
Daisy 0 100
I think following is a workable query
select g1.stuname,
COALESCE(g1.grade,0) as algo
COALESCE(g2.grade,0) as dastr
from grades g1
full outer join grades g2 on g1.stuid = g2.stuid
where g1.subject = algo and g2.subject = dastr;
But, mysql doesnt support full outer join. Is there any other way to resolve the problem?
Also, case 2
assuming there are unknown number of subjects in the table
and the expected output would be
stuname subj1 subj2 subj3 ... subjn
I know I might be using procedure resolve it, is there any other way that I can use to compose columns in mySQL?
Your queries would work better if you re-structured your tables. You are attempting to store too much information in one table. Here is a proposed structure:
Students
student_id student_name
1 Alex
2 Bob
3 Casy
4 Daisy
Subjects
subject_id subject_name
1 Algo
2 Dastr
Grades
student_id subject_id grade
1 1 99
1 2 100
2 1 90
2 2 95
3 1 100
4 2 100
In grades, student_id and subject_id would be a composite key, meaning a unique combination of the two becomes the unique identifier (student 1, subject 1 is unique from student 1, subject 2)
To return the data based on your comment, try:
SELECT a.student_name, b.subject_name, c.grade
FROM students a, subjects b, grades c
WHERE a.student_id = c.student_id
AND b.subject_id = c.subject_id
ORDER BY a.student_id
Have you tried something along the line of:
SELECT a.stuid as sidA, a.grade as grA, a.grade as grB
FROM grades a JOIN grades b ON (a.stuname = b.stuname)
But as D.N. suggested, it may be worth restructuring your tables
From your existing data...
select
stuid,
max( stuName ) stuName,
max( if( subject = "algo", grade, 000 )) as Algo,
max( if( subject = "dastr", grade, 000 )) as Dastr
from
Grades
group by
stuid
order by
stuName
However, if you have multiple people with the same "StuName", by grouping by their unique ID, it will keep them differentiated, so for clarification, I've included the ID column in the final query.
However, the data restructuring as suggested by #D.N. would be a cleaner approach.