Question

I have two tables, conversation and message (a conversation has many messages).

conversation:
  - id

message:
  - id
  - conversation_id
  - created_at
  - read (boolean)

I want to count conversations where last (ordered by message.created_at) message's read attribute is false.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top