Как вы сортируете дерево, сохраненное с использованием модели вложенного множества? - PullRequest
16 голосов
/ 14 октября 2008

Когда я имею в виду модель с вложенным множеством, я имею в виду то, что здесь описано .

Мне нужно построить новую систему для хранения «категорий» (я не могу придумать лучшего слова для этого) в определенной пользователем иерархии. Поскольку модель вложенного набора оптимизирована для чтения вместо записи, я решил использовать это. К сожалению, во время моего исследования и тестирования вложенных множеств я столкнулся с проблемой отображения иерархического дерева с отсортированными узлами. Например, если у меня есть иерархия:

root
    finances
        budgeting
            fy08
    projects
        research
        fabrication
        release
    trash

Я хочу, чтобы это было отсортировано так, чтобы оно отображалось как:

root
    finances
        budgeting
            fy08
    projects
        fabrication
        release
        research
    trash

Обратите внимание, что изготовление появляется до исследования.

В любом случае, после долгих поисков я увидел ответ, такой как «сохранить дерево в многомерном массиве и отсортировать его» и «преобразовать дерево и сериализовать его обратно в модель вложенного множества» (я перефразирую ... ). В любом случае, первое решение - это ужасная трата ОЗУ и ЦП, которые оба являются очень ограниченными ресурсами ... Второе решение выглядит просто как много болезненного кода.

Несмотря на это, я смог выяснить, как (используя модель вложенного множества):

  1. Создать новое дерево в SQL
  2. Вставить узел в качестве дочернего элемента другого узла в дереве
  3. Вставить узел после узла-брата в дереве
  4. Извлечь все дерево со структурой иерархии из SQL
  5. Извлечение поддерева из определенного узла (включая корневой) в иерархии с или без ограничения глубины
  6. Найдите родителя любого узла в дереве

Итак, я подумал, что № 5 и № 6 можно использовать для сортировки, которую я хотел, а также для восстановления дерева в отсортированном порядке.

Однако теперь, когда я посмотрел на все эти вещи, которые я научился делать, я вижу, что # 3, # 5 и # 6 могут использоваться вместе для выполнения сортированных вставок. Если я делал сортированные вставки, то они всегда сортируются. Однако, если я когда-либо изменю критерии сортировки или мне понадобится другой порядок сортировки, я вернусь к исходной точке.

Может ли это быть ограничением модели вложенного множества? Запрещает ли его использование при сортировке результатов запроса?

Ответы [ 8 ]

4 голосов
/ 20 января 2009

Я часто использовал Nested Sets и часто сталкивался с одной и той же проблемой. Что я делаю, и что я бы порекомендовал, так это просто не сортировать элементы в базе данных. Вместо этого сортируйте их в пользовательском интерфейсе. После того, как вы извлекли все узлы из БД, вам, скорее всего, придется преобразовать их в некоторую иерархическую структуру данных. В этой структуре отсортируйте все массивы, содержащие дочерние узлы.

Например, если ваш веб-интерфейс является приложением Flex, а дочерние элементы узла хранятся в ICollectionView, вы можете использовать свойство sort, чтобы они отображались так, как вам нужно.

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

Конечно, это работает, только если вам не нужны фактические записи в БД, но не так ли?

4 голосов
/ 15 октября 2008

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

Я думаю, что это лучший способ сохранить дерево отсортированным при вставке, обновлении или удалении узлов. Это даже делает запросы очень быстрыми, что является одной из основных целей этой структуры данных. Если вы реализуете хранимые процедуры для всех операций, их очень просто использовать.

Вы также можете изменить порядок сортировки предварительно отсортированного дерева. Вам просто нужно использовать ORDER BY node.rgt DESC вместо ORDER BY node.lft ASC.

Если вам действительно нужно поддерживать другой критерий сортировки, вы можете реализовать его, добавив второй индекс lft и rgt к каждому узлу и сохраняя его отсортированным по другим критериям при каждой вставке / обновлении / удалении.

2 голосов
/ 19 января 2009

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

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

Сортировка может быть изменена в операторе INSERT INTO @t, здесь это простая буквенно-цифровая сортировка по «Имени»

Это может быть плохой способ сделать это, особенно с использованием курсора для кода на основе множеств, но, как я уже сказал, это работает для меня, надеюсь, это поможет.

UPDATE:

Код ниже теперь показывает версию без использования cusor. Я вижу примерно 10-кратное улучшение скорости

CREATE VIEW dbo.tree_view

AS

SELECT t2.NodeID,t2.lft,t2.rgt ,t2.Name, COUNT(t1.NodeID) AS level  
FROM dbo.tree t1,dbo.tree t2
WHERE t2.lft BETWEEN t1.lft AND t1.rgt
GROUP BY t2.NodeID,t2.lft,t2.rgt,t2.Name

GO

----------------------------------------------

  DECLARE @CurrentNodeID int
DECLARE @CurrentActualOrder int
DECLARE @CurrentRequiredOrder int
DECLARE @DestinationNodeID int
DECLARE @i0 int
DECLARE @i1 int
DECLARE @i2 int
DECLARE @i3 int

DECLARE @t TABLE (TopLft int,NodeID int NOT NULL,lft int NOT NULL,rgt int NOT NULL,Name varchar(50),RequiredOrder int NOT NULL,ActualOrder int NOT NULL)


INSERT INTO @t (toplft,NodeID,lft,rgt,Name,RequiredOrder,ActualOrder)
    SELECT tv2.lft,tv1.NodeID,tv1.lft,tv1.rgt,tv1.Name,ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.ColumnToSort),ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.lft ASC)
    FROM dbo.tree_view tv1 
    LEFT OUTER JOIN dbo.tree_view tv2 ON tv1.lft > tv2.lft and tv1.lft < tv2.rgt and tv1.level = tv2.level+1
    WHERE tv2.rgt > tv2.lft+1

    DELETE FROM @t where ActualOrder = RequiredOrder


WHILE EXISTS(SELECT * FROM @t WHERE ActualOrder <> RequiredOrder)
BEGIN


    SELECT Top 1 @CurrentNodeID = NodeID,@CurrentActualOrder = ActualOrder,@CurrentRequiredOrder = RequiredOrder
    FROM @t 
    WHERE ActualOrder <> RequiredOrder
    ORDER BY toplft,requiredorder

    SELECT @DestinationNodeID = NodeID
    FROM @t WHERE ActualOrder = @CurrentRequiredOrder AND TopLft = (SELECT TopLft FROM @t WHERE NodeID = @CurrentNodeID) 

    SELECT @i0 = CASE WHEN c.lft < d.lft THEN c.lft ELSE d.lft END,
            @i1 =  CASE WHEN c.lft < d.lft THEN c.rgt ELSE d.rgt END,
            @i2 =  CASE WHEN c.lft < d.lft THEN d.lft ELSE c.lft END,
            @i3 =  CASE WHEN c.lft < d.lft THEN d.rgt ELSE c.rgt END
    FROM dbo.tree c
    CROSS JOIN dbo.tree d
    WHERE c.NodeID = @CurrentNodeID AND d.NodeID = @DestinationNodeID

    UPDATE dbo.tree
    SET lft = CASE  WHEN lft BETWEEN @i0 AND @i1 THEN @i3 + lft - @i1
                    WHEN lft BETWEEN @i2 AND @i3 THEN @i0 + lft - @i2
            ELSE @i0 + @i3 + lft - @i1 - @i2
            END,
        rgt = CASE  WHEN rgt BETWEEN @i0 AND @i1 THEN @i3 + rgt - @i1
                    WHEN rgt BETWEEN @i2 AND @i3 THEN @i0 + rgt - @i2
            ELSE @i0 + @i3 + rgt - @i1 - @i2
            END
    WHERE lft BETWEEN @i0 AND @i3 
    AND @i0 < @i1
    AND @i1 < @i2
    AND @i2 < @i3

    UPDATE @t SET actualorder = @CurrentRequiredOrder where NodeID = @CurrentNodeID
    UPDATE @t SET actualorder = @CurrentActualOrder where NodeID = @DestinationNodeID

    DELETE FROM @t where ActualOrder = RequiredOrder

END
1 голос
/ 15 октября 2008

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

Мой предпочтительный подход для вставок и перемещений во вложенном наборе заключается в обработке затронутой ветви, как в модели смежности: получить список новых братьев и сестер; найти правильное место в списке для нового узла; и создайте требуемые операторы обновления (это тот бит, в котором вы действительно должны быть осторожны). Что касается изменения ваших критериев упорядочения: это одноразовое задание, поэтому вы можете позволить себе израсходовать на нем некоторое количество ОЗУ и ЦП, наиболее гибкий ответ - разбить представление вложенного набора на представление смежности и перестроить вложенный набор из смежность на основе новых критериев.

0 голосов
/ 08 декабря 2013

Смотрите мое простое решение из метода моего класса. $ this-> table-> order - это код платформы Nette для получения данных из БД.

$tree = Array();
$parents = Array();
$nodes = $this->table->order('depth ASC, parent_id ASC, name ASC');
$i = 0;
$depth = 0;
$parent_id = 0;

foreach($nodes as $node) {
    if($depth < $node->depth || $parent_id < $node->parent_id) {
        $i = $parents["{$node->parent_id}"] + 1;
    }
    $tree[$i] = $node;
    $parents["{$node->id}"] = $i;
    $depth = $node->depth;
    $parent_id = $node->parent_id;
    $i += (($node->rgt - $node->lft - 1) / 2) + 1;
}
ksort($tree);
0 голосов
/ 20 января 2011

Сортировка вложенных наборов не имеет ограничений и это не сложно. Просто отсортируйте по ЛЕВОЙ беседке (якорь, что угодно), и все готово. Если у вас есть УРОВЕНЬ для каждого узла, вы также можете выбрать правильный отступ на основе уровня.

0 голосов
/ 10 ноября 2010

Вы можете сортировать их при рендеринге. Я объяснил рендеринг здесь Как отобразить все записи из вложенного набора в реальное HTML-дерево

0 голосов
/ 04 февраля 2010

Я считаю, что в вашем случае, когда узлы, которые вы хотите поменять местами, не имеют потомков, вы можете просто поменять значения lft и rgt. Посмотрите на это дерево:

   A
 /   \
B     C
     / \
    D   E

Это может превратиться в эту группу вложенных наборов:

1 A 10 
2 B 3  
4 C 9
5 D 6
7 E 8

Теперь рассмотрим, что вы хотите поменять местами D и E. Следующие вложенные наборы действительны, а D и E поменялись местами:

1 A 10
2 B 3 
4 C 9 
7 D 8
5 E 6 

Замена узлов, у которых есть поддеревья, конечно, не может быть выполнена таким образом, потому что вам также необходимо обновить значения lft и rgt дочерних элементов.

...