views:

139

answers:

8

For example, "Dole Banana" is a kind of product, it's listed under the "Bananas" category, when I open the "Fruits" category, I want to see "Dole Banana".

+ Food
|--+ Fruits
|------+ Bananas   
|------+ Apples
|--+ Vegetables
|------+ Onion
|------+ Spinach
A: 

You could use simple table structure with parent_category_id and retrieve whole tree with recursion or implement left/right values and fetch whole tree using preordered tree traversal method.

Eimantas
But this is too slow, do you know any more efficient structure?
silent
no it's not. define "slow" in your case.
Eimantas
A: 

If you mean an infinite number of levels, then a self referencing table that can be recursed. Example: StuffID, StuffName, StuffParentID (FK to Stuff ID)

For a finite number, fixed tables: parent-child-grandchild

gbn
Actually I don't really need an infinite one, but may be 4 to 5 levels are required, can the parent-child-grandchild structure meet my requirement?
silent
Unless fixed, I'd use the self reference model
gbn
+2  A: 

If you're looking for online resources that address this problem, "Storing a Tree in a Database" would be a good search phrase.

As for the solution, note that each subcategory can have either one or zero parent categories. Therefore, the entire tree can be stored in a single self-refferental table with a "parent" field.

Using your example tree:

 ID  | PARENT | NAME
-----+--------+-------------
  1  |  null  | Food
  2  |   1    | Fruits
  3  |   2    | Bananas
  4  |   2    | Apples
  5  |   1    | Vegetables
  6  |   5    | Onion
  7  |   5    | Spinach
Mike Koval
+1. This is known as "hierarchical model" http://en.wikipedia.org/wiki/Hierarchical_model
Pascal Thivent
This structure is what I thought, but the problem is, when I open the "Fruits" category, I want all the items (product items, not sub-categories) listed, including all the products under the "Apples" and "Bananas" sub-categories. For example, "Dole Banana" is a kind of product, it's listed under "Bananas" category, when I open the "Fruits" category, I want to see "Dole Banana".
silent
+1  A: 

A Table "Categories" with 3 fields.

  1. CategoryId not null (primary key)
  2. ParentCategoryId null
  3. CategoryName not null

To get all root categories

select * from Categories where ParentCategoryId is null

To get all sub categories of some specific category:

select * from Categories where ParentCategoryId = 12
ajitdh
But what if I need to list all items from all the sub-categories of a specific category?
silent
select * from Categories where ParentCategoryId in (select CategoryId from Categories where ParentCategoryId = 12) will give all items of all sub categories of category 12. But this is not the perfect solution when you have infinte subcategories. The solution for infinite subcategories is to create a recursive method in your code to fetch sub categories and their items.
ajitdh
A: 
    CREATE TABLE [dbo].[Category](
    [CategoryId] [int] NOT NULL,
    [ParentCategoryId] [int] NULL,
    [CategoryName] [nvarchar](50) NOT NULL,
     CONSTRAINT [PK_Category] PRIMARY KEY CLUSTERED 
    (
        [CategoryId] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON 

[PRIMARY]

GO

ALTER TABLE [dbo].[Category]  WITH CHECK ADD  CONSTRAINT [FK_Category_Category] FOREIGN KEY([ParentCategoryId])
REFERENCES [dbo].[Category] ([CategoryId])
GO

ALTER TABLE [dbo].[Category] CHECK CONSTRAINT [FK_Category_Category]
GO
keithwarren7
+1  A: 

I've usually used left-right trees which are very well adapted to database querys. You have a parentId,left and right value for each node. Every nodes children has a left/right value that is between the parent nodes left and right which makes it very easy to find for example all children/parents of a node. It does give a slight overhead on insertions, but it shouldn't be too much of an impact unless you insert alot.

Edit: Just a word of warning though, you need to make the insert/update operations in a locked transaction or the tree can get messed up.

Runeborg
What about the efficiency? I need to implement this onto a heavy-traffic site.
silent
The search efficiency is good. If you want to find all children of a node you do: select * from Whatever where left > @parentLeft and right < @parentRight, I don't think you'll get more effective than that :)
Runeborg
For this structure, If want to delete one node, I'll have to update all the nodes?
silent
Yes, all nodes to the right of the node you are deleting. But unless you modify the tree more than you read it it's not a problem.
Runeborg
Technically, you don't _have_ to update on delete, you can just leave gaps in the left-right values. It's inserting new nodes in the middle that's expensive, and it's moving subtrees around that's hideously expensive. If i recall correctly, Oracle's Vadim Tropashko came up with more insert-friendly schemes a few years ago, see.http://www.dbazine.com/oracle/or-articles/tropashko4http://arxiv.org/html/cs.DB/0401014
wallenborn
A: 

For infinite hierarchy, use the modified preorder tree traversal algorithm

Alex L
A: 

Here's a different approach that might be useful to you. It has slightly more maintenance costs than the PARENT_ID or lft/rght approach, but retrieval is much easier (and faster).

Dole bananas can be in products table. You have a single category_id for a product.

We had a requirement to allow multiple categories for a product. This lead us to having a categories_products join table, where product could have multiple joined rows. Then, we had to decide whether to have Dole bananas in just bananas, or in bananas and all its parents as well. As speed of retrieval was critical, we put dole bananas in its categories and all of their parent categories. There are three category-product joins for dole bananas.

Using this structure, returning all the items from any category is easy and quick, only one query. You can't do this in the PARENT_ID approach (unless you hard-code parents, grand-parents, etc.) Adding a category is easy. Categorizing a product requires inserting multiple rows in the join table. Deleting and moving categories are a bit trickier.

ndp