Est-il possible d'interroger une table de structure d'arbre dans MySQL en une seule requête, à n'importe quelle profondeur?
je pense que la réponse est non, mais j'en serais ravie. n'importe qui a une idée sur la façon de ramper une structure d'arbre à n'importe quelle profondeur en SQL (MySQL), mais avec une seule requête
plus spécifiquement, étant donné une table arborescente structurée (id, data, data, parent_id), et une rangée dans la table, est-il possible d'obtenir tous les descendants (enfant / petit-enfant / etc), ou d'ailleurs tous les ancêtres (parent/grand-parent/etc) sans savoir jusqu'où il sera allez, à l'aide d'une seule requête?
ou utilise-t-on une sorte de récursion, où je continue à chercher plus profondément jusqu'à ce qu'il n'y ait pas de nouveaux résultats?
spécifiquement, J'utilise Ruby et Rails, mais je devine que ce n'est pas très pertinent.
9 réponses
Oui, c'est possible, c'est un pré-commande modifié de traversée de L'arbre, comme le décrit le mieux ici
Joe Celko des Arbres et des Hiérarchies dans SQL pour les Smarties
un exemple de travail (en PHP) est fourni ici
http://www.sitepoint.com/article/hierarchical-data-database/2 /
voici plusieurs ressources:
- http://forums.mysql.com/read.php?10,32818, 32818#msg-32818
- la Gestion Hiérarchique des Données dans MySQL
- http://lists.mysql.com/mysql/201896
fondamentalement, vous aurez besoin de faire une sorte de curseur dans une procédure stockée ou une requête ou construire une table de contiguïté. J'avais évitez la récursion en dehors de la base de données: selon la profondeur de votre arbre, cela pourrait devenir vraiment lent/vague.
la réponse de Daniel Beardsley n'est pas si mauvaise que ça quand les principales questions que vous posez sont 'qu'est-ce que sont tous mes enfants' et 'qu'est-ce que sont tous mes parents'.
en réponse à Alex Weinstein, cette méthode entraîne en fait moins de mises à jour des noeuds sur un mouvement parent que dans la technique Celko. Dans la technique de Celko, si un noeud de niveau 2 à l'extrême gauche passe sous un noeud de niveau 1 à l'extrême droite, alors à peu près tous les noeuds de l'arbre doivent être mis à jour., plutôt que les enfants du noeud.
ce que je dirais, cependant, C'est que Daniel stocke peut-être le chemin de retour à root dans le mauvais sens.
je les stockerais pour que la requête soit
SELECT FROM table WHERE ancestors LIKE "1,2,6%"
cela signifie que mysql peut faire usage d'un index sur la colonne 'ancêtres', qu'il ne serait pas en mesure de faire avec un %dirigeant.
je suis tombé sur ce problème avant et j'ai eu une idée folle. Vous pouvez stocker un champ dans chaque enregistrement qui est une chaîne concaténée de ses ancêtres directs jusqu'à la racine.
Imaginez que vous aviez des documents comme celui-ci (indentation implique l'hérarchie et les nombres sont des id, ancêtres.
- 1, "1"
- 2, "2,1"
- 5, "5,2,1"
- 6, " 6,2,1"
- 7, "7,6,2,1"
- 11, "11,6,2,1"
- 3, " 3,1"
- 8, "8,3,1"
- 9, "9,3,1"
- 10, "10,3,1"
- 2, "2,1"
puis à sélectionnez les descendants de id: 6 , faites juste ceci
SELECT FROM table WHERE ancestors LIKE "%6,2,1"
garder la colonne des ancêtres à jour pourrait être plus difficile que ce qu'elle vaut pour vous, mais c'est une solution réalisable dans n'importe quel DB.
la technique de Celko (ensembles imbriqués) est assez bonne. J'ai aussi utilisé une table de contiguïté avec champs "ancêtre" et "descendant" et "distance" (par exemple, enfants/parents directs ont une distance de 1, petits-enfants/grands-parents ont une distance de 2, etc).
cela doit être maintenu, mais est assez facile à faire pour les inserts: vous utilisez une transaction, puis mettez le lien direct (parent, enfant, distance=1) dans la table, puis insérez ignorer une sélection d'existant parents&enfants en ajoutant des distances (je peux tirer vers le haut le SQL lorsque j'ai une chance), qui se veut un index sur chacune des 3 champs pour la performance. Si cette approche devient laid est pour les suppressions... vous devez essentiellement marquer tous les éléments qui ont été touchés et puis les reconstruire. Mais un avantage de ceci est qu'il peut manipuler des graphes acycliques arbitraires, alors que le modèle d'ensemble imbriqué peut seulement faire des hiérarchies droites (par exemple chaque élément sauf que la racine a un et un seul parent).
SQL n'est pas un langage complet Turing, ce qui signifie que vous n'allez pas être en mesure d'effectuer ce genre de boucle. Vous pouvez faire des choses très intelligentes avec des structures de SQL et d'arbre, mais je ne peux pas penser à une façon de décrire une rangée qui a un certain id "dans sa hiérarchie" pour une hiérarchie de profondeur arbitraire.
votre meilleur pari est quelque chose du genre de ce que @Dan a suggéré, qui est de simplement travailler votre chemin à travers l'arbre dans un autre langage plus compétent. Vous peut en fait générer une chaîne de requête dans un langage universel en utilisant une boucle, où la requête est juste une série alambiquée de jointures (ou de sous-requêtes) qui reflète la profondeur de la hiérarchie que vous recherchez. Ce serait plus efficace que la boucle et les requêtes multiples.
cela peut certainement être fait et ce N'est pas si compliqué pour SQL. J'ai répondu à cette question et fourni un exemple de travail en utilisant le code de procédure mysql ici:
MySQL: comment trouver des feuilles dans un noeud spécifique
Booth: si vous êtes satisfait, vous devez noter l'une des réponses acceptées.
j'ai utilisé la routine "avec émulateur" décrite dans https://stackoverflow.com/questions/27013093/recursive-query-emulation-in-mysql (fourni par https://stackoverflow.com/users/1726419/yossico ). Jusqu'à présent, j'ai obtenu de très bons résultats (du point de vue de la performance), mais je n'ai pas une abondance de données ou un grand nombre de descendants à chercher à travers/pour.
vous allez presque certainement vouloir employer une certaine récursion pour cela. Et si vous faites cela, alors il serait trivial (en fait plus facile) d'obtenir l'arbre entier plutôt que des morceaux de celui-ci à une profondeur fixe.
en pseudo-code très rugueux vous voulez quelque chose du genre:
getChildren(parent){
children = query(SELECT * FROM table WHERE parent_id = parent.id)
return children
}
printTree(root){
print root
children = getChildren(root)
for child in children {
printTree(child)
}
}
bien que dans la pratique vous voudriez rarement faire quelque chose comme ça. Il sera plutôt inefficace puisqu'il fait une demande pour chaque rang dans la table, donc ce ne sera raisonnable que pour les petites tables, ou les arbres qui ne sont pas nichés trop profondément. Pour être honnête, dans les deux cas, vous voudrez probablement pour limiter la profondeur.
cependant, étant donné la popularité de ces types de structure de données, il peut très bien y avoir quelques trucs MySQL pour vous aider avec cela, spécifiquement pour réduire le nombre de requêtes que vous devez faire.
Edit: après y avoir pensé, il est très peu logique de faire tous ces requête. Si vous lisez la table entière de toute façon, alors vous pouvez simplement slurp le tout en RAM-en supposant qu'il est assez petit!