Как реализовать широкий многоуровневый запрос PostgreSQL с самообъединением?

У меня есть несколько строк, которые представляют собой person. У каждого person есть столбец для bio_mother _uuid и bio_father_uuid. Я использовал эту страницу (я упоминаю об этом, потому что на нее есть ссылка в аналогичном вопросе) для реализации этого соединения.

SELECT 
base.uuid base_uuid, base.created_at base_created_at, base.givenname base_givenname, base.bio_mother_uuid base_bio_mother_uuid, base.bio_father_uuid base_bio_father_uuid,
mother.uuid mother_uuid, mother.created_at mother_created_at, mother.givenname mother_givenname, mother.bio_mother_uuid mother_bio_mother_uuid, mother.bio_father_uuid mother_bio_father_uuid,
father.uuid father_uuid, father.created_at father_created_at, father.givenname father_givenname, father.bio_mother_uuid father_bio_mother_uuid, father.bio_father_uuid father_bio_father_uuid
FROM person AS base
LEFT JOIN person AS mother ON base.bio_mother_uuid = mother.uuid
LEFT JOIN person as father ON base.bio_father_uuid = father.uuid;

Я надеюсь разложить строки в эту структуру Go.

type Person struct{
  UUID string
  Givenname string
  Bio_mother *Person
}

Если я делаю запрос, используя приведенный выше запрос, я получаю NULL для каждого человека, у которого нет данных о матери или отце (это хорошо), но затем при построении данных о матери/отце я в конечном итоге создаю неполные экземпляры Person, поскольку я не в запросе нет «прародительских» данных. Я мог бы решить эту проблему, внедрив логику в код для повторного запроса, если существует UUID, но это значительно замедляет работу программы, чем каким-то образом делать это в запросе.

Для меня это учебный проект, но я не совсем уверен, что нужно гуглить, чтобы получить хороший ответ.

Как я могу адаптировать свой запрос для обработки моего варианта использования и возврата только в одной строке? Это будет связано с рекурсивным CTE, но я не совсем уверен, как его реализовать после просмотра некоторых страниц учебника по PostgreSQL.

Редактировать: Я понял, что отсутствие данных означает необходимость динамически называть эти столбцы или, может быть, какой-то код? Может быть, аддитивные префиксы? В этом примере вывода я просто использую «псевдоним». Обратите внимание, что Гертруде не хватает данных о матери: в строках есть пробел. Это потому, что в модели всегда на первом месте были матери, и я не уверен, что вообще согласен с тем, что моделирование модели имеет смысл, но это модель, которую я могу придумать прямо сейчас для этих данных. Пожалуйста, дайте мне знать, если я стреляю не в том направлении.

(Я также сократил столбцы до «другие вещи» после первых нескольких итераций для удобства чтения, и я сажусь в поезд без Wi-Fi, поэтому хочу заранее подготовить этот вопрос и отключить его)

Пример ввода и вывода

Пример данных

uuid собственное имя фамилия bio_mother_uuid bio_father_uuid аа Дэйвид Смит аб переменный ток аб Мэри Смит объявление ае переменный ток Джон Смит аф аг объявление дерзость снег ах ае Ричард снег аф Кэти Смит аг Мэтью Смит ах Уинстон Снег

Желаемый результат

base_uuid базовое_имя базовая_фамилия base_bio_mother_uuid base_bio_father_uuid mother_uuid мать_имя мать_фамилия mother_bio_mother_uuid mother_bio_father_uuid отец_uuid отец_другие вещи alias_uuid alias_otherstuff alias_uuid alias_otherstuff alias_uuid alias_otherstuff alias_uuid alias_otherstuff alias_uuid alias_otherstuff аа Дэйвид Смит аб переменный ток аб Мэри Смит объявление ае переменный ток Джон Смит.. объявление Гертруда Сноу ае Ричард Сноу аф Кэти Смит аг Мэтью Смит Уинстон Сноу аб Мэри Смит объявление ае объявление Гертруда снег ах ае Ричард Сноу ах Уинстон Сноу

РЕПО ДЛЯ ЭТОЙ РАБОТЫ на Github

Привет, ты вообще-то не задал вопрос. Кроме того, пожалуйста, не ссылайтесь на внешние сайты, вопросы на этом форуме должны быть самостоятельными, поэтому, пожалуйста, четко объясните, с чем вы столкнулись, а затем задайте вопрос по этому поводу.

NickW 01.04.2024 12:04

@NickW Абсолютлети. Я перефразировал свой вопрос, чтобы попытаться лучше объяснить мою проблему, и фактически задал вопрос там :) Спасибо за отзыв о том, как лучше задать вопрос.

Sai 01.04.2024 14:34

Добавьте несколько строк примеров данных и ожидаемый результат. Рекурсивный CTE полностью способен возвращать нужные вам данные. Альтернативные, явные левые соединения также могут это сделать.

The Impaler 01.04.2024 14:56

@TheImpaler добавил примеры данных! Я думаю, что то, как будет работать именование, меня сбивает с толку. Я попробовал создать несколько рекурсивных CTE, но обнаружил, что некоторые руководства конфликтуют друг с другом, и мне не удалось экстраполировать их в свой вариант использования. Тогда я решил опубликовать этот вопрос.

Sai 01.04.2024 15:43

Как далеко назад вы хотите пойти? (если мать представлена ​​как Person, то включаем ли мы мать матери?). В зависимости от того, что вам нужно, это может быть не так сложно, как вы думаете (см. эту скрипту), но требования все еще не совсем ясны.

Brits 02.04.2024 02:09

@Brits Я думаю, что это произвольное число. На самом деле, он никогда не будет моделировать более двух поколений, поэтому, возможно, для безопасности можно использовать 5 уровней.

Sai 02.04.2024 21:32

@Sai - Хорошо, если вы хотите вернуться так далеко назад, у вас может получиться много столбцов (32 - количество атрибутов на человека). Я бы предложил вернуть запись каждого человека и объединить ее в вашем коде (по одной записи на человека, а не привязывать все к одной строке). Можно было бы сделать это в одну строку, но вам нужно будет написать код для демаршалинга, и я чувствую, что это будет более запутанно. Альтернативой может быть возврат данных в формате JSON (немного сложнее написать запрос, но, вероятно, его легче демаршалировать).

Brits 02.04.2024 21:42

Да, я пошел по этому пути, потому что заметил, как запросы для каждого родственника сильно замедляли работу базы данных, поэтому я попытался попытаться применить больше логики в запросах, чтобы справиться с этим. Думаю, попробую найти какую-нибудь комбинацию. Знаете ли вы, как будет выглядеть запрос для 3 поколений (1 ребенок, 2 родителя, 4 бабушки и дедушки). Не знаю, как обрабатывать динамическое именование такого количества строк или как писать этот рекурсивный запрос, и простое написание этого запроса вручную кажется многословным.

Sai 02.04.2024 22:01
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
8
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Примечание. Это ответ, отражающий обсуждение в комментариях.

Учитывая пример структуры/данных:

create table people
(
    uuid            text,
    givenname       text,
    surname         text,
    bio_mother_uuid text,
    bio_father_uuid text
);

insert into people (uuid, givenname, surname, bio_mother_uuid, bio_father_uuid)
values ('aa', 'david', 'smith', 'ab', 'ac'),
       ('ab', 'mary', 'smith', 'ad', 'ae'),
       ('ac', 'john', 'smith', 'af', 'ag'),
       ('ad', 'gertude', 'snow', null, 'ah'),
       ('ae', 'Richard', 'snow', null, null),
       ('af', 'Katie', 'smith', null, null),
       ('ag', 'Mathew', 'smith', null, null),
       ('ah', 'Winston', 'Snow', null, null);   

Вы можете запустить рекурсивный CTE, например:

with recursive t as (
  select 
       p.uuid    as request_uuid,
       p.uuid                 as uuid,
       p.givenname            as givenname,
       p.surname              as surname,
       p.bio_mother_uuid      as bio_mother_uuid,
       p.bio_father_uuid      as bio_father_uuid,
       0 as generation
       from people p
  union all 
  select 
       child.request_uuid    as request_uuid,
       p.uuid                 as uuid,
       p.givenname            as givenname,
       p.surname              as surname,
       p.bio_mother_uuid      as bio_mother_uuid,
       p.bio_father_uuid      as bio_father_uuid,
       child.generation + 1 as generation       
       from 
       t child join 
       people p on p.uuid = child.bio_mother_uuid or p.uuid = child.bio_father_uuid
  )
  select * from t where request_uuid = 'aa'  and generation < 3  

Результат (рабочий пример):

request_uuid uuid собственное имя фамилия bio_mother_uuid bio_father_uuid поколение аа аа Дэйвид Смит аб переменный ток 0 аа аб Мэри Смит объявление ае 1 аа переменный ток Джон Смит аф аг 1 аа объявление дерзость снег ах 2 аа ае Ричард снег 2 аа аф Кэти Смит 2 аа аг Мэтью Смит 2

Затем это предоставляет всю информацию, необходимую для заполнения вашей структуры (для этого потребуется некоторый специальный код). Получение всех данных в одном запросе, как этот, вероятно, будет быстрее, чем выполнение нескольких запросов (но это будет зависеть от ряда факторов).

Хотя могут быть способы вернуть все эти данные в одну строку, я чувствую, что с этим будет сложнее справиться, чем с описанным выше (вам все равно понадобится специальный код для демаршалинга, и у вас возникнет дополнительная сложность, связанная с необходимостью обработки различное количество столбцов). Альтернативой может быть возврат структуры в формате JSON (это усложнит SQL, но упростит ваш код Go).

Отлично! Не могли бы вы указать мне какие-либо ресурсы, позволяющие как вернуть это в одной строке, так и вернуть JSON. Я думаю, что попробую все разные варианты и посмотрю, что мне скажут тесты. Благодаря тонну!

Sai 02.04.2024 23:42

На самом деле только функции Postgres JSON (работать с ними может быть немного неудобно). Скорость вряд ли будет существенно отличаться, поскольку вы по-прежнему будете выполнять один запрос и передавать сопоставимый объем данных. Ref возвращает одну строку; это будет просто запрос с большим количеством самосоединений...

Brits 02.04.2024 23:48

Я думаю, что причина, по которой я хочу, чтобы они все были в одной строке, заключается в том, что если мне нужно запросить несколько строк, я демаршализую строку за строкой буквально через цикл for, поэтому мне придется затем обходить этот итератор и иметь некоторая дополнительная логика, чтобы выяснить, где я нахожусь, и что я не напутал при построении дерева. Но я МОГУ это сделать, поэтому буду работать над этим.

Sai 02.04.2024 23:55

Вероятно, проще сначала просто поместить результаты в map (тогда довольно просто построить дерево, рекурсивно просматривая предков).

Brits 03.04.2024 00:31

Я хотел вернуться и оставить огромное спасибо. Я использовал то, что вы сказали, и поместил результаты на карту, а затем рекурсивно перестроил базовую строку. Вы можете проверить, что я сделал, если хотите, в репозитории GitHub, указанном в сообщении по адресу /routes/person и /models/person. Также имеется ссылка, поэтому, если кто-нибудь еще зайдет на этот пост, они смогут проверить, как я собрал его в окончательном варианте приложения.

Sai 05.04.2024 05:07

Другие вопросы по теме