To just get the count as outlined:
SELECT count(*) AS msg_ct
FROM (
SELECT DISTINCT ON (conversation_id) *
FROM messages
ORDER BY conversation_id, created_at DESC
) sub
WHERE NOT read;
You do no need to include the parent table conversation
here at all.
The subquery returns the last row per conversation ("ordered by messages.created_at"
).
Details here: Select first row in each GROUP BY group?
The outer query counts those where read
is FALSE
.
Alternative with NOT EXISTS
:
SELECT count(*) AS msg_ct
FROM messages m
WHERE NOT read
AND NOT EXISTS (
SELECT 1
FROM messages m1
WHERE m1.conversation_id = m.conversation_id
AND m1.created_at > m.created_at
);
Might be faster for big tables. You'd have to test with EXPLAIN ANALYZE
.