Вы можете получить необходимые данные из одного запроса с помощью рекурсивного CTE. Хитрость в том, чтобы удерживать нисходящие уровни вместе. В качестве функции этот запрос становится единым элементом в функции SQL. Что касается форматирования, которое вам нужно, то это то, что лучше оставить приложению (в общем, это всегда лучше для форматирования.) Я бы, вероятно, даже не пытался сделать это в SQL и не буду делать этого здесь. Вы можете попробовать это как упражнение. Но чтобы помочь в этом, я сгенерировал 2 дополнительных столбца. Первый столбец (new_heading) либо нулевой, либо содержит звездочку (*). Звездочка указывает, что эта строка представляет строку заголовка. Последний столбец (hier_level) показывает уровень в иерархии для этой строки (обратите внимание, что заголовок будет иметь уровень 0) и может использоваться для определения требуемого отступа.
create or replace function todays_menu()
returns table (new_heading text
,menu_heading text
,menu_item text
,price numeric
,hier_level integer
)
language sql
as $$
with recursive the_menu (menu_heading, menu_item, price, item_id, heading_id, parent_id, mpath) as
(select mh.name, m.name menu_item, m.price, m.id item_id, m.menu_heading_id, m.parent_id, array[m.id]
from menu m
join menu_headings mh on m.menu_heading_id = mh.id
where parent_id is null
union all
select mh.name, m.name, m.price, m.id ,m.menu_heading_id, m.parent_id, array_append(tm.mpath, m.id)
from the_menu tm
join menu m on m.parent_id = tm.item_id
join menu_headings mh on m.menu_heading_id = mh.id
)
select case when coalesce(lag(menu_heading) over( order by heading_id, mpath),'') <> menu_heading then '*' else null end new_menu
, menu_heading, menu_item, price, array_length(mpath,1) hier_level
from the_menu
order by heading_id, mpath;
$$;
-- test
select * from todays_menu();
В функции обратите внимание на столбец mpath. Этот столбец создает массив идентификаторов меню. Поскольку массивы могут быть отсортированы, это используется для поддержания каждой цепи в дереве в правильном отношении друг к другу. Также он определяет упомянутый выше уровень hier_level. Примечание. Сам запрос можно извлечь и запустить отдельно. Кроме того, это зависит от следующего предложения @a_horse_with_no_name, чтобы использовать null без parent_id вместо 0. Это всегда должно соблюдаться.