Como você classifica uma árvore armazenada usando o modelo de conjunto aninhado?

When I refer to nested set model I mean what is described here.

Eu preciso construir um novo sistema para armazenar "categorias" (não consigo pensar em palavra melhor para ele) em uma hierarquia definida pelo usuário. Como o modelo de conjunto aninhado é otimizado para leituras em vez de gravações, decidi usá-lo. Infelizmente durante a minha pesquisa e teste de conjuntos aninhados, me deparei com o problema de como exibir a árvore hierárquica com nós classificados. Por exemplo, se eu tiver a hierarquia:

root
    finances
        budgeting
            fy08
    projects
        research
        fabrication
        release
    trash

Eu quero que isso seja classificado para que seja exibido como:

root
    finances
        budgeting
            fy08
    projects
        fabrication
        release
        research
    trash

Observe que a fabricação aparece antes da pesquisa.

De qualquer forma, depois de uma longa pesquisa, vi respostas como "armazenar a árvore em uma matriz multidimensional e classificá-la" e "recorrer à árvore e serializá-la novamente em seu modelo de conjunto aninhado" (estou parafraseando ...). De qualquer forma, a primeira solução é um desperdício horrível de RAM e CPU, ambos recursos muito finos ... A segunda solução parece um monte de código doloroso.

Independentemente disso, eu fui capaz de descobrir como (usando o modelo de conjunto aninhado):

  1. Iniciar uma nova árvore no SQL
  2. Insira um nó como filho de outro nó na árvore
  3. Insira um nó depois de um nó irmão na árvore
  4. Puxar a árvore inteira com a estrutura de hierarquia do SQL
  5. Puxar uma subárvore de um nó específico (incluindo raiz) na hierarquia com ou sem um limite de profundidade
  6. Encontre o pai de qualquer nó na árvore

Então imaginei que # 5 e # 6 poderiam ser usados ​​para fazer a classificação que eu queria, e também poderia ser usado para reconstruir a árvore em ordem de classificação também.

No entanto, agora que observei todas essas coisas que aprendi, vejo que # 3, # 5 e # 6 poderiam ser usados ​​juntos para executar inserções classificadas. Se eu fiz inserções classificadas, ele sempre será classificado. No entanto, se eu mudar os critérios de classificação ou quiser uma ordem de classificação diferente, estou de volta à estaca zero.

Isso poderia ser apenas a limitação do modelo de conjunto aninhado? Seu uso inibe na classificação de consulta da saída?

17

8 Respostas

Eu usei muito o Nested Sets e enfrentei o mesmo problema com frequência. O que eu faço e o que eu recomendaria é simplesmente não classificar os itens no banco de dados. Em vez disso, classifique-os na interface do usuário. Depois que você puxou todos os nós do banco de dados, provavelmente terá que convertê-los em alguma estrutura hierárquica de dados, de qualquer forma. Nessa estrutura, classifique todos os arrays que contêm os filhos do nó.

Por exemplo, se seu frontend for um aplicativo Flex e os filhos de um nó estiverem armazenados em um ICollectionView, você poderá usar a propriedade de classificação para que eles sejam exibidos da maneira desejada.

Outro exemplo, se o seu frontend é alguma saída de um script PHP, você poderia ter os filhos de cada nó em um array e usar as funções de ordenação de array do PHP para realizar sua ordenação.

Claro, isso só funciona se você não precisar que as entradas reais do banco de dados sejam classificadas, mas você precisa?

4
adicionado

Eu acho que isso é realmente uma limitação do modelo de conjunto aninhado. Você não pode classificar facilmente os nós filho dentro de seu respectivo nó pai, porque a ordenação do conjunto de resultados é essencial para reconstruir a estrutura da árvore.

Eu acho que é provavelmente a melhor abordagem para manter a árvore classificada ao inserir, atualizar ou excluir nós. Isso faz com que as consultas sejam muito rápidas, o que é um dos principais objetivos dessa estrutura de dados. Se você implementar procedimentos armazenados para todas as operações, é muito fácil de usar.

Você também pode inverter a ordem de classificação de uma árvore pré-classificada. Você só precisa usar ORDER BY node.rgt DESC em vez de ORDER BY node.lft ASC .

Se você realmente precisa dar suporte a outro critério de classificação, você poderia implementá-lo adicionando um segundo índice lft e rgt a cada nó e mantê-lo classificado pelos outros critérios em cada insert/update/delete.

4
adicionado

Acabei de terminar de escrever o seguinte, o que funciona para mim na classificação de uma árvore inteira de conjuntos aninhados.

The sort (ideally) requires a view that lists the current level of each node in the tree and a procedure for swapping two nodes - both are included below, the sibling swap code comes from Joe Celkos ' Tree & Hierarchies' book which I strongly recommend to anyone using nested sets.

A classificação pode ser alterada na instrução 'INSERT INTO @t', aqui é uma simples ordem alfanumérica em 'Nome'

Esta pode ser uma maneira ruim de fazê-lo, especialmente usando o cursor para o código baseado em conjunto, mas como eu digo que funciona para mim, espero que ajude.

ATUALIZAÇÃO:

Código abaixo agora mostra a versão sem usar o cusor. Eu vejo melhorias na velocidade de 10x

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
2
adicionado
Incrível, isso é exatamente o que eu tenho procurado. Isso resolveu completamente o problema de classificação que eu estava tendo com nossa hierarquia de conjuntos aninhados.
adicionado o autor Hamman359, fonte

Sim, é uma limitação do modelo de conjunto aninhado, uma vez que os conjuntos aninhados são uma representação pré-ordenada de uma hierarquia. Esta pré-encomenda é a razão pela qual é tão rápida para as leituras. O modelo de adjacência, também descrito na página à qual você está vinculado, fornece a filtragem e a filtragem mais flexíveis, mas com um impacto significativo no desempenho.

Minha abordagem preferencial para inserções e movimentações em um conjunto aninhado é manipular o ramo afetado como no modelo de adjacência: Obter uma lista dos novos irmãos; encontre o lugar certo na lista para o novo nó; e construa as instruções de atualização necessárias (sendo essa a parte em que você realmente precisa ser cuidadoso). Quanto a mudar seus critérios de pedido: É um trabalho em lote único, então você pode gastar um pouco de RAM e CPU, a resposta mais flexível seria dividir a representação do conjunto aninhado em uma representação de adjacência e reconstruir o conjunto aninhado a adjacência baseada em novos critérios.

1
adicionado

Ordenar conjuntos aninhados não tem limites e não é difícil. Basta classificar pelo bower esquerdo (âncora, seja o que for) e pronto. Se você tiver um NÍVEL para cada nó, também é possível extrair o recuo correto com base no Nível.

1
adicionado
Esse é o ponto real que estou tentando fazer (e vou pegar o -1 para fazer isso ;-). Mesmo a boa solução de Justin ainda usa um loop While que ainda é um cursor sem a palavra CURSOR nele. A chave para tudo isso é inicialmente criar os conjuntos aninhados na ordem correta. Eu poderia postar um par de links sobre como fazer isso corretamente e com velocidade suficiente que você poderia facilmente fazê-lo em qualquer alteração, mas eu provavelmente só seria criticado por apenas postar uma URL em vez de código como eu já uma vez. ;-)
adicionado o autor Jeff Moden, fonte

Acredito que, no seu caso, onde os nós que você deseja trocar não possuem descendentes, você pode simplesmente trocar os valores de lft e rgt por todos os lados. Considere esta árvore:

   A
/  \
B     C
    /\
    D   E

Isso pode se transformar nesse grupo de conjuntos aninhados:

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

Agora considere que você quer trocar D e E. Os seguintes conjuntos aninhados são válidos e D e E são trocados:

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

Trocar nós que possuem subárvores não pode ser feito dessa forma, é claro, porque você precisaria atualizar os valores lft e rgt das crianças também.

0
adicionado

You can sort thier when you render. I explained rendering here How to render all records from a nested set into a real html tree

0
adicionado

See my simple solution from method of my class. $this->table->order is Nette framework code to get data from DB.

$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
adicionado