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.