Типичный код, с использованием ORM Propel2 выглядит примерно так:

$query = BooksQuery::create()->filterByYear(2016);

foreach($query->find() as $book) {
   $author_name = $book->getAuthor()->getName();
}

Проблема в том что на каждую выборку книги, в данном примере будет создан дополнительный запрос к связанной таблице author и в итоге 1 потенциальный запрос к БД в случае RAW SQL будет заменен на 51 (пусть и по PK) например для выборки из 50 книг.

Непорядок, в частности за такое поведение часто критикуют все ORM.

Но есть очень простой способ "научить" Propel действовать более разумно:

$query = BooksQuery::create()->filterByYear(2016)->joinWithAuthor();

foreach($query->find() as $book) {
   $author_name = $book->getAuthor()->getName();
}

В этом случае планировщик запросов сам заджойнит нужную нам дополнительную таблицу а маппер автоматически загрузит дополнительные объекты в структуру. Причем, поскольку по умолчанию Propel контролирует экземпляры по первичному ключу - дополнительных объектов будет ровно по числу уникальных сущностей author в выборке.

Profit!