Home > database >  Aggregate functions are not allowed in FILTER
Aggregate functions are not allowed in FILTER

Time:10-31

I have this SQL query:

SELECT
    users.id,
    users.name,
    users.avatar,
    MAX(messages.created_at) max_created_at,
    MAX(messages.body) FILTER (WHERE messages.created_at = MAX(messages.created_at)) last_message,
    CASE WHEN(COUNT(messages.is_read) FILTER (WHERE is_read = false AND messages.from_id != 14) = 0) THEN true ELSE false END is_read,
    COUNT(messages.is_read) FILTER (WHERE is_read = false AND messages.from_id != 14) count_unread
FROM 
    messages 
INNER JOIN 
    users ON messages.from_id = users.id OR messages.to_id = users.id
WHERE 
    (messages.from_id = 14 OR messages.to_id = 14) 
    AND users.id != 14
GROUP BY 
    users.id;

But, this query is showing an error

Aggregate functions are not allowed in FILTER

When I change

MAX(messages.body) FILTER (WHERE messages.created_at = MAX(messages.created_at)) last_message

to

MAX(messages.body) FILTER (HAVING messages.created_at = MAX(messages.created_at)) last_message

the query is now showing this error

Syntax error at or near "HAVING"

How to fix this?

CodePudding user response:

Could be done with window functions and DISTINCT ON in a single instance of SELECT:

SELECT DISTINCT ON (u.id)
       u.id, u.name, u.avatar
     , m.created_at  AS max_created_at
     , m.body        AS last_message
     , bool_and(is_read) FILTER (WHERE m.from_id <> 14)          OVER w AS is_read
     , count(*) FILTER (WHERE NOT m.is_read AND m.from_id <> 14) OVER w AS count_unread
FROM   messages m
JOIN   users    u ON u.id IN (m.from_id, m.to_id)
WHERE  14 IN (m.from_id, m.to_id)
AND    u.id <> 14
WINDOW w AS (PARTITION BY u.id)
ORDER  BY u.id, m.created_at DESC NULLS LAST, m.body DESC NULLS LAST;

Related:

CodePudding user response:

I think you require this:

select a.* from(
SELECT
    users.id,
    users.name,
    users.avatar,
    MAX(messages.created_at) max_created_at,
    messages.body   last_message,
    CASE WHEN(COUNT(messages.is_read) FILTER (WHERE is_read = false AND messages.from_id != 14) = 0) THEN true ELSE false END is_read,
    COUNT(messages.is_read) FILTER (WHERE is_read = false AND messages.from_id != 14) count_unread,
        RANK () OVER ( 
        PARTITION BY user_id
        ORDER BY messages.created_at DESC
    ) message_rank 
FROM 
    messages 
INNER JOIN 
    users ON messages.from_id = users.id OR messages.to_id = users.id
WHERE 
    (messages.from_id = 14 OR messages.to_id = 14) 
    AND users.id != 14
GROUP BY 
    users.id)a
where message_rank=1;

CodePudding user response:

I would create a CTE with MAX(messages.created_at) as a column max_created_at and reuse your query with the CTE instead of table messages (w/o looking into its logic).

with t as (select *, MAX(created_at) over () max_created_at from messages)
SELECT -- Basically this is your query that follows with some simplification
    users.id,
    users.name,
    users.avatar,
    max(t.max_created_at),
    MAX(t.body) FILTER (WHERE t.created_at = t.max_created_at) last_message,
    COUNT(t.is_read) FILTER (WHERE not is_read AND t.from_id != 14) = 0 is_read,
    COUNT(t.is_read) FILTER (WHERE not is_read AND t.from_id != 14) count_unread
FROM 
    t 
INNER JOIN 
    users ON t.from_id = users.id OR t.to_id = users.id
WHERE 
    (t.from_id = 14 OR t.to_id = 14) 
    AND users.id != 14
GROUP BY 
    users.id;
  • Related