В SqlServer вариант 1 можно реализовать с помощью materialized view, в этом случае по затратности он будет приближаться к третьему варианту.
Создаём представление:
create view dbo.LikeCount
with schemabinding
as
select post_id, Cnt = count_big(*)
from dbo.Like
group by post_id
GO
Добавляем индекс, материализующий представление:
create unique clustered index IX_LikeCount on dbo.LikeCount (post_id)
GO
Аналогичное представление можно создать для таблицы Comment. После чего можно запрашивать данные следующим образом:
select p.id, p.title, isnull(lc.Cnt, 0) as LikeCnt, isnull(cc.Cnt, 0) as CommentCnt
from Post p
left join LikeCount lc with (noexpand) on lc.post_id = p.id
left join CommentCount cc with (noexpand) on cc.post_id = p.id
Удобство такого варианта в том, что при изменении данных в таблицах Comment и Like количества будут автоматически пересчитываться (в вариантах 2 и 3 эту логику придётся реализовывать специально - в триггерах, или процедурах/запросах, которые работают с добавлением/удалением лайков и комментариев).
Минус в том, что для лайков и комментариев представления раздельные, в следствие чего дополнительных соединений в запросе два (в варианте 3 оно будет одно, а в варианте 2 его вообще не будет). Также, если есть вероятность, что в будущем может потребоваться удалять пользователей, оставляя лайки, то этот вариант не подойдёт, и лучше смотреть в сторону вариантов 2 и 3.
Вариант 2 выгоднее для select (в запросе не потребуется дополнительное соединение для вытаскивания количеств лайков и комментариев). Вариант 3 лучше с точки зрения независимости постов от сопутствующих им количеств (если происходит изменение записи, соответствующей какому-то посту, и в это же время кому-то вздумалось лайкнуть эту запись, то эти два действия не будут блокировать друг-друга).