Quelle est la façon la plus efficace / élégante de couper une table plate en un arbre?

supposons que vous ayez une table plate qui stocke une hiérarchie d'arbre ordonnée:

Id   Name         ParentId   Order
 1   'Node 1'            0      10
 2   'Node 1.1'          1      10
 3   'Node 2'            0      20
 4   'Node 1.1.1'        2      10
 5   'Node 2.1'          3      10
 6   'Node 1.2'          1      20

voici un diagramme, où nous avons [id] Name . Le nœud racine 0 est fictif.

                       [0] ROOT
                          /     
              [1] Node 1          [3] Node 2
              /                          
    [2] Node 1.1     [6] Node 1.2      [5] Node 2.1
          /          
 [4] Node 1.1.1

quelle approche minimaliste utiliseriez-vous pour produire cela en HTML (ou en texte, d'ailleurs) comme un arbre correctement ordonné, correctement indenté?

supposons en outre que vous avez seulement des structures de données de base( tableaux et hachmaps), aucune Fantaisie objets avec références parents / enfants, pas D'ORM, pas de cadre, juste vos deux mains. La table est représentée sous la forme d'un jeu de résultats auquel on peut accéder au hasard.

Pseudo code ou simple anglais est acceptable, c'est une question purement conceptuelle.

question Bonus: y a-t-il une meilleure façon de stocker une structure arborescente comme celle-ci dans un RDBMS?


MODIFICATIONS ET AJOUTS

pour répondre à la question d'un commentateur ( Mark Bessey 's): un noeud racine n'est pas nécessaire, parce qu'il ne sera jamais affiché de toute façon. ParentId = 0 est la convention pour exprimer "ce sont de haut niveau". La colonne Ordre définit comment les noeuds avec le même parent seront triés.

le "jeu de résultats" dont j'ai parlé peut être décrit comme un tableau de hashmaps (pour rester dans cette terminologie). Pour mon exemple était censé être déjà y. Certaines réponses vont plus loin et construisent d'abord, mais c'est pas grave.

l'arbre peut être arbitrairement profond. Chaque noeud peut avoir N enfants. Je n'avais pas exactement un arbre "des millions d'entrées" à l'esprit, cependant.

ne confondez pas mon choix de nom de noeud ('noeud 1.1.1') avec quelque chose sur lequel compter. Les noeuds peuvent tout aussi bien être appelés 'Frank' ou 'Bob', aucune structure de nommage n'est implicite, ceci était simplement pour le rendre lisible.

j'ai posté ma propre solution pour que vous puissiez la mettre en pièces.

471
demandé sur Community 2008-10-10 20:47:43

14 réponses

maintenant que MySQL 8.0 est sur le point de sortir, toutes les bases de données SQL populaires supporteront les requêtes récursives en syntaxe standard.

WITH RECURSIVE MyTree AS (
    SELECT * FROM MyTable WHERE ParentId IS NULL
    UNION ALL
    SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;

j'ai testé des requêtes récursives dans MySQL 8.0 dans ma présentation Recursive Query Throwdown en 2017.

ci-dessous est ma réponse originale de 2008:


les données dans une base de données relationnelle. Ce que vous montrez dans votre exemple utilise deux méthodes:

  • liste de contiguïté (colonne "parent") et
  • chemin D'énumération (les numéros en pointillés dans la colonne de votre nom).

une autre solution est appelée ensembles imbriqués , et il peut être stocké dans la même table aussi. Lire arbres et Hiérarchies en SQL pour Smarties " par Joe Celko pour beaucoup plus d'informations sur ces conceptions.

je préfère habituellement un modèle appelé table de fermeture (alias" relation de contiguïté") pour stocker des données structurées par arbre. Il nécessite une autre table, mais alors interroger les arbres est assez facile.

je couvre la table de fermeture dans ma présentation modèles pour les données hiérarchiques avec SQL et PHP et dans mon livre SQL Antipatterns: Éviter les Pièges de la Programmation de Base de données .

CREATE TABLE ClosureTable (
  ancestor_id   INT NOT NULL REFERENCES FlatTable(id),
  descendant_id INT NOT NULL REFERENCES FlatTable(id),
  PRIMARY KEY (ancestor_id, descendant_id)
);

stocker tous les chemins dans la table de fermeture, où il ya une ascendance directe d'un nœud à un autre. Inclure une ligne pour chaque nœud de référence lui-même. Par exemple, en utilisant l'ensemble de données que vous avez montré dans votre question:

INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
  (1,1), (1,2), (1,4), (1,6),
  (2,2), (2,4),
  (3,3), (3,5),
  (4,4),
  (5,5),
  (6,6);

Maintenant vous pouvez obtenir un arbre commençant au noeud 1 comme ceci:

SELECT f.* 
FROM FlatTable f 
  JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;

la sortie (dans le client MySQL) ressemble à ce qui suit:

+----+
| id |
+----+
|  1 | 
|  2 | 
|  4 | 
|  6 | 
+----+

en d'autres termes, les noeuds 3 et 5 sont exclus, parce qu'ils font partie d'une hiérarchie séparée, ne descendant pas du noeud 1.


Re: commentaires de e-satis sur les enfants immédiats (ou le parent immédiat). Vous pouvez ajouter une colonne" path_length " à la colonne ClosureTable pour faciliter la recherche spécifiquement pour un enfant immédiat ou un parent (ou toute autre distance).

INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
  (1,1,0), (1,2,1), (1,4,2), (1,6,1),
  (2,2,0), (2,4,1),
  (3,3,0), (3,5,1),
  (4,4,0),
  (5,5,0),
  (6,6,0);

, Alors vous pouvez ajouter un terme de votre recherche pour interroger les enfants immédiats d'un nœud donné. Ce sont des descendants dont le path_length est 1.

SELECT f.* 
FROM FlatTable f 
  JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
  AND path_length = 1;

+----+
| id |
+----+
|  2 | 
|  6 | 
+----+

Re comment from @ashraf: "Que Diriez-vous de trier l'arbre entier [par son nom]?"

voici un exemple de requête pour retourner tous les noeuds qui sont des descendants du noeud 1, les joindre au FlatTable qui contient d'autres attributs de noeud comme name , et Trier par le nom.

SELECT f.name
FROM FlatTable f 
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;

Re commentaire de @Nate:

SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f 
JOIN ClosureTable a ON (f.id = a.descendant_id) 
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id) 
WHERE a.ancestor_id = 1 
GROUP BY a.descendant_id 
ORDER BY f.name

+------------+-------------+
| name       | breadcrumbs |
+------------+-------------+
| Node 1     | 1           |
| Node 1.1   | 1,2         |
| Node 1.1.1 | 1,2,4       |
| Node 1.2   | 1,6         |
+------------+-------------+

un utilisateur a suggéré une édition aujourd'hui. Donc les modérateurs ont approuvé l'édition, mais je l'inverse.

l'édition a suggéré que l'ORDRE PAR dans la dernière requête ci-dessus devrait être ORDER BY b.path_length, f.name , probablement pour s'assurer que l'ordre correspond à la hiérarchie. Mais ce n'est pas fonctionne, parce qu'il commanderait "noeud 1.1.1" après "noeud 1.2".

si vous voulez que l'ordre corresponde à la hiérarchie d'une manière raisonnable, c'est possible, mais pas simplement en ordonnant par la longueur du chemin. Par exemple, voir ma réponse à MySQL Fermeture de la Table de base de données hiérarchique - Comment extraire des informations dans le bon ordre .

407
répondu Bill Karwin 2018-07-03 12:44:13

si vous utilisez des ensembles imbriqués (parfois appelé Arbre de traversée modifié avant l'ordre), vous pouvez extraire la structure entière de l'arbre ou n'importe quel sous-arbre à l'intérieur de celle-ci dans l'ordre de l'arbre avec une seule requête, au coût d'inserts étant plus coûteux, comme vous avez besoin de gérer des colonnes qui décrivent un chemin dans l'ordre à travers la structure de l'arbre.

pour django-mptt , j'ai utilisé une structure comme celle-ci:

id  parent_id  tree_id  level  lft  rght
--  ---------  -------  -----  ---  ----
 1       null        1      0    1    14
 2          1        1      1    2     7
 3          2        1      2    3     4
 4          2        1      2    5     6
 5          1        1      1    8    13
 6          5        1      2    9    10
 7          5        1      2    11   12

qui décrit un arbre qui ressemble à ceci (avec id représentant chaque élément):

 1
 +-- 2
 |   +-- 3
 |   +-- 4
 |
 +-- 5
     +-- 6
     +-- 7

ou, comme un diagramme de série imbriqué qui rend plus évidente la façon dont les valeurs lft et rght fonctionnent:

 __________________________________________________________________________
|  Root 1                                                                  |
|   ________________________________    ________________________________   |
|  |  Child 1.1                     |  |  Child 1.2                     |  |
|  |   ___________    ___________   |  |   ___________    ___________   |  |
|  |  |  C 1.1.1  |  |  C 1.1.2  |  |  |  |  C 1.2.1  |  |  C 1.2.2  |  |  |
1  2  3___________4  5___________6  7  8  9___________10 11__________12 13 14
|  |________________________________|  |________________________________|  |
|__________________________________________________________________________|

comme vous pouvez le voir, pour obtenir toute la sous-arborescence pour un noeud donné, dans l'ordre de l'arbre, vous devez simplement sélectionner toutes les lignes qui ont des valeurs lft et rght entre ses valeurs lft et rght . C'est aussi simple pour récupérer l'arbre des ancêtres pour un noeud donné.

la colonne level est un peu de dénormalisation pour la commodité plus que tout et la colonne tree_id vous permet de redémarrer la numérotation lft et rght pour chaque noeud de premier niveau, ce qui réduit le nombre de colonnes affectées par inserts, mouvements et suppressions, car les colonnes lft et rght doivent être ajustées en conséquence lorsque ces opérations ont lieu dans afin de créer ou de combler les lacunes. J'ai fait quelques notes de développement au moment où j'essayais d'enrouler ma tête autour des requêtes nécessaires pour chaque opération.

en termes de travail réel avec ces données pour afficher un arbre, j'ai créé une fonction utilitaire tree_item_iterator qui, pour chaque noeud, devrait vous donner suffisamment d'informations pour générer n'importe quel type d'affichage que vous voulez.

plus d'information à propos de MPTT:

53
répondu Jonny Buchanan 2011-11-15 15:00:10

depuis Oracle 9i, vous pouvez utiliser CONNECT BY.

SELECT LPAD(' ', (LEVEL - 1) * 4) || "Name" AS "Name"
FROM (SELECT * FROM TMP_NODE ORDER BY "Order")
CONNECT BY PRIOR "Id" = "ParentId"
START WITH "Id" IN (SELECT "Id" FROM TMP_NODE WHERE "ParentId" = 0)

depuis SQL Server 2005, vous pouvez utiliser une expression de table commune récursive (CTE).

WITH [NodeList] (
  [Id]
  , [ParentId]
  , [Level]
  , [Order]
) AS (
  SELECT [Node].[Id]
    , [Node].[ParentId]
    , 0 AS [Level]
    , CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
  FROM [Node]
  WHERE [Node].[ParentId] = 0
  UNION ALL
  SELECT [Node].[Id]
    , [Node].[ParentId]
    , [NodeList].[Level] + 1 AS [Level]
    , [NodeList].[Order] + '|'
      + CONVERT([varchar](MAX), [Node].[Order]) AS [Order]
  FROM [Node]
    INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[ParentId]
) SELECT REPLICATE(' ', [NodeList].[Level] * 4) + [Node].[Name] AS [Name]
FROM [Node]
  INNER JOIN [NodeList] ON [NodeList].[Id] = [Node].[Id]
ORDER BY [NodeList].[Order]

tous deux produiront les résultats suivants.

Name
'Node 1'
'    Node 1.1'
'        Node 1.1.1'
'    Node 1.2'
'Node 2'
'    Node 2.1'
18
répondu Eric Weilnau 2011-11-15 15:00:43

c'est une question assez ancienne, mais comme il a de nombreux points de vue, je pense qu'il vaut la peine de présenter une alternative, et à mon avis très élégante, solution.

pour lire une structure arborescente, vous pouvez utiliser (CTEs). Il donne la possibilité de récupérer la structure entière de l'arbre à la fois, avoir les informations sur le niveau du noeud, son noeud parent et l'ordre dans les enfants du noeud parent.

Permettez-moi de vous montrer comment cela fonctionnerait dans PostgreSQL 9.1.

  1. créer une structure

    CREATE TABLE tree (
        id int  NOT NULL,
        name varchar(32)  NOT NULL,
        parent_id int  NULL,
        node_order int  NOT NULL,
        CONSTRAINT tree_pk PRIMARY KEY (id),
        CONSTRAINT tree_tree_fk FOREIGN KEY (parent_id) 
          REFERENCES tree (id) NOT DEFERRABLE
    );
    
    
    insert into tree values
      (0, 'ROOT', NULL, 0),
      (1, 'Node 1', 0, 10),
      (2, 'Node 1.1', 1, 10),
      (3, 'Node 2', 0, 20),
      (4, 'Node 1.1.1', 2, 10),
      (5, 'Node 2.1', 3, 10),
      (6, 'Node 1.2', 1, 20);
    
  2. Écrire une requête

    WITH RECURSIVE 
    tree_search (id, name, level, parent_id, node_order) AS (
      SELECT 
        id, 
        name,
        0,
        parent_id, 
        1 
      FROM tree
      WHERE parent_id is NULL
    
      UNION ALL 
      SELECT 
        t.id, 
        t.name,
        ts.level + 1, 
        ts.id, 
        t.node_order 
      FROM tree t, tree_search ts 
      WHERE t.parent_id = ts.id 
    ) 
    SELECT * FROM tree_search 
    WHERE level > 0 
    ORDER BY level, parent_id, node_order;
    

    Voici les résultats:

     id |    name    | level | parent_id | node_order 
    ----+------------+-------+-----------+------------
      1 | Node 1     |     1 |         0 |         10
      3 | Node 2     |     1 |         0 |         20
      2 | Node 1.1   |     2 |         1 |         10
      6 | Node 1.2   |     2 |         1 |         20
      5 | Node 2.1   |     2 |         3 |         10
      4 | Node 1.1.1 |     3 |         2 |         10
    (6 rows)
    

    les noeuds d'arbre sont ordonnés par un niveau de profondeur. Dans le résultat final nous les présentons dans les lignes suivantes.

    pour chaque niveau, ils sont ordonnés par parent_id et node_order dans le parent. Ceci nous indique comment les présenter dans le noeud output - link au parent dans cet ordre.

    ayant une telle structure, il ne serait pas difficile de faire une très belle présentation en HTML.

    CTEs récursifs sont disponibles en PostgreSQL, IBM DB2, MS SQL Server et Oracle .

    si vous souhaitez en savoir plus sur les requêtes SQL récursives, vous pouvez soit documentation de vos SGBD préférés ou lire mes deux articles couvrant ce sujet:

17
répondu Michał Kołodziejski 2014-03-13 11:19:21

la réponse de Bill est sacrément bonne, cette réponse y ajoute des choses qui me font souhaiter des réponses filetées si soutenues.

de toute façon je voulais soutenir la structure de l'arbre et la propriété de L'ordre. J'ai inclus une propriété unique dans chaque noeud appelé leftSibling qui fait la même chose Order est destiné à faire dans la question originale (maintenir l'ordre de gauche à droite).

mysql> desc nodes ;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| name        | varchar(255) | YES  |     | NULL    |                |
| leftSibling | int(11)      | NO   |     | 0       |                |
+-------------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

mysql> desc adjacencies;
+------------+---------+------+-----+---------+----------------+
| Field      | Type    | Null | Key | Default | Extra          |
+------------+---------+------+-----+---------+----------------+
| relationId | int(11) | NO   | PRI | NULL    | auto_increment |
| parent     | int(11) | NO   |     | NULL    |                |
| child      | int(11) | NO   |     | NULL    |                |
| pathLen    | int(11) | NO   |     | NULL    |                |
+------------+---------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

plus de détails et de code SQL sur mon blog .

Merci Bill votre réponse a été utile pour commencer!

9
répondu bobobobo 2012-06-28 13:27:13

si j'avais le choix, j'utiliserais des objets. Je créerais un objet pour chaque enregistrement où chaque objet a une collection children et les stocke tous dans un tableau assoc (/hashtable) où L'Id est la clé. Et blitz à travers la collection une fois, en ajoutant les enfants aux champs enfants pertinents. Simple.

mais parce que vous n'êtes pas amusant en limitant l'utilisation d'une bonne OOP, je voudrais probablement itérer basé sur:

function PrintLine(int pID, int level)
    foreach record where ParentID == pID
        print level*tabs + record-data
        PrintLine(record.ID, level + 1)

PrintLine(0, 0)

Edit: c'est similaire à quelques autres entrées, mais je pense que c'est légèrement plus propre. Une chose que j'ajouterai: c'est extrêmement intensif en SQL. C'est nasty . si vous avez le choix, suivez la route OOP.

7
répondu Oli 2008-10-10 17:36:21

cela a été écrit rapidement, et n'est ni jolie ni efficace (plus il autoboxes alot, conversion entre int et Integer est ennuyeux!), mais il fonctionne.

cela enfreint probablement les règles puisque je crée mes propres objets mais hey je fais cela comme une diversion du vrai travail :)

cela suppose également que le jeu de résultats / table est complètement lu dans une sorte de structure avant de commencer à construire des noeuds, qui ne seraient pas le la meilleure solution si vous avez des centaines de milliers de lignes.

public class Node {

    private Node parent = null;

    private List<Node> children;

    private String name;

    private int id = -1;

    public Node(Node parent, int id, String name) {
        this.parent = parent;
        this.children = new ArrayList<Node>();
        this.name = name;
        this.id = id;
    }

    public int getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public void addChild(Node child) {
        children.add(child);
    }

    public List<Node> getChildren() {
        return children;
    }

    public boolean isRoot() {
        return (this.parent == null);
    }

    @Override
    public String toString() {
        return "id=" + id + ", name=" + name + ", parent=" + parent;
    }
}

public class NodeBuilder {

    public static Node build(List<Map<String, String>> input) {

        // maps id of a node to it's Node object
        Map<Integer, Node> nodeMap = new HashMap<Integer, Node>();

        // maps id of a node to the id of it's parent
        Map<Integer, Integer> childParentMap = new HashMap<Integer, Integer>();

        // create special 'root' Node with id=0
        Node root = new Node(null, 0, "root");
        nodeMap.put(root.getId(), root);

        // iterate thru the input
        for (Map<String, String> map : input) {

            // expect each Map to have keys for "id", "name", "parent" ... a
            // real implementation would read from a SQL object or resultset
            int id = Integer.parseInt(map.get("id"));
            String name = map.get("name");
            int parent = Integer.parseInt(map.get("parent"));

            Node node = new Node(null, id, name);
            nodeMap.put(id, node);

            childParentMap.put(id, parent);
        }

        // now that each Node is created, setup the child-parent relationships
        for (Map.Entry<Integer, Integer> entry : childParentMap.entrySet()) {
            int nodeId = entry.getKey();
            int parentId = entry.getValue();

            Node child = nodeMap.get(nodeId);
            Node parent = nodeMap.get(parentId);
            parent.addChild(child);
        }

        return root;
    }
}

public class NodePrinter {

    static void printRootNode(Node root) {
        printNodes(root, 0);
    }

    static void printNodes(Node node, int indentLevel) {

        printNode(node, indentLevel);
        // recurse
        for (Node child : node.getChildren()) {
            printNodes(child, indentLevel + 1);
        }
    }

    static void printNode(Node node, int indentLevel) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < indentLevel; i++) {
            sb.append("\t");
        }
        sb.append(node);

        System.out.println(sb.toString());
    }

    public static void main(String[] args) {

        // setup dummy data
        List<Map<String, String>> resultSet = new ArrayList<Map<String, String>>();
        resultSet.add(newMap("1", "Node 1", "0"));
        resultSet.add(newMap("2", "Node 1.1", "1"));
        resultSet.add(newMap("3", "Node 2", "0"));
        resultSet.add(newMap("4", "Node 1.1.1", "2"));
        resultSet.add(newMap("5", "Node 2.1", "3"));
        resultSet.add(newMap("6", "Node 1.2", "1"));

        Node root = NodeBuilder.build(resultSet);
        printRootNode(root);

    }

    //convenience method for creating our dummy data
    private static Map<String, String> newMap(String id, String name, String parentId) {
        Map<String, String> row = new HashMap<String, String>();
        row.put("id", id);
        row.put("name", name);
        row.put("parent", parentId);
        return row;
    }
}
5
répondu matt b 2008-10-10 18:25:29

en supposant que vous savez que les éléments racine sont zéro, voici le pseudocode pour sortir au texte:

function PrintLevel (int curr, int level)
    //print the indents
    for (i=1; i<=level; i++)
        print a tab
    print curr \n;
    for each child in the table with a parent of curr
        PrintLevel (child, level+1)


for each elementID where the parentid is zero
    PrintLevel(elementID, 0)
3
répondu wcm 2008-10-10 16:59:37

vous pouvez émuler n'importe quelle autre structure de données avec un hashmap, donc ce n'est pas une limitation terrible. En scannant de haut en bas, vous créez un hashmap pour chaque ligne de la base de données, avec une entrée pour chaque colonne. Ajoutez chacun de ces hashmaps à un hashmap "master", saisi sur l'id. Si un noeud a un "parent" que vous n'avez pas encore vu, créez une entrée placeholder pour lui dans le HashMap maître, et remplissez le quand vous voyez le noeud réel.

pour l'imprimer, faire une profondeur simple-d'abord passer à travers les données, en gardant la trace du niveau d'indentation le long du chemin. Vous pouvez rendre cela plus facile en gardant une entrée "enfants" pour chaque ligne, et en la peuplant pendant que vous scannez les données.

quant à savoir s'il y a une "meilleure" façon de stocker un arbre dans une base de données, Cela dépend de la façon dont vous allez utiliser les données. J'ai vu des systèmes qui avaient une profondeur maximale connue et qui utilisaient une table différente pour chaque niveau de la hiérarchie. Qui fait beaucoup de sens si les niveaux dans l'arbre n'est pas tout à fait équivalent après tout (catégories de niveau supérieur étant différent que les feuilles).

3
répondu Mark Bessey 2008-10-10 17:24:34

il y a vraiment de bonnes solutions qui exploitent la représentation btree interne des indices sql. Ceci est basé sur de grandes recherches effectuées aux alentours de 1998.

Voici un exemple de tableau (mysql).

CREATE TABLE `node` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `tw` int(10) unsigned NOT NULL,
  `pa` int(10) unsigned DEFAULT NULL,
  `sz` int(10) unsigned DEFAULT NULL,
  `nc` int(11) GENERATED ALWAYS AS (tw+sz) STORED,
  PRIMARY KEY (`id`),
  KEY `node_tw_index` (`tw`),
  KEY `node_pa_index` (`pa`),
  KEY `node_nc_index` (`nc`),
  CONSTRAINT `node_pa_fk` FOREIGN KEY (`pa`) REFERENCES `node` (`tw`) ON DELETE CASCADE
)

Les seuls champs nécessaires à la représentation de l'arbre sont:

  • tw: l'indice de pré-ordre DFS de gauche à droite, où root = 1.
  • pa: la référence (en utilisant tw) à le noeud parent, root a nul.
  • sz: la taille de la branche du noeud y compris lui-même.
  • nc: est utilisé comme sucre syntaxique. il s'agit de tw+nc et représente la tw du "prochain enfant"du noeud.

voici un exemple de population de noeuds, ordonnée par tw:

+-----+---------+----+------+------+------+
| id  | name    | tw | pa   | sz   | nc   |
+-----+---------+----+------+------+------+
|   1 | Root    |  1 | NULL |   24 |   25 |
|   2 | A       |  2 |    1 |   14 |   16 |
|   3 | AA      |  3 |    2 |    1 |    4 |
|   4 | AB      |  4 |    2 |    7 |   11 |
|   5 | ABA     |  5 |    4 |    1 |    6 |
|   6 | ABB     |  6 |    4 |    3 |    9 |
|   7 | ABBA    |  7 |    6 |    1 |    8 |
|   8 | ABBB    |  8 |    6 |    1 |    9 |
|   9 | ABC     |  9 |    4 |    2 |   11 |
|  10 | ABCD    | 10 |    9 |    1 |   11 |
|  11 | AC      | 11 |    2 |    4 |   15 |
|  12 | ACA     | 12 |   11 |    2 |   14 |
|  13 | ACAA    | 13 |   12 |    1 |   14 |
|  14 | ACB     | 14 |   11 |    1 |   15 |
|  15 | AD      | 15 |    2 |    1 |   16 |
|  16 | B       | 16 |    1 |    1 |   17 |
|  17 | C       | 17 |    1 |    6 |   23 |
| 359 | C0      | 18 |   17 |    5 |   23 |
| 360 | C1      | 19 |   18 |    4 |   23 |
| 361 | C2(res) | 20 |   19 |    3 |   23 |
| 362 | C3      | 21 |   20 |    2 |   23 |
| 363 | C4      | 22 |   21 |    1 |   23 |
|  18 | D       | 23 |    1 |    1 |   24 |
|  19 | E       | 24 |    1 |    1 |   25 |
+-----+---------+----+------+------+------+

chaque résultat d'arbre peut être fait de façon non-récursive. Par exemple, pour obtenir une liste des ancêtres de noeud à tw= '22'

ancêtres

select anc.* from node me,node anc 
where me.tw=22 and anc.nc >= me.tw and anc.tw <= me.tw 
order by anc.tw;
+-----+---------+----+------+------+------+
| id  | name    | tw | pa   | sz   | nc   |
+-----+---------+----+------+------+------+
|   1 | Root    |  1 | NULL |   24 |   25 |
|  17 | C       | 17 |    1 |    6 |   23 |
| 359 | C0      | 18 |   17 |    5 |   23 |
| 360 | C1      | 19 |   18 |    4 |   23 |
| 361 | C2(res) | 20 |   19 |    3 |   23 |
| 362 | C3      | 21 |   20 |    2 |   23 |
| 363 | C4      | 22 |   21 |    1 |   23 |
+-----+---------+----+------+------+------+

les frères et sœurs et les enfants sont triviaux - il suffit d'utiliser pa field ordering par tw.

Descendants

par exemple l'ensemble (branche) de noeuds qui sont enracinés à tw = 17.

select des.* from node me,node des 
where me.tw=17 and des.tw < me.nc and des.tw >= me.tw 
order by des.tw;
+-----+---------+----+------+------+------+
| id  | name    | tw | pa   | sz   | nc   |
+-----+---------+----+------+------+------+
|  17 | C       | 17 |    1 |    6 |   23 |
| 359 | C0      | 18 |   17 |    5 |   23 |
| 360 | C1      | 19 |   18 |    4 |   23 |
| 361 | C2(res) | 20 |   19 |    3 |   23 |
| 362 | C3      | 21 |   20 |    2 |   23 |
| 363 | C4      | 22 |   21 |    1 |   23 |
+-----+---------+----+------+------+------+

Notes Complémentaires

cette méthodologie est extrêmement utile lorsqu'il y a une plus de lectures qu'il n'y a d'encarts ou de mises à jour.

Parce que l'insertion, le mouvement, ou la mise à jour d'un nœud dans l'arbre nécessite l'arbre pour être ajustée, il est nécessaire de verrouiller la table avant de commencer l'action.

le coût d'insertion/suppression est élevé car l'indice tw et les valeurs sz (taille de la branche) devront être mis à jour sur tous les noeuds après le point d'insertion, et pour tous les ancêtres respectivement.

déplacement de branche implique le déplacement de la valeur tw de la branche hors de portée, il est donc également nécessaire de désactiver les contraintes de clé étrangère lors du déplacement d'une branche. Il existe essentiellement quatre requêtes nécessaires pour déplacer une branche:

  • Déplacer la branche hors de portée.
  • fermer la brèche qu'il a laissé. (l'arbre restant est maintenant normalisé).
  • ouvrir la brèche où elle ira.
  • déplacer le dans sa nouvelle position.

Ajuster Arbre De Requêtes

l'ouverture/fermeture des trous dans l'arbre est une sous-fonction importante utilisée par les méthodes create/update / delete, donc je l'inclus ici.

nous avons besoin de deux paramètres - un drapeau représentant si oui ou non nous réduisons ou augmentons, et l'index tw du noeud. Ainsi, par exemple tw=18 (qui a une taille de branche de 5). Supposons que nous sommes réduction des effectifs (suppression de tw) - cela signifie que nous utilisons '-' au lieu de '+' dans les mises à jour de l'exemple suivant.

nous utilisons d'abord une fonction d'ancêtre (légèrement modifiée) pour mettre à jour la valeur sz.

update node me, node anc set anc.sz = anc.sz - me.sz from 
node me, node anc where me.tw=18 
and ((anc.nc >= me.tw and anc.tw < me.pa) or (anc.tw=me.pa));

Ensuite, nous devons ajuster la tw, pour ceux dont la tw est supérieure à la branche pour être supprimé.

update node me, node anc set anc.tw = anc.tw - me.sz from 
node me, node anc where me.tw=18 and anc.tw >= me.tw;

ensuite, nous devons ajuster le parent pour ceux dont la tw du pa est plus élevée que la branche à supprimer.

update node me, node anc set anc.pa = anc.pa - me.sz from 
node me, node anc where me.tw=18 and anc.pa >= me.tw;
3
répondu Konchog 2017-03-14 08:43:51

si des cartes de hachage imbriquées ou des tableaux peuvent être créés, alors je peux simplement descendre la table du début et ajouter chaque élément au tableau imbriqué. Je dois tracer chaque ligne jusqu'au noeud racine afin de savoir dans quel niveau dans le tableau imbriqué insérer. Je peux employer la mémoïsation pour que je n'aie pas besoin de chercher le même parent encore et encore.

Edit: je voudrais lire la table entière dans un tableau d'abord, de sorte qu'il ne sera pas interroger la base de données à plusieurs reprises. Bien sûr, ce ne sera pas être pratique si votre table est très importante.

après la construction de la structure, je dois faire un tour en profondeur et imprimer le HTML.

il n'y a pas de meilleure façon fondamentale de stocker cette information en utilisant une seule table (je pourrais me tromper ;), et j'aimerais voir une meilleure solution ). Cependant, si vous créez un schéma pour employer des tables de db créées dynamiquement, alors vous avez ouvert un tout nouveau monde au sacrifice de la simplicité, et le risque of SQL hell ;).

1
répondu tchen 2008-10-10 20:16:34

si les éléments sont dans l'ordre de l'arbre, comme indiqué dans votre exemple, vous pouvez utiliser quelque chose comme L'exemple Python suivant:

delimiter = '.'
stack = []
for item in items:
  while stack and not item.startswith(stack[-1]+delimiter):
    print "</div>"
    stack.pop()
  print "<div>"
  print item
  stack.append(item)

ce n'est maintenir une pile représentant la position courante dans l'arbre. Pour chaque élément du tableau, il Pop éléments de pile (fermeture des divs correspondants) jusqu'à ce qu'il trouve le parent de l'élément courant. Puis il sort le début de ce noeud et le pousse vers la pile.

si vous voulez sortir l'arbre utilisant l'indentation plutôt que des éléments imbriqués, vous pouvez simplement sauter les instructions d'impression pour imprimer les divs, et imprimer un nombre d'espaces égal à un certain multiple de la taille de la pile avant chaque élément. Par exemple, en Python:

print "  " * len(stack)

, Vous pouvez facilement utiliser cette méthode pour construire un ensemble de listes imbriquées ou les dictionnaires.

Edit: je vois de votre clarification que les noms n'étaient pas destinés à être des chemins de noeuds. Qui suggère une autre approche:

idx = {}
idx[0] = []
for node in results:
  child_list = []
  idx[node.Id] = child_list
  idx[node.ParentId].append((node, child_list))

cela construit un arbre de tableaux de tuples(!). idx[0] représente la racine(s) de l'arbre. Chaque élément d'un tableau est un 2-tuple composé du nœud lui-même et une liste de tous ses enfants. Une fois construit, vous pouvez garder idx[0] et rejeter idx, à moins que vous ne vouliez accéder aux noeuds par leur ID.

1
répondu Nick Johnson 2008-10-14 12:05:26

pour étendre la solution SQL de Bill, vous pouvez faire la même chose en utilisant un tableau plat. En outre, si vos chaînes ont toutes la même longueur et que votre nombre maximum d'enfants est connu (par exemple dans un arbre binaire), vous pouvez le faire en utilisant une seule chaîne (tableau de caractères). Si vous avez un nombre arbitraire d'enfants, cela complique un peu les choses... Je dois vérifier mes anciennes notes, pour voir ce qui peut être fait.

alors, sacrifiant un peu de mémoire, surtout si votre arbre est clairsemé et / ou déséquilibré, vous pouvez, avec un peu d'index math, accéder à toutes les chaînes au hasard en stockant votre arbre, largeur d'abord dans le tableau comme so (pour un arbre binaire):

String[] nodeArray = [L0root, L1child1, L1child2, L2Child1, L2Child2, L2Child3, L2Child4] ...

vous connaissez la longueur de votre corde, vous le savez

je suis au travail maintenant, donc je ne peux pas y passer beaucoup de temps, mais avec intérêt, je peux récupérer un peu de code pour le faire.

nous l'employons pour le faire à la recherche dans les arbres binaires faits de codons D'ADN, un processus construit le arbre, puis nous l'avons aplati pour rechercher des modèles de texte et une fois trouvé, bien que l'index math (des verveux d'en haut) nous récupérons le noeud... très rapide et efficace, tough notre arbre avait rarement des noeuds vides, mais nous pouvions chercher des gigabytes de données en un clin d'œil.

1
répondu Newtopian 2012-10-02 12:13:21

pensez à utiliser des outils nosql comme neo4j pour les structures hiérarchiques. E. g une application en réseau comme linkedin utilise couchbase (une autre solution nosql)

mais n'utiliser nosql que pour les requêtes au niveau data-mart et ne pas stocker /maintenir les transactions

0
répondu sreenivasulu kandakuru 2012-11-26 18:29:12