Pull to refresh

Comments 9

А можно просто использовать схему наподобие Nested Sets (те что с lkey rkey level) и не переизобретать ничего :)

Можно, конечно. Но я решал конкретную проблему на той структуре, что уже была на проекте. Менять её не планировалось, т.к. работало всё сносно. Если уж на то пошло, то тягать One-to-Many связь через сущность -- вообще такое себе. Лучше было бы вообще сырой запрос через Doctrine DBAL сделать и всё. Но опять же, подход зависит от ситуации.

Спасибо. Всегда приятно читать о таких практических кейсах. Вот прямо очень не хватает на Хабре именно такого рода статей.


решение проблемы в данной статье рассчитано именно на использование adjacency list

Мне кажется, вы здесь не совсем верно атрибутировали решение, поскольку оно полностью построено на работе с uid, то есть решение фактически рассчитано на использование materialized path, а с чистым списком смежности работать, разумеется, не будет.


У вас, кстати, баг в запросе. Вы не экранируете подчеркивание. То есть, запрос LIKE 222_% найдет как детей категории 222, так и детей всех категорий от 2220 до 2229. Хотя если у вас фиксированная длина uid, то это, конечно, не страшно. Но в этом случае тогда его надо или совсем убрать, или лучше оставить, но экранировать. Причем не знаю, как в постгресе, а в мускуле надо экранировать дважды. То есть в запросе надо получить строку видаuid\\_%


Ну и совсем мелочь. Неужели sprintf('%s_%%', $uid)) читается проще, чем "{$uid}_%"? Вот мне в первом случае физически приходится продираться через эти нолики, чтобы понять кто есть ху.

Спасибо. Всегда приятно читать о таких практических кейсах. Вот прямо очень не хватает на Хабре именно такого рода статей.

Благодарю за отзыв :)

Мне кажется, вы здесь не совсем верно атрибутировали решение, поскольку
оно полностью построено на работе с uid, то есть решение фактически
рассчитано на использование materialized path, а с чистым списком
смежности работать, разумеется, не будет.

Судя по всему, я зря усложнил сущность, добавив в неё UID. Это сбивает с толку. Там в тестах выбирается коллекция дочерних сущностей через Category::children(), materialized path при этом не задействуется, только связь One-to-Many. А метод findDescendantsByUid намеренно вытаскивает всех потомков подряд (со всех уровней), чтобы было проще потом Category::children() тестировать.

У вас, кстати, баг в запросе. Вы не экранируете подчеркивание. То есть, запрос LIKE 222_%
найдет как детей категории 222, так и детей всех категорий от 2220 до
2229. Хотя если у вас фиксированная длина uid, то это, конечно, не
страшно.

Честно говоря, не понял, откуда возьмётся 2220-2229.

Ну и совсем мелочь. Неужели sprintf читается лучше, чем интерполяция строк? Вот мне в первом случае физически приходится продираться через эти нолики, чтобы понять кто есть ху.

Действительно. Что-то я к этому sprintf() так привык уже, что использую его везде :)

P.S. Извинияюсь за неточную цитату: Хабр некорректно обрабатывает код, заключенный в тег code, внутри цитат. Я честно пытался.

Так, давайте разбираться.


Честно говоря, не понял, откуда возьмётся 2220-2229.

Здесь все просто. Как говорил старик Тровальдссон, 5 строк кода стоят тысячи слов, и я с ним полностью согласен! (И снова поблагодарим rozhnev за такой замечательный сервис!)


По поводу же механизма, я, честно говоря, в непонятках.


Судя по всему, я зря усложнил сущность, добавив в неё UID…

Вот тут я перестал вообще что-либо понимать.
Можете показать сам SQL запрос, который решает поставленную задачу — выбрать всех детишек одним запросом — нo при этом без использования UID?

Здесь все просто. Как говорил старик Тровальдссон, 5 строк кода стоят тысячи слов, и я с ним полностью согласен! (И снова поблагодарим @rozhnev за такой замечательный сервис!)

Теперь дошло! На проекте с такой проблемой не сталкивался.

Вот тут я перестал вообще что-либо понимать.

Можете показать сам SQL запрос, который решает поставленную задачу —
выбрать всех детишек одним запросом — нo при этом без использования UID?

Метод Category::findDescendantsByUid() из статьи как раз вытягивает всех потомков за один запрос. Но проблема в том, что если мы у любого из них будем вызывать метод связи children(), то Doctrine будет каждый раз делать запрос в БД:

$descendants = $repo->findDescendantsByUid('foo');

// без фикса тут будет +2 запроса в БД
$descendants[0]->children()->first()->children()->first();

Фикс заключается в ручной сборке вложенных коллекций таким образом, чтобы Doctrine не пыталась загружать их заново каждый раз.

На проекте с такой проблемой не сталкивался.

Вы, все-таки, посмотрите повнимательнее. Видимо, сейчас у вас все категории идут с одной разрядностью. Но если в какой-то момент добавится разряд, то проблемы начнутся в полный рост.


чтобы Doctrine не пыталась загружать их заново каждый раз.

Да, это логично. Меня просто смутили ваши слова о ненужности UID, хотя как раз на это поле-то и завязан весь механизм получения детей одним запросом.


Плюс, как я писал выше, из текста статьи складывается впечатление, что задача заключалась в той самой оптимизации. Но только в комментариях выясняется, что сама по себе эта оптимизация у вас уже была, но Доктрина её не видела. И задача заключалась не в том, чтобы сделать оптимизацию, а в том, чтобы подружить её с Доктриной.


В итоге сама оптимизация отодвигается на второй план, что, на мой взгляд, и незаслуженно, и делает статью менее информативной.

with recursive cat_tree as (
    select id, name from categories where id = 1
    union all
    select categories.id, categories.name from categories 
    join cat_tree on cat_tree.id = categories.parent_id
) select * from cat_tree;

рекурсивный запрос который выберет всех детишек

https://sqlize.online/sql/psql15/bbf907629c612304c0a34b1526b6b525/

Вместо 1 подставить нужную цифру

Да, я подобными запросами вытаскивал полное название категории:

-- all ancestors
select string_agg(name, ' • ') from (
    with recursive
        ancestor (id, uid, name) as
            (
                select c.parent_category_id, c.uid, c.name
                from category c
                -- или какое-то другое условие
                where c.uid = 'some_child_uid'
                union all
                select a.parent_category_id, a.uid, a.name
                from ancestor, category c
                where a.category_id = ancestor.id
            )
    select name from ancestor
    order by uid
) as tbl;

-- all descendants
select string_agg(name, ' • ') from (
    with recursive
        descedant (id, uid, name) as
            (
                select c.category_id, c.uid, c.name
                from category c
                -- или какое-то другое условие
                where c.uid = 'some_uid'
                union all
                select c.category_id, c.uid, c.name
                from descedant, category c
                where c.parent_category_id = descedant.id
            )
    select name from descedant
    order by uid
) as tbl;

Sign up to leave a comment.

Articles