Search code examples
postgresqldata-structurespostgresql-8.4hierarchical-queryhierarchical-trees

PostgreSQL Hierarchical, category tree


ENV : postgresql-8.4

I'm trying to build a category tree . Basically I'm expecting a final output such :

categoryName 
categoryPath
leafcategory
e.g. :

Digital Camera
Electronics ::: Digital Camera 
true

The table structure is

CREATE TABLE categories (
    id  SERIAL PRIMARY KEY,
    categoryid bigint,
    categoryparentid bigint,
    categoryname text,
    status integer DEFAULT 0,
    lang text,
    eysiteid text,
    country text,
    tempid text,
    leafcategory boolean
);

So far I've got this but is not working. Any help would be highly appreciated :

WITH RECURSIVE tree (CategoryID, CategoryParentID, CategoryName, category_tree, depth) 
AS ( 
    SELECT 
        CategoryID,
        CategoryParentID,
        CategoryName,
        CategoryName AS category_tree,
        0 AS depth 
    FROM categories 
    WHERE CategoryParentID IS NULL 
UNION ALL 
    SELECT 
        c.CategoryID,
        c.CategoryParentID,
        c.CategoryName,
        tree.category_tree  || '/' || c.CategoryName AS category_tree,
        depth+1 AS depth 
    FROM tree 
        JOIN categories c ON (tree.category_tree  = c.CategoryParentID) 
) 
SELECT * FROM tree ORDER BY category_tree;

Sample from database

cat=> select * from categories;
  id   | categoryid | categoryparentid |          categoryname          | status | lang | eysiteid | country | tempid | leafcategory 
-------+------------+------------------+--------------------------------+--------+------+------------+---------+--------+--------------
     1 |         -1 |                0 | Root                           |      1 | en   | 0          | us      |        | f
     2 |      20081 |               -1 | Antiques                       |      1 | en   | 0          | us      |        | f
    17 |       1217 |            20081 | Primitives                     |      0 | en   | 0          | us      |        | t
    23 |      22608 |            20081 | Reproduction Antiques          |      0 | en   | 0          | us      |        | t
    24 |         12 |            20081 | Other                          |      0 | en   | 0          | us      |        | t
    25 |        550 |               -1 | Art                            |      1 | en   | 0          | us      |        | f
    29 |       2984 |               -1 | Baby                           |      1 | en   | 0          | us      |        | f


Solution

  • It appears you were joining on the wrong field.

     --  create some test data
    DROP SCHEMA tmp CASCADE;
    CREATE SCHEMA tmp ;
    SET search_path=tmp;
    
    CREATE TABLE categories
        -- ( id  SERIAL PRIMARY KEY
        ( categoryid SERIAL PRIMARY KEY
        , categoryparentid bigint REFERENCES categories(categoryid)
        , categoryname text
        -- , status integer DEFAULT 0
        -- , lang text
        -- , ebaysiteid text
        -- , country text
        -- , tempid text
        -- , leafcategory boolean
            );
    INSERT INTO categories(categoryid,categoryparentid) SELECT gs, 1+(gs/6)::integer
    FROM generate_series(1,50) gs;
    
    UPDATE categories SET categoryname = 'Name_' || categoryid::text;
    UPDATE categories SET categoryparentid = NULL WHERE categoryparentid <= 0;
    UPDATE categories SET categoryparentid = NULL WHERE categoryparentid  >= categoryid;
    
    
    WITH RECURSIVE tree (categoryid, categoryparentid, categoryname, category_tree, depth)
    AS (
        SELECT
            categoryid
            , categoryparentid
            , categoryname
            , categoryname AS category_tree
            , 0 AS depth
        FROM categories
        WHERE categoryparentid IS NULL
    UNION ALL
        SELECT
            c.categoryid
            , c.categoryparentid
            , c.categoryname
            , tree.category_tree  || '/' || c.categoryname AS category_tree
            , depth+1 AS depth
        FROM tree
            JOIN categories c ON tree.categoryid  = c.categoryparentid
        )
    SELECT * FROM tree ORDER BY category_tree;
    

    EDIT: the other ("non-function") notation for recursive seems to work better:

    WITH RECURSIVE tree AS (
        SELECT
            categoryparentid AS parent
            , categoryid AS self
            , categoryname AS treepath
            , 0 AS depth
        FROM categories
        WHERE categoryparentid IS NULL
    UNION ALL
        SELECT
            c.categoryparentid AS parent
            , c.categoryid AS self
            , t.treepath  || '/' || c.categoryname AS treepath
            , depth+1 AS depth
        FROM categories c
        JOIN tree t ON t.self  = c.categoryparentid
        )
    SELECT * FROM tree ORDER BY parent,self
       ;
    

    UPDATE: in the original query, you should replace

    WHERE CategoryParentID IS NULL
    

    by:

    WHERE CategoryParentID = 0
    

    or maybe even:

    WHERE COALESCE(CategoryParentID, 0) = 0