Я бы сделал это, используя коррелированный подзапрос:
select b.*,
(select string_agg(b2.title, ', ') within group (order by b2.title)
from books b2
where b2.title <> b.title
) as other_titles
from books b;
Коррелированный подзапрос облегчает сохранение других столбцов, которые могут быть в вашей таблице.
Если честно, производительность не будет особенно хорошей. Вы можете объединить все вместе и затем удалить свой заголовок:
select b.*,
stuff(replace(bb.titles, ', ' + b.title, ''), 1, 2, '') as other_titles
from books b cross join
(select ', ' + string_agg(b2.title, ', ') as titles
from books
) bb;
Вызов replace()
на titles
, как правило, будет иметь лучшую производительность, чем цикл по всей таблице для восстановления строки для каждой строки.