SQL finding most popular books - mysql

I have Books table
BookID BookName
1 BookA
2 BookB
3 BookC
Member table
MemberID MemberName
1 MemberA
2 MemberB
Borrow Table
MemberID BookID
1 1
1 2
2 1
2 2
I want to find out five popular book by Memeber A
I tried the following query
SELECT TOP (5) Book.BookTitle, COUNT(*) AS Count, Member_1.MemberName
FROM Book INNER JOIN
Borrow ON Book.BookID = Borrow.BookID INNER JOIN
Member ON Borrow.MemberID = Member.MemberID INNER JOIN
Member AS Member_1 ON Borrow.MemberID = Member_1.MemberID
where Member.MemberName='A'
GROUP BY Book.BookTitle, Member_1.MemberName
ORDER BY Count DESC
But this is not giving me the actual result.
Any suggestion would be appreciated.

I think you have too many joins:
SELECT TOP (5) b.BookTitle, COUNT(*) AS Count, m.MemberName
FROM Book b INNER JOIN
Borrow bo
ON bo.BookID = b.BookID INNER JOIN
Member m
ON bo.MemberID = m.MemberID
WHERE m.MemberName = 'A'
GROUP BY b.BookTitle, m.MemberName
ORDER BY Count DESC;
Note: This syntax is usually associated with SQL Server and does not work in MySQL. In MySQL, you would use LIMIT 5 rather then SELECT TOP (5).

Related

MySQL - query that should filter based on records contained in the join table [duplicate]

This question already has answers here:
Left Outer Join doesn't return all rows from my left table?
(3 answers)
Closed 6 months ago.
studentTable:
id
studentName
1
Name1
2
Name2
3
Name3
studentCourseTable:
id
studentId
courseId
1
1
1
2
1
2
3
3
1
4
3
3
5
2
2
I want to (let's say) list students who have taken courseId 1 AND 3 (together) BUT have NOT taken 2. Or any dynamic combination such as that, like courseId 1,2,3 should be taken; 1,2 not taken but 3 is taken etc etc.
I have tried some JOIN clause to filter but have not been able to apply more than 1 condition:
SELECT student.*
FROM studentTable AS s
LEFT JOIN studentCourseTable AS sc
ON sc.studentId = s.id
WHERE sc.studentId IN (1,3)
AND sc.studentId NOT IN (2)
or:
SELECT student.*
FROM studentTable AS s
LEFT JOIN studentCourseTable AS sc
ON sc.studentId = s.id
AND sc.courseId IN (1, 3)
AND sc.courseId IN (2)
The important thing is that I want to find students that take specified courses TOGETHER, AND not take any other specified course. The student may take more courses than specified (as long as it is not in NOT taken list).
**Edit for some clarifications: ** For example if I say the student should take (2,4) but NOT (3), returning a student that takes (2,4,5) is ok. But (2,3) or (2,4,5) are NOT ok.
There are some other tables that I'm joining the student table with, not sure if it matters but this is the gist of it.
Can anyone assist me with this?
** Edit: ** #lemon has cracked it. Here's the demo he made, which lists any user that attended 1 OR 3 AND have not attended 2. Here's my updated demo which lists students that attended 1 AND 3 AND have not attended 2.
Thanks to all who helped me, this was superb.
You can select all information from your students and use two kind of JOIN operations:
an INNER JOIN for each due attended course
a LEFT JOIN for non-attended courses, to be filtered out in the WHERE clause
SELECT s.*
FROM students s
INNER JOIN (SELECT DISTINCT studentId FROM courses WHERE courseId = 1) c1
ON s.id = c1.studentId
INNER JOIN (SELECT DISTINCT studentId FROM courses WHERE courseId = 3) c3
ON s.id = c3.studentId
LEFT JOIN (SELECT DISTINCT studentId FROM courses WHERE courseId IN (2)) not_c
ON s.id = not_c.studentId
WHERE not_c.studentId IS NULL
Check the demo here.
Another option is to count
positively your needed courses
negatively your unneeded courses
Eventually sum up the values, and filter out those students whom don't have sum equal to the amount of needed courses.
SELECT s.*
FROM courses c
INNER JOIN students s
ON s.id = c.studentId
GROUP BY s.id,
s.studentName
HAVING SUM(CASE WHEN c.courseId IN (1,3) THEN 1
WHEN c.courseId IN (2) THEN -1 END) = 2
Check the demo here.
To get only students that are in groups 1 and 3
SELECT s.studentName
from studenttable AS s
INNER JOIN studentCourseTable AS sc ON sc.studentId = s.id
where sc.courseId in (1,3)
group by s.id
having count(*) = 2;
We first should group by student name or id, then we get only those that are in the 2 courses by adding "having count(*) = 2"

mysql select of select

Table Student
StudentID
StudentName
1
A
2
B
3
C
Table Book
BookID
BookName
1
Book1
2
Book2
3
Book3
Table BookAssignment
AssignID
BookID
StudentID
DateTime
1
1
1
2021-06-26
2
2
1
2021-07-01
3
1
2
2021-07-03
The result table should be
StudentID
StudentName
BookCount
1
A
2
2
B
1
3
C
0
How to get the result table in one SQL execution?
Left JOIN seems not an option since it eliminates StudentID 3
Just added another DateTime column to the BookAssignment table - What is SQL syntax to query the book count over the last 7 consecutive days (even for 0 book for day count)?
you need to use simple group by using left join between two tables:
select s.StudentID, s.StudentName , count(*) BookCount
from students s
left join books b
on s.StudentID = b.StudentID
group by s.StudentID, s.StudentName
I'd left join the student table on an aggregate query of the books and use coalesce to fill in the zeros:
SELECT s.StudentID, StudentName, COALESCE(cnt, 0)
FROM student s
LEFT JOIN (SELECT StudentID, COUNT(*) AS cnt
FROM books
GROUP BY StudentID) b ON s.StudentID = b.StudentID
You can also use a correlated subquery:
select s.*,
(select count(*)
from books b
where s.StudentID = b.StudentID
) as bookCount
from students s;
This has some advantages over using a join/group by approach:
You can trivially include all columns in the select. They don't have to repeated in the group by.
With an index on books(StudentID) this often has the best performance.
This avoids the outer aggregation, which can kill performance.
Adding another dimension (say the number of courses the student has) just works, without worrying about Cartesian Products.
select s.StudentID, s.StudentName ,(select count(*) from BookAssignment b where b.studentid = s.studentid) as BookCount
from students s

SQL select inside select

I have 3 tables.
person{personid,name, etc}
bid{bidid,personid,etc}
rating{ratingid,bidid,rating}
A person gets the rating after the bid is accepted by customers. So 1 bid = 1 rating.
And then the person bids another order, but the rating won't show up.
I already tried:
SELECT a.namausaha,ROUND(AVG(c.rating)) AS rating,a.kota,a.kontak,b.bidprice,a.mitraid
FROM tb_mitra a
JOIN tb_bid b ON b.mitraid=a.mitraid
LEFT JOIN tb_rating c ON c.bidid=b.bidid
WHERE b.orderid='OD004' AND b.statusbidid='1'
GROUP BY a.mitraid
but it doesn't work.
How to do it? I want to show the rating for every person.
Some rating return null due to left join
Try this
SELECT a.name,
ROUND(AVG(CASE WHEN c.rating IS NULL THEN 1 ELSE c.rating END )) AS rating,
a.etc,b.etc,a.personid
FROM person a
JOIN bid b ON b.personid=a.personid
LEFT JOIN rating c ON c.bidid=b.bidid
GROUP BY a.personid
SQL Fiddle

How to do multiple tasks in a single SQL query

I was given the database below:
movie(movie_id, movie_name, production_year, votes, ranking, rating)
movie_info(movie_id, movie_genre_id, note)
movie_genre(movie_genre_id, genre_name)
person(person_id, person_name, gender)
role(person_id, movie_id, role_name, role_type_id)
role_type(role_type_id, type_name)
I was asked to display the name of the top 7 directors with at least 3 movies in the list, the number of movies they are in and the average rating of their movies, sorted by the average rating. With the query below I managed to get the name of the directors, the number of movies they are in and the average rating, but I'm having issues limiting it to the top 7 and sorting them by the average rating. I tried using LIMIT and ORDER BY, but I'm getting syntax errors.
SELECT
person_name, COUNT(role.movie_id), AVG(rating)
FROM
movie
INNER JOIN
role
ON role.movie_id = movie.movie_id
INNER JOIN
person
ON role.person_id = person.person_id
INNER JOIN
role_type
ON role.role_type_id = role_type.role_type_id
WHERE
type_name = 'director'
GROUP BY
person_name
HAVING
COUNT(role.movie_id) > 2;
I can even order by the number of movies they did and limit it to the top 7, but for God I cannot order it by the AVG(rating)
person_name COUNT(role.movie_id) AVG(rating)
Hitchcock, Alfred 9 8.2888890372382
Kubrick, Stanley 8 8.2999999523163
Wilder, Billy 6 8.3000000317891
Spielberg, Steven 6 8.4000000953674
Scorsese, Martin 6 8.3166666030884
Nolan, Christopher 6 8.5333331425985
Tarantino, Quentin 6 8.3666666348775
In MySQL, Aliases defined in the Select clauses can be used in the Group By, Order By and Having clauses.
Use Order by .. DESC to sort the result-set in descending order and Limit 7 to get only 7 rows.
You should use proper Aliasing in multi table queries, to avoid ambiguous and unintended behavior.
You need to use Group By on person_id also, as there may be cases where director(s) have same name.
If you have duplicate entries in role table, you will have to use Count(Distinct ...) to avoid counting duplicate rows.
Try the following query:
SELECT
p.person_id,
p.person_name,
COUNT(r.movie_id) AS movies_count,
AVG(m.rating) AS average_rating
FROM
movie AS m
INNER JOIN
role AS r
ON r.movie_id = m.movie_id
INNER JOIN
person AS p
ON r.person_id = p.person_id
INNER JOIN
role_type AS rt
ON r.role_type_id = rt.role_type_id
WHERE
rt.type_name = 'director'
GROUP BY
p.person_id,
p.person_name
HAVING
movies_count > 2
ORDER BY
movies_count DESC,
average_rating DESC
LIMIT 7

SQL subquery to return MIN of a column and corresponding values from another column

I'm trying to query
number of courses passed,
the earliest course passed
time taken to pass first course, for each student who is not currently expelled.
The tricky part here is 2). I constructed a sub-query by mapping the course table onto itself but restricting matches only to datepassed=min(datepassed). The query appears to work for a very sample, but when I try to apply it to my full data set (which would return ~1 million records) the query takes impossibly long to execute (left it for >2 hours and still wouldn't complete).
Is there a more efficient way to do this? Appreciate all your help!
Query:
SELECT
S.id,
COUNT(C.course) as course_count,
C2.course as first_course,
DATEDIFF(MIN(C.datepassed),S.dateenrolled) as days_to_first
FROM student S
LEFT JOIN course C
ON C.studentid = S.id
LEFT JOIN (SELECT * FROM course GROUP BY studentid HAVING datepassed IN (MIN(datepassed))) C2
ON C2.studentid = C.studentid
WHERE YEAR(S.dateenrolled)=2013
AND U.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
ORDER BY S.id
Student table
id status dateenrolled
1 graduated 1/1/2013
3 graduated 1/1/2013
Expelled table
id dateexpelled
2 5/1/2013
Course table
studentid course datepassed
1 courseA 5/1/2014
1 courseB 1/1/2014
1 courseC 2/1/2014
1 courseD 3/1/2014
3 courseA 1/1/2014
3 couseB 2/1/2014
3 courseC 3/1/2014
3 courseD 4/1/2014
3 courseE 5/1/2014
SELECT id, course_count, days_to_first, C2.course first_course
FROM (
SELECT S.id, COUNT(C.course) course_count,
DATEDIFF(MIN(datepassed),S.dateenrolled) as days_to_first,
MIN(datepassed) min_datepassed
FROM student S
LEFT JOIN course C ON C.studentid = S.id
WHERE S.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND S.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
) t1 LEFT JOIN course C2
ON C2.studentid = t1.id
AND C2.datepassed = t1.min_datepassed
ORDER BY id
I would try something like:
SELECT s.id, f.course,
COALESCE( DATEDIFF( c.first_pass,s.dateenrolled), 0 ) AS days_to_pass,
COALESCE( c.num_courses, 0 ) AS courses
FROM student s
LEFT JOIN
( SELECT studentid, MIN(datepassed) AS first_pass, COUNT(*) AS num_courses
FROM course
GROUP BY studentid ) c
ON s.id = c.studentid
JOIN course f
ON c.studentid = f.studentid AND c.first_pass = f.datepassed
LEFT JOIN expelled e
ON s.id = e.id
WHERE s.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND e.id IS NULL
This query assumes a student can pass only one course on a given day, otherwise you can get more than one row for a student as its possible to have many first courses.
For performance it would help to have an index on dateenrolled in student table and a composite index on (studentid,datepassed) in courses table.