Você pode usar funções analíticas
SELECT *
FROM (SELECT c.*,
rank() over (partition by user_id order by ts desc) rnk
FROM comments c)
WHERE rnk = 1
Dependendo de como você deseja lidar com empates (se puder haver duas linhas com o mesmo
user_id
e ts
), você pode querer usar o row_number
ou dense_rank
função em vez de rank
. rank
permitiria que várias linhas fossem as primeiras se houvesse um empate. row_number
retornaria arbitrariamente uma linha se houvesse um empate. dense_rank
se comportaria como rank
para as linhas que empataram em primeiro, mas considerariam que a próxima linha seria a segunda, em vez de a terceira, assumindo duas linhas empatadas em primeiro.