SELECT AVG subquery with condition - sql

I have the follwing tables:
teachers , teacher_rating and cities.
teachers has the following columns:
teacherID,location,name,othercities,status
teacher_rating has the following columns:
ratingId, teacherId,content,ratingNumber,created,status,userName
cities has the following columns:
id,name
I'm trying to sort all of the teachers (rows from teachers where status=0) by their average rating, this is my php variable with the SQL:
$q="SELECT
*,
AVG(pr.ratingNumber) AS rating_average
FROM teachers as p
LEFT JOIN teacher_rating pr
ON pr.teacherId = p.teacherID
WHERE p.location=(SELECT `name` FROM `cities` WHERE `id`=:location) AND p.status=0 OR p.othercities REGEXP CONCAT('[[:<:]](', :location,')( |)[[:>:]]') AND p.status=0
GROUP BY p.teacherID
ORDER BY rating_average DESC
";
It works fine, the only problem is that the rating average includes ratings from the teacher_rating table where status=1, I want it to calculate the average rating only using the values from teacher_rating where status=0.
I'm not sure how to approach this problem, thanks for the help!

How about adding pr.status=0 in the join?
ON pr.teacherId = p.teacherID AND pr.status=0

You are mixing AND and OR, so you should use parentheses. I think you intend:
WHERE p.status = 0 AND
pr.status = 0 AND
(p.location = (SELECT `name` FROM `cities` WHERE `id`=:location) OR
p.othercities REGEXP CONCAT('[[:<:]](', :location,')( |)[[:>:]]')
)

Related

Which Join for SQL plus query

I have 4 tables, I would like to select one column from each table, but only if the department has both 'Mick' and 'Dave working in it (must have both names, not one or the other). But it does not seem to be working properly:
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM STUDENTS
NATURAL JOIN SCHOOLS NATURAL JOIN TOWNS NATURAL JOIN
COUNTIES
WHERE FIRST_NAME IN ('Mick','Dave)
/
I'm going wrong somewhere (probably lots of places :( ). Any help would be great
Don't use NATURAL JOIN. It is an abomination, because it does not take properly declared foreign key relationships into account. It only looks at the names of columns. This can introduce really hard to find errors.
Second, what you want is aggregation:
select sc.SCHOOL_NAME, t.TOWN, c.COUNTY
from STUDENTS st join
SCHOOLS sc
on st.? = sc.? join
TOWNS t
on t.? = ? join
COUNTIES c
on c.? = t.?
where FIRST_NAME in ('Mick', 'Dave')
group by sc.SCHOOL_NAME, t.TOWN, c.COUNTY
having count(distinct st.first_name) = 2;
The ? are placeholders for table and column names. If you are learning SQL, it is all the more important that you understand how columns line up for joins in different tables.
A where clause can only check the values in a single row. There is a separate row for each student, so there is no way -- with just a where -- to find both students. That is where the aggregation comes in.
You need at least three Join conditions, and properly end the string Dave with quote :
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM SCHOOLS h
JOIN TOWNS t ON (t.id=h.town_id)
JOIN COUNTIES c ON (t.county_id=c.id)
WHERE EXISTS ( SELECT school_id
FROM STUDENTS s
WHERE s.first_name in ('Mick','Dave')
AND school_id = h.id
GROUP BY school_id
HAVING count(1)>1
);
SQL Fiddle Demo
You can use an analytic function in a sub-query to count the students who have the name Mick or Dave for each school_id (assuming that is your identifier for a school):
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM ( SELECT *
FROM (
SELECT d.*,
COUNT(
DISTINCT
CASE WHEN FIRST_NAME IN ( 'Mick', 'Dave' ) THEN FIRST_NAME END
) OVER( PARTITION BY school_id )
AS num_matched
FROM STUDENTS d
)
WHERE num_matched = 2
)
NATURAL JOIN SCHOOLS
NATURAL JOIN TOWNS
NATURAL JOIN COUNTIES;
SQLFiddle
You would also be better to use an INNER JOIN and explicitly specify the join condition rather than relying on NATURAL JOIN.

Oracle sql - referencing tables

My school task was to get names from my movie database actors which play in movies with highest ratings
I made it this way and it works :
select name,surname
from actor
where ACTORID in(
select actorid
from actor_movie
where MOVIEID in (
select movieid
from movie
where RATINGID in (
select ratingid
from rating
where PERCENT_CSFD = (
select max(percent_csfd)
from rating
)
)
)
);
the output is :
Gary Oldman
Sigourney Weaver
...but I'd like to also add to this select mentioned movie and its rating. It accessible in inner selects but I don't know how to join it with outer select in which i can work just with rows found in Actor Table.
Thank you for your answers.
You just need to join the tables properly. Afterwards you can simply add the columns you´d like to select. The final select could be looking like this.
select ac.name, ac.surname, -- go on selecting from the different tables
from actor ac
inner join actor_movie amo
on amo.actorid = ac.actorid
inner join movie mo
on amo.movieid = mo.movieid
inner join rating ra
on ra.ratingid = mo.ratingid
where ra.PERCENT_CSFD =
(select max(percent_csfd)
from rating)
A way to get your result with a slightly different method could be something like:
select *
from
(
select name, surname, percent_csfd, row_number() over ( order by percent_csfd desc) as rank
from actor
inner join actor_movie
using (actorId)
inner join movie
using (movieId)
inner join rating
using(ratingId)
(
where rank = 1
This uses row_number to evaluate the "rank" of the movie(s) and then filter for the movie(s) with the highest rating.

Adding 0 or null to missing fields

I have the following tables:
student(sid, sname)
teacher(tid, tname)
enrollment(sid, cid, tid)
course(cid, course)
rank(sid, tid, grade, date, valid)
I need to calculate the average grade of all the teachers (data is in rank table), when only the grade from the most recent date counts (and if it's invalid - ignore it).
I wrote the following query, and it's working nice. The problem is that I also need the average for ALL the teachers, including those who were not ranked yet/their rank is invalid (their average grade will be 0 in that case, and I'll have to count their students like I did for the others).
I think it's something with LEFT OUTER JOIN, but all the examples I see online have only two tables in FROM, and I can't figure out the right syntax in my case.
SELECT teacher.tid,
tname,
AVG(grade) AS avgGrade,
COUNT(DISTINCT enrollment.sid) AS studCount
FROM rank,
teacher,
enrollment,
( SELECT rank.sid, rank.tid, MAX(date) AS maxDate
FROM rank
GROUP BY sid, tid
) lastGrades
WHERE teacher.tid=enrollment.tid
AND rank.tid=teacher.tid
AND rank.tid=lastGrades.tid
AND rank.sid=lastGrades.sid
AND rank.date=lastGrades.maxDate
AND valid = TRUE
GROUP BY teacher.tid, tname
You could use a subquer to look up the latest rank per (teacher, student) combination. Use a left join to count enrollments that have not been ranked:
select t.tid
, t.tname
, avg(r.grade) as AverageRank
, count(distinct e.sid) as StudentCount
from teacher t
join enrollment e
on t.tid = e.tid
left join
rank r
on r.tid = t.tid
and r.sid = e.sid
and r.valid = true
and r.date =
(
select max(date)
from rank r2
where r2.sid = r.sid
and r2.tid = r.tid
and r2.valid = true
)
group by
t.tid
, t.tname
Example without data at SQL Fiddle.
The table design is kind of strange. You'd expect a student to enroll in a course, not in a teacher!

sort sql query by values in another table

I am querying a table named artists, but I would like to sort the response based on a table named paintings (an artist has_many paintings - the painting table has an artist_id column).
To be more specific, I want to sort the artists by their most recent painting (paintings have a column named date_created). Does anyone know how this could be done?
Ideally this should be done using ANSI joins:
SELECT DISTINCT a.artist
FROM artists a
INNER JOIN paintings p
ON a.artistID = p.artistID
ORDER BY p.date_created desc
Perhaps something like this, depending on the specifics of your schema?
SELECT DISTINCT artists.* FROM
artists, paintings
WHERE artists.id = paintings.artist_id
ORDER BY paintings.painting_date DESC;
This will join the two tables on the artist id, and then order by their painting dates. DISTINCT ensures you only get one row per artist.
This will only return each artist once, with the latest date_created value for that artist.
SELECT artists.name, paintings.date_created
FROM artists JOIN (
SELECT artist_id, MAX(date_created) as date_created FROM paintings GROUP BY artist_id
) paintings ON artists.id = paintings.artist_id
ORDER BY paintings.date_created DESC
If I understand your requirement correctly:
1) Write an aggregation query that returns each artist and his/her latest painting;
2) Use it as a sub-query, joining it to the artists table;
3) SELECT columns from the join, ordering by the date of latest painting.
You can create a query:
select artistid, max(paintingdate)
from paintings
group by artistid
and then join to that as an inline-view:
select artistname from artist
inner join
(
select artistid, max(paintingdate) as latestdate
from paintings
group by artistid
) as Foo
on artist.artistid = Foo.artistid
order by latestdate desc

MYSQL get amount of results

When you use mysql, you can do something like this:
SELECT * FROM student WHERE teacher_id = 1
...then you get the amount of results, and with that the result. But I only want the amount of results but then for every teacher.
Is that possible in mysql like sizeof or something?
Also, if there are teachers that have no students is it true that there not in the list? or does it have to say 0?
Do you mean you want the number of students for every teacher? You can use something like this:
SELECT teacher_id, count(*) AS student_count
FROM student
GROUP BY teacher_id
This will not include teachers that don't have any students. To include them in the results, you need to use a JOIN (assuming you have a teacher table):
SELECT teacher.id, sum(if(student.id IS NULL, 0, 1)) AS student_count
FROM teacher
LEFT JOIN student ON student.teacher_id=teacher.id
GROUP BY teacher.id
SELECT teacher_id, count(*) as count FROM student GROUP BY teacher_id;
Use:
SELECT t.teacher_id,
IFNULL(ns.num_students, 0)
FROM TEACHERS t
LEFT JOIN (SELECT s.teacher_id,
COUNT(s.student_id) 'num_students'
FROM STUDENTS s
GROUP BY s.teacher_id) ns ON ns.teacher_id = t.teacher_id