Ho finito per risolverlo con una sottoquery correlata:
$qb
->select('a')
->from('Article', 'a')
->join('a.views', 'v')
->orderBy('v.viewDate', 'DESC')
->setMaxResults(20)
// Only select the most recent article view for each individual article
->where('v.viewDate = (SELECT MAX(v2.viewDate) FROM ArticleView v2 WHERE v2.article = a)')
In questo modo l'ordinamento ignora ArticleView diverso dal più recente per un determinato articolo. Sebbene la mia ipotesi sia che questo funzioni abbastanza male rispetto alle altre soluzioni SQL grezze, qualsiasi risposta con prestazioni migliori sarebbe comunque molto apprezzata :).