SQL trees and other nonsense... - Mailing list pgsql-general

From Trilobite Trilobite
Subject SQL trees and other nonsense...
Date
Msg-id BAY15-F67VGeSJIEoPX0002c11b@hotmail.com
Whole thread Raw
Responses Re: SQL trees and other nonsense...
Re: SQL trees and other nonsense...
List pgsql-general
First, If there are any developers listening... Thank you so much for your
work.  I love everything about PG.  It makes the life of a db enthusiast so
sweet (otherwise, it would be an expensive habit).  Also, I'm helping
companies move to a better db and cut their costs.  It's truly one of the
great open source projects out there.  Good - now I got that off my chest.

Now the issue at hand:

I'm playing with tree structures in SQL.  It's kinda cool and definitely a
big hack.

Anyway, there are a few things in our database that are more hierarchal then
they are relational.  The problem I'm working with is accounting, as in,
"accounts > owners equity > expense accounts > rent > shop rent" etc...  I
have no idea how many accounts the end user will set up and I have no idea
about their structure.

In this case I'm making a base table to be inherited from any other tables
that need to store information in a hierarchy.  Also, there is not just one
big branch.  There are many trees each with its own root.  Here is the
definition of the base table "trees":


CREATE TABLE trees
(
    id    SERIAL    PRIMARY KEY,
    root    INTEGER    NULL,
    low    INTEGER    NOT NULL,
    high    INTEGER    NOT NULL,
    CONSTRAINT low_less_then_high CHECK (low < high),
    CONSTRAINT low_grater_then_zero CHECK (low > 0),
    CONSTRAINT high_greater_than_one CHECK (high > 1),
    CONSTRAINT root_must_link_to_id FOREIGN KEY (root) REFERENCES trees ON
DELETE CASCADE ON UPDATE CASCADE
);


db=# SELECT * from trees;

id | root | low | high
----+------+-----+------
1  | NULL |   1 |    8
2  |    1 |   2 |    3
3  |    1 |   4 |    5
4  |    1 |   6 |    7
(4 rows)

Visualization of data:

             1
         (1  N  8)
        /    |    \
       /     |     \
      /      |      \
     2       |       4
(2  1  3)   |   (6  1  7)
             3
         (4  1  5)


Another example where I might use this is in a threaded conversation.  So I
would just create a table for conversations
and accounts and add the additional information needed:


CREATE TABLE conversations
(
    body    TEXT    NOT NULL
)INHERITS(trees);

and...

CREATE TABLE accounts
(
    name    TEXT    NOT NULL,
    opened    DATE    NOT NULL

)INHERITS(trees);


I plan on writing functions to insert nodes into the tree, remove them, drop
the whole branch, etc... because changes to one node might affect the
others.  But, I though I could save myself some function writing if I could
make a self-referencing table.  So if the "root" is deleted or changed FKs
would take care of themselves.  The problem is that it doesn't seem to work
like I thought it would.  So I'm left with things like:


CREATE OR REPLACE FUNCTION forest.prune_branch(INTEGER) RETURNS VOID AS
'
DECLARE
    node_id        ALIAS FOR $1;
    node_root    INTEGER;
    node_low    INTEGER;
    node_high    INTEGER;
    less_count    INTEGER;
BEGIN
    SELECT INTO node_root, node_low, node_high root, low, high
    FROM base.trees
    WHERE id = node_id;

    -- Make sure it is there before moving on...
    IF NOT FOUND THEN
        RAISE EXCEPTION \'Branch not found at %!\', node_id;
        RETURN;
    END IF;

    -- RAISE INFO \'Node = %, Root = %, Low = %, High = %\', node_id,
node_root, node_low, node_high;

    -- If the node is a root then do a quick and dirty delete...
    IF (node_root IS NULL) AND (node_low = 1) THEN
        DELETE FROM base.trees WHERE root = node_id;
        DELETE FROM base.trees WHERE id = node_id;
        RETURN;
    END IF;

    -- Not a root, do it the hard way...
    less_count := (node_high - node_low + 1);

    DELETE FROM base.trees
    WHERE low BETWEEN node_low AND node_high AND root = node_root;

    UPDATE base.trees SET
        low =    CASE WHEN low > node_low
            THEN low - less_count
            ELSE low
        END,
        high =  CASE WHEN high > node_low
            THEN high - less_count
            ELSE high
        END
    WHERE root = node_root OR id = node_root;
    RETURN;
END;
'
LANGUAGE 'plpgsql';

While a function like this is needed if you are deleting a node from the
middle of a branch, it shouldn't be necessary to delete a node where it is a
"root".

So I guess what I'm wondering is:

1) Anything wrong with my approach?
2) Should I forget about the FK constraints on the base table - seeing as
they don't seem to work?
3) If I did go with a collection of functions to maintain the tree, how
would I enforce their use (restrict dels, ins, and ups) without restricting
the functions?
4) What other ideas do you all have?

Thanks in advance for your input and sorry if this is a poorly stated
question but it is different enough from standard SQL that I thought "the
more info the better".

Later,
Me

_________________________________________________________________
Get reliable access on MSN 9 Dial-up. 3 months for the price of 1!
(Limited-time offer)
http://join.msn.com/?page=dept/dialup&pgmarket=en-us&ST=1/go/onm00200361ave/direct/01/


pgsql-general by date:

Previous
From: "John Liu"
Date:
Subject: Re: select distinct w/order by
Next
From: "Rajat Katyal"
Date:
Subject: PERFORM statement inside procedure