Representing Trees in PostgreSQL
woss.name
woss.name
http://www.amazon.com/Hierarchies-Smarties-Edition-Kaufmann-...
He spends a chapter on each of the models outlined in this post: adjacency, path, and nested set models.
I worked on a multi-tenant application with distinct trees present in one table and with one tree per table and so on. Fun fun fun!
But I've never had to use it, so I am just guessing.
Same article, different site: https://communities.bmc.com/docs/DOC-9902
And a paper: http://www.sigmod.org/publications/sigmod-record/0506/p47-ar...
Here's a comparison of the different approaches in a matrix: http://vadimtropashko.wordpress.com/2008/08/09/one-more-nest...
I experimented with a variation by Vadim Tropashko using something called "Farey fractions" [1]. These represent the intervals as a 2x2 matrix of four integers rather than two floating point values.
The numbers are effectively limited to 32-bit values in the matrix since a multiplication is required (resulting in 64-bit intermediate results).
It was very interesting, but couldn't model a file system hierarchy well. It could roughly 10^32 items in the best case, but a very small number (hundreds) in edge cases.
For example, it maxes out at a depth of 17 with 100 items at each level, or a depth of 34 with 10 items each. This might be fine for modeling some hierarchies, but definitely not a file system. The edge case is if the fraction extends along one edge. So if we have 10 items per level, and add a child at the leftmost edge each time, we create the most costly fractional subdivision. Do this 35 times and you hit an math overflow.
[1] Check out the Chapter 5 and Errata links: http://vadimtropashko.wordpress.com/“sql-design-patterns”-bo...
If you're using Rails (3.2 through 4.1), try this: http://mceachen.github.io/closure_tree/
Like it says in the README:
* Fetch your whole ancestor lineage in 1 SELECT. * Grab all your descendants in 1 SELECT. * Get all your siblings in 1 SELECT. * Fetch all descendants as a nested hash in 1 SELECT. * Find a node by ancestry path in 1 SELECT. * 2 SQL INSERTs on node creation * 3 SQL INSERT/UPDATEs on node reparenting
None of the other approaches above have even remotely similar performance characteristics. If your tree is small (tens of nodes), you won't care. If it's bigger, you will.
Adjacency lists also don't perform that badly with recursive queries in my experience.
The project I'm on used materialized paths, which lead to great pain. I investigated nested intervals ... and they could't achieve the tree depths we needed (we were modeling a file system tree).
We are back to adjacency lists (using a parent ID) but redesigned to avoid the need for recursive ancestor and descendant queries.
_But_ the RDBMS doesn't support recursive queries and I've been curious about PostgreSQL's recursive query support. I played with it, but not on a fully loaded database with deep trees of data.
Does PostgreSQL recursive query support work well with deep trees (> 100 levels) on tables with tens of millions or more rows?
Second, the materialized path's length exceeded the database's indexable length limit. (MySQL, the DB in question, has a default index limit of 767 bytes, so only first 767 bytes are indexed.)
There are ways around these issues (like using "UPDATE ... WHERE" rather than using an ORM to walk the tree and update... sigh). We also had other app-specific / design-specific issues too that swamped these issues performance wise.
I'm hopefully we won't need ancestor and descendant queries again in our re-design. But I'm keeping PostgreSQL with its Common Table Expression stuff--the thing that facilities recursive queries--in my back pocket. It's that or use a stored procedure to build the capability by hand.
http://illuminatedcomputing.com/posts/2014/09/postgres-cte-f...
The problem I was tackling was making the tree sorted like you see in threaded, scored comments.
[1] https://coderwall.com/p/lixing/closure-tables-for-browsing-t...
http://stackoverflow.com/questions/192220/what-is-the-most-e...
http://www.slideshare.net/billkarwin/models-for-hierarchical...
http://karwin.blogspot.in/2010/03/rendering-trees-with-closu...
I believe Disqus uses this, as well: http://cramer.io/2010/05/30/scaling-threaded-comments-on-dja...
Of course, then we had two problems on our hands. :)
That being said, I've tried all these methods before across a few different DBMS.
IMO a good way to go about this all is to actually just reimplement a file system; You have a caching virtual file system in your application, and store data as key:parentKey:name (equivalent-ish dentry:parentDentry:fileName) in every table which contains child nodes. It's fast, often more predictable, and definitely more portable (as you aren't relying on DMBS-specific constructs). It's also amenable to partitioning/sharding by parent key. You can drastically reduce the amount of queries that are being sent. Also, if you use a b+tree as an index for your paths, you can invalidate cache/subtrees pretty fast in your application.
Of course, you end up duplicating functionality of the database, and if there is a lot of latency between you and the DB, this might not be the best method (or it might, depending).
It would be nice if MySQL finally supported CTEs.
1
1.1
1.2
2
2.1
2.1.1
2.1.2
2.1.2.1
In this way, displaying comments in a tree form is trivial. Just ORDER BY the sort key. I find it brilliant for applying to such an application.
1
1.1
1.2
10
2
2.1
To support that, you will have to be careful when picking collation order, and binary collation will not do it. It may be better to use fixed-width numbers with leading zeroes instead, but that, of course, may unnecessarily grow the space used by the table.And of course, that also is what RCS and SCCS use for numbering revisions.
1
11
12
1.0
2
21
Then you just ensure that '.' sorts after all digits. (Probably by using a different symbol.) Basically, the '.' means "insert after."Compared to a mysql with nested set postgres using with recursive is a life changer :D
Namely using the && operator let's you make use of indices on the materialized paths. We've been using it in production for years to great effect.
Implement a view helper to take the flat data and return a hierarchical structure that you can render.
My most common query is to find whether user X is a child of user Y.
My solution was to use application-level triggers to maintain a separate lookup table, so i can simply do `user_id IN (SELECT child_id FROM lookup_table WHERE parent_id = Y)` as an additional search clause. With appropriate indexes it's very fast to query, and i can do partial updates to maintain the lookup table.
If my most common tree query was something else (e.g. enumerate children in sorted order) then i'd need some other data structure.
in models.py:
class Node(models.Model):
parent = models.ForeignKey('self', related_name='children')
in the view: nodes = Node.objects.prefetch_related('children')
in template: {% for node in nodes %}
{% for subnode in node.children.all %}
...
{% endfor %}
{% endfor %}
This will make exactly two queries; one for all the parent nodes, and one for the children nodes. Django will make the pairing automagically, so you don't have to do it in view code.However, there is of course a django package to help with this and gathers all the nodes you care about into one query:
http://blog.jupo.org/2010/01/26/linear-traversal-of-adjacenc...
No need for mptt either.
Neo4j is a great database, and I use it myself for a project that involves lots of deep hierarchies. But it has a fairly sparse feature-set where schema enforcement and data integrity checking are concerned; you basically have to add all that stuff yourself at the application level which can amount to a lot of work.
Line from the page: "I just picked up a copy and it looks great! You are right about the whole approach and my stuff stinks." - Joe Celko, author of SQL for Smarties.
...and the author just wrote-off the best performing generic tree solution, because "it was too complex to wrap my head around!"
:facepalm: