I have this query:
SELECT
(SELECT COUNT(media_id) FROM likes WHERE like_state = true AND media_id = ?) AS likes,
(SELECT COUNT(media_id) FROM likes WHERE like_state = false AND media_id = ?) AS dislikes,
(SELECT media_views FROM media_thumbnail WHERE media_id = ?) AS views;
It works fine but I'm not sure if this is the best way. Any recommendations?
I don't think this requires any explanation, but... It counts how many likes, or dislikes are in function of the like_state
(true
= like, false
= dislike) and returns the view count of the associated video.
It's definitely faster to use a single SELECT
with two counts on table likes
instead of two separate SELECT
:
SELECT COUNT( like_state OR NULL) AS likes
, COUNT(NOT like_state OR NULL) AS dislikes
,(SELECT media_views FROM media_thumbnail WHERE media_id = ?) AS views
FROM likes
WHERE media_id = ?;
There are corner-case differences between this query with an uncorrelated subquery, your original query with two implicit CROSS JOIN
(a comma ,
in the FROM
list is almost identical to a CROSS JOIN
) as well as Gordon's answer with a CROSS JOIN
to the last subquery.
The first two subqueries always return a row, so a CROSS JOIN
cannot eliminate the result.
But the third query might not find the given media_id
in table media_thumbnail
and return no row. A CROSS JOIN
to that results in no row
for the whole query.
My query with a subquery in the SELECT
list converts no row to a null value and still returns a result row. The same could be achieved with LEFT JOIN ... ON true
:
My subquery in the SELECT
raises an exception if it returns more than one row - which cannot happen if media_thumbnail.media_id
is defined UNIQUE
or PRIMARY KEY
, and probably should not happen in any case.
About conditional counts:
The pretty and clean syntax would be with a FILTER
clause:
SELECT COUNT(*) FILTER (WHERE like_state) AS likes
, COUNT(*) FILTER (WHERE NOT like_state) AS dislikes
...
A bit more verbose, but may be easier to read. Performance is basically the same.