How to make sure result pairs are unique - without using distinct? - sql

I have three tables I want to iterate over. The tables are pretty big so I will show a small snippet of the tables. First table is Students:
id
name
address
1
John Smith
New York
2
Rebeka Jens
Miami
3
Amira Sarty
Boston
Second one is TakingCourse. This is the course the students are taking, so student_id is the id of the one in Students.
id
student_id
course_id
20
1
26
19
2
27
18
3
28
Last table is Courses. The id is the same as the course_id in the previous table. These are the courses the students are following and looks like this:
id
type
26
History
27
Maths
28
Science
I want to return a table with the location (address) and the type of courses that are taken there. So the results table should look like this:
address
type
The pairs should be unique, and that is what's going wrong. I tried this:
select S.address, C.type
from Students S, Courses C, TakingCourse TC
where TC.course_id = C.id
and S.id = TC.student_id
And this does work, but the pairs are not all unique. I tried select distinct and it's still the same.

Multiple students can (and will) reside at the same address. So don't expect unique results from this query.
Only an overview is needed, so that's why I don''t want duplicates
So fold duplicates. Simple way with DISTINCT:
SELECT DISTINCT s.address, c.type
FROM students s
JOIN takingcourse t ON s.id = t.student_id
JOIN courses c ON t.course_id = c.id;
Or to avoid DISTINCT (why would you for this task?) and, optionally, get counts, too:
SELECT c.type, s.address, count(*) AS ct
FROM students s
JOIN takingcourse t ON s.id = t.student_id
JOIN courses c ON t.course_id = c.id
GROUP BY c.type, s.address
ORDER BY c.type, s.address;
A missing UNIQUE constraint on takingcourse(student_id, course_id) could be an additional source of duplicates. See:
How to implement a many-to-many relationship in PostgreSQL?

Related

Count the sum of a left outer join table

I have two tables which is
Student
ID
Name
Gender
Address
1
Abby
Girl
street
2
Mark
Boy
street
3
Lula
Girl
street
4
Bren
boy
street
Lessons
ID
Lessons_code
3
MK2234
5
22324KL
6
KCS233
and I want to join these tables then get the sum result of the students that didn't took a lesson then group it by gender like this:
Gender
total
Boy
2
girl
1
I know it use sum() and left outer join (?) but I don't know how to implement it.
I would suggest not exists:
select s.gender, count(*)
from students s
where not exists (select 1
from lessons l
where l.id = s.id
)
group by s.gender;
You have a very awkward data model. I would expect a column called lessons.id to refer to the primary key of the lessons table. Instead. it seems to refer to a student. A better name would be student_id.

Native Query to Select records to only contain specific ids

I am in the process of implementing an advanced search in my application. I'm sort of new with Oracle and JPA. The database structure has a many-to-many relationship (ERD) with an intermediate table that contains StudentID and CourseID.
I'm trying to return rows of students that have the list of classes/courses.
Basically, I want the results of
SELECT DISTINCT s.* FROM STUDENT s
INNER JOIN STUDENT_COURSE sc ON s.StudentID = sc.StudentID
INNER JOIN COURSE c ON c.CourseID = sc.CourseID
WHERE ( c.CourseID IN (SELECT DISTINCT CourseID FROM STUDENT_COURSE WHERE CourseID = 'A01') AND c.CourseID IN (SELECT DISTINCT CourseID FROM STUDENT_COURSE WHERE CourseID = 'A02'));
which returns the records of students that have both courses 'A01' and 'A02'.
ID
Age
Grade
1
...
...
4
...
....
9
...
....
with courses
Courses
A01,A02
A01,A02,A03,X02
A01, A02, A03
The goal is to get a similar result using Spring Data JPA. And make it more general to select any number of Course Ids.
I've tried
#Query(value="SELECT DISTINCT s.* FROM STUDENT s "
+"INNER JOIN STUDENT_COURSE sc ON s.StudentID = sc.StudentID "
+"INNER JOIN COURSE c ON c.CourseID = sc.CourseID "
+"WHERE ( s.STUDENTID IN (SELECT DISTINCT STUDENTID FROM STUDENT_COURSE WHERE CourseID =:courseIds)",nativeQuery=true))
public List<Student> advancedSearch(#Param("courseIds") String courseIds);
One example, The courseIds field contain "A01,A02". The result would be empty.
I've looked at examples where people use IN. When I tried it, it would return records of the student have courses AO1 OR A02 OR Both.
DON'T WANT THIS
ID
Age
Grade
2
...
...
3
...
....
8
...
....
With Courses
CourseID
A01,A03
A01, X01
A02
I want records of students that have both A01 AND A02 as shown in the other table.
My guess is that the query you want is
select s.name /* note this column doesn't appear in your ERD */
,s.age
,s.grade
from student s
join student_course sc
on s.studentID = sc.studentID
where sc.courseID IN ('A01', 'A02')
group by s.name /* again, this column isn't in your ERD */
,s.age
,s.grade
having count(sc.courseID) = 2
That doesn't produce the output table you say you want. But neither does the query you posted. This will return all the students that have taken both courses (you could add a distinct to the having clause if the data model were to change to allow a student to take a course multiple times). It won't give you the list of all courses that student has taken as a comma-separated list. But the query you posted doesn't return any course information either.
If this isn't what you are looking for, it would be really helpful to update your question with
The DDL for your schema in text form (so we can run it) rather than as an image
The DML to insert whatever sample data you want
The actual results you'd want the query to return (since there is a conflict between what the query you posted returns, the table of data you say you want, and the descriptive text of what you are trying to accomplish, it is confusing to try to figure out which is right).

PostgreSQL: How do I get data from table `A` filtered by a column in table `B`

I want to fetch all parents that have kids in a specific grade only in a school.
Below are trimmed down version of the tables.
TABLE students
id,
last_name,
grade_id,
school_id
TABLE parents_students
parent_id,
student_id
TABLE parents
id,
last_name,
school_id
I tried the below query but it doesn't really work as expected. It rather fetches all parents in a school disregarding the grade. Any help is appreciated. Thank you.
SELECT DISTINCT
p.id,
p.last_name,
p.school_id,
st.school_id,
st.grade_id,
FROM parents p
INNER JOIN students st ON st.school_id = p.school_id
WHERE st.grade_id = 118
AND st.school_id = 6
GROUP BY p.id,st.grade_id,st.school_id;
I would think:
select p.*
from parents p
where exists (select 1
from parents_students ps join
students s
on ps.student_id = s.id
where ps.parent_id = p.id and
s.grade_id = 118 and
s.school_id = 6
);
Your question says that you want information about the parents. If so, I don't see why you are including redundant information about the school and grade (it is redundant because the where clause specifies exactly what those values are).

Difficulty understanding a SQL Join query

I have these tables in my database:
Student:
id,
style_id,
instrument,
name,
Teacher:
id,
style_id,
name
lessons:
id,
style_id,
teacher_id,
student_id,
month
And I'm trying to write a query that returns every student enrolled for a particular month and their teacher and style e.g. it would return something like this:
March
Student Name Teacher Style
-------------------------------
Glen Bob Jazz
Missy Bruce Rock
So far all I got is something like this (I know I need to use joins just don't know how):
SELECT
students.name, teacher.name, style.style_name
FROM
lessons
WHERE
month = "March"
JOINS students, teachers
First off, it is not a good practice for you to have the name of the table in your primary key, in your table teacher, you used musician_id, use teacher_id instead. This is the code I would use to achieve what you are trying to do.
SELECT a.style_name, b.name, c.name, d.month
FROM lessons d
INNER JOIN Styles a ON a.style_id = d.style_id
INNER JOIN Student b ON b.student_id = d.student_id
INNER JOIN Teacher c ON c.teacher_id = d.teacher_id
WHERE d.month = "march";
Since you are in difficulty with joins, Inner join, will see if its evidence of the ids in both tables to return true, thats why it solvs your problem.
To answer your specific question,
SELECT Student.name AS Student, Teacher.name AS Teacher, Styles.style_name AS Style
FROM lessons
INNER JOIN Styles ON lessons.style_id = Styles.style_id
INNER JOIN Teacher ON lessons.teacher_id = Teacher.musician_id
INNER JOIN Student ON lessons.student_id = Student.student_id
WHERE lessons.month="March";
You might want to look at your table design, though. You have a style_id in the lessons, the Teachers and the Students tables. The query above matches the style name with the style_id in the lessons table, while it could be linked to either the Teacher table or the Student table. This could lead to reporting problems if Bob is teaching a Jazz lesson and his profile says he's a Rock teacher.
It's better to remove the style_id from the Teacher and Student tables and introduce a new table
Interests: participant_id (could be teacher id or student id), role ('teacher' or 'student'), style_id

Get courses chosen by all students

Now there are two tables student(sid, name) and course(cid, name), which relation is many-to-many. So there is another table student_course(sid, cid) that stores the information about what courses have been chosen by whom.
How to write the sql that can get the courses that have been chosen by all the students?
Standard solution: use the NOT EXISTS ( ... NOT EXISTS (...)) construct:
find all courses in which all students take part
==>> there must not exist a student that does not take part in this course
SELECT * FROM course c
WHERE NOT EXISTS (
SELECT * from student s
WHERE NOT EXISTS (
SELECT * from student_course cs
WHERE cs.sid = s.sid
AND cs.cid = c.cid
)
)
;
This query is often faster (given appropiate indexes) than the count() == count() variant. The reason for this: you do not have to count all the (distinct) records; once you found one student that does not take this course you can omit this course from your list of suspects. Also: ANTI-joins often can make use of indexes [so can a count(), but that still has to count all the (distinct) keyvalues in the index]
Select c.cid, c.name
From course c where
(select count(1) from student) = (select count(1) from student_course sc where sc.cid = c.cid);
See SQL Fiddle
It finds all courses where the count of entries for that course in the student_course table matches the number of students
CID NAME
1 Test Course1
4 Test Course4