discourse-legacysite-perl/site/glist/templates/help/GT/SQL/Tree.html
2024-06-17 22:24:05 +10:00

687 lines
22 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>GT::SQL::Tree - Helps create and manage a tree in an SQL database.</title>
<link rev="made" href="mailto:root@penguin.office.gossamer-threads.com" />
<style type="text/css">
/* $MVD$:fontset("Untitled Font Set 1","ARIEL","HELVETICA","HELV","SANSERIF") */
/* $MVD$:fontset("Arial","Arial") */
/* $MVD$:fontset("Arial Black","Arial Black") */
/* $MVD$:fontset("Algerian","Algerian") */
body {
background-color: white;
font-family: Verdana, Arial, sans-serif;
font-size: small;
color: black;
}
p {
background-color : white;
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
h1 {
font-family : Verdana, Arial, sans-serif;
font-weight : bold;
font-size : medium;
background-color : white;
color : maroon;
}
h2 {
font-family : Verdana, Arial, sans-serif;
font-size : medium;
font-weight : bold;
color : blue;
background-color : white;
}
h3 {
font-family : Verdana, Arial, sans-serif;
font-weight : bold;
font-size : medium;
color : black;
background-color : white;
}
h4 {
font-family : Verdana, Arial, sans-serif;
font-weight : bold;
font-size : small;
color : maroon;
background-color : white;
}
h5 {
font-family : Verdana, Arial, sans-serif;
font-weight : bold;
font-size : small;
color : blue;
background-color : white;
}
h6 {
font-family : Verdana, Arial, sans-serif;
font-weight : bold;
font-size : small;
color : black;
background-color : white;
}
ul {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
ol {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
dl {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
li {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
th {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
td {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
dl {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
dd {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
dt {
font-family : Verdana, Arial, sans-serif;
font-size : small;
color : black;
}
code {
font-family : Courier;
font-size : small;
color : black;
}
pre {
font-family : Courier;
font-size : small;
color : black;
}
.mvd-H1 {
font-family : Verdana, Arial, sans-serif;
font-weight : bold;
font-size : 14.0pt;
background-color : transparent;
background-image : none;
color : maroon;
}
.mvd-H2 {
font-family : Verdana, Arial, sans-serif;
font-size : 12.0pt;
color : blue;
}
p.indent {
font-family : "Verdana, Arial, sans-serif";
list-style-type : circle;
list-style-position : inside;
color : black;
margin-left : 16.0pt;
}
.mvd-P-indent {
font-family : Verdana, Arial, sans-serif;
list-style-type : circle;
list-style-position : inside;
color : black;
margin-left : 16.0pt;
}
pre.programlisting {
font-size : 9.0pt;
list-style-type : disc;
margin-left : 16.0pt;
margin-top : -14.0pt;
}
.mvd-PRE-programlisting {
font-size : 9.0pt;
list-style-type : disc;
margin-left : 16.0pt;
margin-top : -14.0pt;
}
.mvd-PRE {
font-size : 9.0pt;
}
p.note {
margin-left : 28.0pt;
}
.mvd-P-note {
margin-left : 28.0pt;
}
.mvd-H4 {
font-family : Verdana, Arial, sans-serif;
font-weight : normal;
font-size : 9.0pt;
color : black;
margin-left : 6.0pt;
margin-top : -14.0pt;
}
.mvd-P {
font-family : Verdana, Arial, sans-serif;
font-size : 10.0pt;
color : black;
}
.mvd-BODY {
font-family : Verdana, Arial, sans-serif;
background-color : white;
}
p.indentnobullet {
font-family : Verdana, Arial, sans-serif;
list-style-type : none;
}
.mvd-P-indentnobullet {
font-family : Verdana, Arial, sans-serif;
list-style-type : none;
}
</style>
</head>
<body style="background-color: white">
<p><a name="__index__"></a></p>
<!-- INDEX BEGIN -->
<ul>
<li><a href="#name">NAME</a></li>
<li><a href="#synopsis">SYNOPSIS</a></li>
<li><a href="#description">DESCRIPTION</a></li>
<li><a href="#methods">METHODS</a></li>
<ul>
<li><a href="#new__tree">new, tree</a></li>
<li><a href="#create__add_tree">create, add_tree</a></li>
<li><a href="#destroy__drop_tree">destroy, drop_tree</a></li>
<li><a href="#root_id_col__father_id_co__depth_col">root_id_col, father_id_co, depth_col</a></li>
<li><a href="#children">children</a></li>
<li><a href="#parents">parents</a></li>
<li><a href="#child_ids">child_ids</a></li>
<li><a href="#parent_ids">parent_ids</a></li>
</ul>
<li><a href="#indices">INDICES</a></li>
<li><a href="#copyright">COPYRIGHT</a></li>
<li><a href="#version">VERSION</a></li>
</ul>
<!-- INDEX END -->
<hr />
<p>
</p>
<h1><a name="name">NAME</a></h1>
<p>GT::SQL::Tree - Helps create and manage a tree in an SQL database.</p>
<p>
</p>
<hr />
<h1><a name="synopsis">SYNOPSIS</a></h1>
<pre>
use GT::SQL::Tree;</pre>
<pre>
my $tree = $table-&gt;tree;
my $children = $tree-&gt;children(id =&gt; [1,2,3], max_depth =&gt; 2);</pre>
<pre>
my $parents = $tree-&gt;parents(id =&gt; [4,5,6]);</pre>
<p>
</p>
<hr />
<h1><a name="description">DESCRIPTION</a></h1>
<p>GT::SQL::Tree is designed to implement a tree structure with a SQL table. Most
of the work on managing the table is performed automatically behind the scenes,
however there are a couple of front end methods to retrieving the tree nodes
from a GT::SQL::Tree object.</p>
<p>
</p>
<hr />
<h1><a name="methods">METHODS</a></h1>
<p>
</p>
<h2><a name="new__tree">new, tree</a></h2>
<p>Typically, the way to get a tree object is to call -&gt;tree on a table object. The
table object then calls GT::SQL::Tree-&gt;new for you and returns the results,
which is a GT::SQL::Tree object. Typically you should not call -&gt;new directly,
but instead let $table-&gt;tree call it with the proper arguments.</p>
<p>
</p>
<h2><a name="create__add_tree">create, add_tree</a></h2>
<p>To use GT::SQL::Tree, you need to first call create(). You shouldn't call it
directly, but instead call -&gt;<code>add_tree()</code> on an editor object. The arguments to
add_tree are passed through to create, so that they are essentially the same
(there is one exception - add_tree passed in <code>table =&gt; $table_object</code>).</p>
<p><code>create()</code> will create a tree table, with the name passed on the name of the table
passed in. For example, if you wish to build a tree on 'MyTable', the tree table
that is created by <code>create()</code> will be named MyTable_tree. The tree table provides
easy one-query access to all of a nodes parents or children, and also keeps
track of the number of hops between a node and its descendant, allowing you to
limit how far you descend into the tree.</p>
<p>The following arguments are required:</p>
<dl>
<dt><strong><a name="item_table">table</a></strong><br />
</dt>
<dd>
This contains the table object for the table the tree is to be built upon. Note
that when calling add_tree you <strong>should not</strong> specify this - add_tree passes it
along on its own.
</dd>
<p></p>
<dt><strong><a name="item_father">father</a></strong><br />
</dt>
<dd>
This must specify the name of the father ID column. The father ID column
controls the relationship between father/child.
</dd>
<dd>
<p>For example, if your primary key is ``my_id'' and your father id column is
``my_father_id'', you would pass in ``my_father_id'' as the value to <a href="#item_father"><code>father</code></a>.</p>
</dd>
<p></p>
<dt><strong><a name="item_root">root</a></strong><br />
</dt>
<dd>
This is used to specify the name of the root column. For example, if your
primary key is ``my_id'' and your root id column is ``my_root_id'', you would pass
in ``my_root_id'' as the value to <a href="#item_root"><code>root</code></a>.
</dd>
<p></p>
<dt><strong><a name="item_depth">depth</a></strong><br />
</dt>
<dd>
This is used to specify the name of the depth column for the table. For example,
if you are using a column named ``my_depth'' to keep track of the depth of a node,
you would pass in ``my_depth'' as the value to <a href="#item_depth"><code>depth</code></a>.
</dd>
<p></p></dl>
<p>The following are optional arguments to create/add_tree:</p>
<dl>
<dt><strong><a name="item_force">force</a></strong><br />
</dt>
<dd>
Takes a value such as 'force' or 'check'. This value is passed on to the
GT::SQL table creation subroutine.
</dd>
<p></p>
<dt><strong><a name="item_rebuild">rebuild</a></strong><br />
</dt>
<dd>
You can pass in a GT::SQL::Tree::Rebuild object if you have an incomplete or
invalid table structure. See <a href="glist.cgi?do=admin_gtdoc&topic=/GT/SQL/Tree/Rebuild.html">the GT::SQL::Tree::Rebuild manpage</a> for more details.
</dd>
<p></p>
<dt><strong><a name="item_debug">debug</a></strong><br />
</dt>
<dd>
Sets the debug level of the tree object. <code>add_tree()</code> automatically passes in the
debug value for the table object, so it normally is not necessary to set this.
</dd>
<p></p></dl>
<p>
</p>
<h2><a name="destroy__drop_tree">destroy, drop_tree</a></h2>
<p>You can call <code>$tree-&gt;destroy</code> to destroy a tree. This involves dropping the
tree table and deleting the tree reference from the table the tree was on. This
can be called by calling <code>$tree-&gt;destroy()</code> on a GT::SQL::Tree object,
however this is typically invoked by calling <code>$editor-&gt;drop_tree()</code> on a
table editor object.</p>
<p>Neither <code>$tree-&gt;destroy()</code> nor <code>$editor-&gt;drop_tree()</code> take any
arguments.</p>
<p>
</p>
<h2><a name="root_id_col__father_id_co__depth_col">root_id_col, father_id_co, depth_col</a></h2>
<p>These three tree object methods return the name of the associated column in the
main table. Usually you will already know them, and these methods are primarily
used internally.</p>
<p>
</p>
<h2><a name="children">children</a></h2>
<p>This is where the usefulness of the tree module comes into play.
<code>$tree-&gt;children</code> is used to access all of the children of a particular
node. It takes a wide variety of arguments to control the return.</p>
<p>Usually, the return will be either a hash reference of array references each
containing hash references, or else an array reference of hash references. Which
reference you get depends on what you request via the <a href="#item_id"><code>id</code></a> parameter, described
below. Each inner hash reference is a row from the database, typically a joined
row from the table the tree is on with the tree table, however the
<a href="#item_roots_only"><code>roots_only</code></a>, <a href="#item_cols"><code>cols</code></a>, and <a href="#item_select_from"><code>select_from</code></a> parameters all change this behaviour.</p>
<p>The arguments to <code>children()</code> are as follows:</p>
<dl>
<dt><strong><a name="item_id">id</a></strong><br />
</dt>
<dd>
The value of the id key is either a scalar value, or an array reference. The
value/values to id should be the id whose descendants you are looking for. For
example, if you are looking for the children of ID 3 and ID 4, you would pass in
<code>id =&gt; [3, 4]</code>. The return value of children will be a hash reference
containing two keys: 3 and 4.
</dd>
<dd>
<p>If you are looking for the children of a single ID and pass the id as a scalar
value, you will get back an array reference as described above.</p>
</dd>
<dd>
<p>So, basically, if the value to id is an array reference, you will get back a
hash reference of array references of hash references; if it is a scalar value,
you will get back an array reference of hash references.
$tree-&gt;children(id =&gt; [1])-&gt;{1};
and
$tree-&gt;children(id =&gt; 1);
will result in the same thing.</p>
</dd>
<dd>
<p>To get all the trees in a single query, you pass in 0 as the value. This is as
if you are requesting the children of the imaginary root to which all roots
belong.</p>
</dd>
<dd>
<p><a href="#item_id"><code>id</code></a> is the only required parameter.</p>
</dd>
<p></p>
<dt><strong><a name="item_max_depth">max_depth</a></strong><br />
</dt>
<dd>
You can specify a max_depth value to specify that the records returned should
not be more a certain distance from the node. For example, supposing you have
this tree:
a
b
c
d
Selecting the children of a with a max_depth of 1 would return just b, not c or
d. A max_depth of 2 would return b and c.
</dd>
<dd>
<p>Not specifying max_depth means that you do not want to limit the maximum
distance from the parent of the returned values.</p>
</dd>
<p></p>
<dt><strong><a name="item_cols">cols</a></strong><br />
</dt>
<dd>
You can specify an array reference as the value to <a href="#item_cols"><code>cols</code></a> to alter the values
returned. Instead of doing ``SELECT * FROM ...'', the query will be ``SELECT &lt;what
you specify&gt; FROM ...''. Note, however, that the father, root, and depth columns
are required and will be present in the rows returned whether or not you specify
them.
</dd>
<p></p>
<dt><strong><a name="item_sort_col_2c_sort_order">sort_col, sort_order</a></strong><br />
</dt>
<dd>
Where the <code>sort</code> option sorts the results based on tree levels, <code>sort_col</code> and
<code>sort_order</code> control the sorting for nodes with the same father ID. For
example, with this tree:
a
b
c
<code>sort_col</code> and <code>sort_order</code> affect whether or not b comes before or after c.
The value of each can either be a scalar value or an array reference. There is
essentially no difference, the scalar value is just a little easier when you are
only sorting on a single column. The values of <code>sort_col</code> should be column
names, and the values of <code>sort_order</code> 'ASC' or 'DESC', per sort column
respectively. For example:
sort_col =&gt; ['a','b'], sort_order =&gt; ['ASC', 'DESC']
will sort first in ascending order based on the value of a, then descending
order based on the value of column b. This correlates directly to SQL - it
becomes ``ORDER BY a ASC, b DESC''.
</dd>
<dd>
<p>You can specify a different sort order for roots by using the <a href="#item_roots_order_by"><code>roots_order_by</code></a>
option, when using <code>id =&gt; 0</code>. See below.</p>
</dd>
<p></p>
<dt><strong><a name="item_condition">condition</a></strong><br />
</dt>
<dd>
If you want to limit the results, you can pass a GT::SQL::Condition object into
<code>children()</code> via the condition key. The condition will apply to the select
performed. For example, if you want to select rows with a column ``a'' having a
value less than 20, you could do:
my $cond = GT::SQL::Condition-&gt;new(a =&gt; '&lt;' =&gt; 20)
my $children = $tree-&gt;children(..., condition =&gt; $cond);
</dd>
<p></p>
<dt><strong><a name="item_limit">limit</a></strong><br />
</dt>
<dd>
Like condition, you can specify any valid LIMIT _____ value here, for example
``50, 25''. This option is only used when using <code>id =&gt; 0</code> - it will limit the
number of roots returned, taking into account the sort_col and sort_order.
</dd>
<p></p>
<dt><strong><a name="item_roots_only">roots_only</a></strong><br />
</dt>
<dd>
If you specify this option, it will assume that what you passed in via <a href="#item_id"><code>id</code></a>
consists only of root_ids. Doing so makes a join with the tree table
unneccessary and allows you to use the <a href="#item_select_from"><code>select_from</code></a> option. This option can be
used (and generally this is a good idea) when specifying <code>id =&gt; 0</code>.
</dd>
<p></p>
<dt><strong><a name="item_roots_order_by">roots_order_by</a></strong><br />
</dt>
<dd>
This option controlls the order of root posts, when selecting roots using
<code>id =&gt; 0</code> and a limit. <code>sort_order</code> above will affect the order of
children of the roots, but the order of the roots themselves will be controlled
by whatever <code>ORDER BY</code> value you specify here.
</dd>
<dd>
<p>Again, this option requires that <code>id =&gt; 0</code>, <a href="#item_roots_only"><code>roots_only</code></a>, and <a href="#item_limit"><code>limit</code></a> are
also being used.</p>
</dd>
<dd>
<p>If this option is omitted, the <code>ORDER BY</code> will be generated from the values of
the <code>sort_col</code> and <code>sort_order</code> options.</p>
</dd>
<p></p>
<dt><strong><a name="item_select_from">select_from</a></strong><br />
</dt>
<dd>
If you are using roots_only, you can also specify the <a href="#item_select_from"><code>select_from</code></a> option.
This option allows you to perform the selects from a GT::SQL::Relation object
instead of just the table associated with the tree. Note that the table
associated with the tree must be part of the relation, however you can have as
many other tables as you like.
</dd>
<p></p>
<dt><strong><a name="item_left_join">left_join</a></strong><br />
</dt>
<dd>
If the select_from relation should be a left join, pass <code>left_join =&gt; 1</code>.
This simply passes the <a href="#item_left_join"><code>left_join</code></a> option to -&gt;select. This option is only
applicable when select_from is used.
</dd>
<p></p></dl>
<p>
</p>
<h2><a name="parents">parents</a></h2>
<p>This is effectively the opposite of children. Instead of getting back all of the
children nodes, it gives the parents, all the way up to the root for any given
node. The return value is the same as that of <code>children</code>, so see that section.</p>
<p>Each array returned by <code>children</code> is sorted by depth from root to parent.</p>
<dl>
<dt><strong>id</strong><br />
</dt>
<dd>
<a href="#item_id"><code>id</code></a> is the only required parameter for <code>parents()</code>. It should be either a
scalar value or an array reference. You specify the ID's of children whose
parents you are looking for. The type of argument (scalar or array ref) affects
the return in the same way as <code>children()</code>.
</dd>
<p></p>
<dt><strong>cols</strong><br />
</dt>
<dd>
<a href="#item_cols"><code>cols</code></a> works in a similar way to the <a href="#item_cols"><code>cols</code></a> parameter to <code>children</code>. You
specify the columns you want in the return as an array ref. What you get back
will have these columns in it. If <a href="#item_cols"><code>cols</code></a> is not specified, you'll get back all
columns.
</dd>
<dd>
<p>Note that 'tree_id_fk' and the depth column for the table are required fields
and will be added if not specified.</p>
</dd>
<p></p></dl>
<p>
</p>
<h2><a name="child_ids">child_ids</a></h2>
<p>If you are looking for just the ID's of the children of a particular node, you
should use this. The return value is one of the following, depending on what you
pass in:</p>
<p>hash reference of array references:
{ ID =&gt; [ID, ID, ...], ... }
with one ID in the hash reference for each id you specify. The array reference
contains the child ID's of the key ID.</p>
<p>hash reference of hash references:
{ ID =&gt; { ID =&gt; dist, ID =&gt; dist, ... }, ... }
with one ID in the other hash reference for each id you specify. The inner hash
reference is made of child_id =&gt; child_distance key-value pairs.</p>
<p>array reference or hash reference:
[ID, ID, ...]
hash reference:
{ ID =&gt; dist, ID =&gt; dist }</p>
<p>The first two apply when passing in an array reference for <a href="#item_id"><code>id</code></a>, the latter two
when passing a scalar value for <a href="#item_id"><code>id</code></a>. The first and third are without
<a href="#item_include_dist"><code>include_dist</code></a> specified, the second and fourth occur when you specify
<a href="#item_include_dist"><code>include_dist</code></a>.</p>
<dl>
<dt><strong>id</strong><br />
</dt>
<dd>
Like all other accessors, child_ids takes a scalar value or array reference as
the <a href="#item_id"><code>id</code></a> value. Return as noted above.
</dd>
<p></p>
<dt><strong><a name="item_include_dist">include_dist</a></strong><br />
</dt>
<dd>
This changes the return as noted above - instead of just getting an array
reference of child ID's, you get the child ID's as the keys of a hash reference,
and the distances of the child from the parent you requested as the values.
</dd>
<p></p></dl>
<p>
</p>
<h2><a name="parent_ids">parent_ids</a></h2>
<p>Exactly the same as child_ids, except that this works <em>up</em> the tree instead of
<em>down</em>. Takes the same arguments, gives the same possible returns.</p>
<p>
</p>
<hr />
<h1><a name="indices">INDICES</a></h1>
<p>A tree requires a few indices to get optimal performance out of it. If the table
is never expected to be more than just a few rows, you won't notice a
substantial difference, however, as with any table, as the table grows the
performance proper indexing provides becomes more appreciable.</p>
<p>Two indices are created automatically on the tree table, one on tree_id_fk, and
the other on tree_anc_id_fk,tree_dist, so you don't need to worry about that
table.</p>
<p>Obviously, the usage of the tree affects how many indices you want, this section
is simply to provide some general guidelines for the indices required.</p>
<p>Because the roots_only option is based solely on the main table and not the
tree, if you are using roots_only (calling children with id =&gt; 0 automatically
turns on the roots_only option), you want to make sure you have an index on the
root column. If you also use the max_depth depth option, add the depth column to
this index.</p>
<p>Keep in mind that you may need to mix other columns in here if you are using a
condition with children(). This also applies when using the <code>sort_col</code> and
<code>sort_order</code> parameters - basically you need to figure out what your indices
are, and then add in the root column and, if using max_depth, the depth column.</p>
<p>
</p>
<hr />
<h1><a name="copyright">COPYRIGHT</a></h1>
<p>Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved.
<a href="http://www.gossamer-threads.com/">http://www.gossamer-threads.com/</a></p>
<p>
</p>
<hr />
<h1><a name="version">VERSION</a></h1>
<p>Revision: $Id: Tree.pm,v 1.29 2005/05/31 06:26:32 brewt Exp $</p>
</body>
</html>