Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Designing hierarchy tables

Tags:

sql-server

Lets say that I have to store the following information in my database, enter image description here

Now my database tables will be designed and structured like this, enter image description here

In a later date, if I had to add another sub category level how will I be able to achieve this without having to change the database structure at all?

I have heard of defining the columns as row data in a table and using pivots to extract the details later on...Is that the proper way to achieve this?

Can someone please enlighten me or guide me in the proper direction? Thanks in advance...

:)

like image 435
Gayanee Wijayasekara Avatar asked Jan 22 '15 10:01

Gayanee Wijayasekara


People also ask

How do you create a hierarchy table?

The best way is to use a Hierarchy table to maintain Parent-Child relationship . In category table, you can add categories to n levels. In Items table, you can store the lowest level category. For example, take the case of Pepsi - its categoryId is 3.

What is a hierarchy table?

A hierarchy table specifies a hierarchy of terms (such as drugs and events) available for selecting criteria for data mining results before viewing them. A hierarchy table has no effect on the generation of statistical results for a run.

How do you create a hierarchy in SQL?

Select the data, adding a column that converts the Level data into a text value that is easy to understand. This query also orders the result by the hierarchyid data type. SELECT CAST(Level AS nvarchar(100)) AS [Converted Level], * FROM SimpleDemo ORDER BY Level; Here is the result set.


2 Answers

It would be difficult to add more columns to your table when new levels are to be generated. The best way is to use a Hierarchy table to maintain Parent-Child relationship.

Table : Items

 x----x------------------x------------x
 | ID |      Items       | CategoryId |
 |----x------------------x------------x
 | 1  |   Pepsi          |     3      |
 | 2  |   Coke           |     3      |
 | 3  |   Wine           |     4      |
 | 4  |   Beer           |     4      |     
 | 5  |   Meals          |     2      |
 | 6  |   Fried Rice     |     2      |
 | 7  |   Black Forest   |     7      |
 | 8  |   XMas Cake      |     7      |
 | 9  |   Pinapple Juice |     8      |
 | 10 |   Apple Juice    |     8      |
 x----x------------------x------------x

Table : Category

enter image description here

In category table, you can add categories to n levels. In Items table, you can store the lowest level category. For example, take the case of Pepsi - its categoryId is 3. In Category table, you can find its parent using JOINs and find parent's parents using Hierarchy queries.

In Category table, the categories with ParentId is null(that is with no parentId) will be the MainCategory and the other items with ParentId will be under SubCategory.

EDIT :

Any how you need to alter the tables, because as per your current schema, you cannot add column to the first table because the number of Sub category may keep on changing. Even if you create a table as per Rhys Jones answer, you have to join two tables with string. The problem in joining with string is that, when there is a requirement to change the Sub category or Main category name, you have to change in every table which you be fall to trouble in future and is not a good database design. So I suggest you to follow the below pattern.

Here is the query that get the parents for child items.

DECLARE @ITEM VARCHAR(30) = 'Black Forest'

;WITH CTE AS
(
    -- Finds the original parent for an ITEM ie, Black Forest 
    SELECT I.ID,I.ITEMS,C.CategoryId,C.Category,ParentId,0 [LEVEL]
    FROM #ITEMS I
    JOIN #Category C ON I.CategoryId=C.CategoryId   
    WHERE ITEMS = @ITEM

    UNION ALL

    -- Now it finds the parents with hierarchy level for ITEM  
    -- ie, Black Forest. This is called Recursive query, which works like loop
    SELECT I.ID,I.ITEMS,C.CategoryId,C.Category,C.ParentId,[LEVEL] + 1
    FROM CTE I
    JOIN #Category C ON C.CategoryId=I.ParentId 
)
-- Here we keep a column to show header for pivoting ie, CATEGORY0,CATEGORY1 etc
-- and keep these records in a temporary table #NEWTABLE
SELECT ID,ITEMS,CATEGORYID,CATEGORY,PARENTID,
'CATEGORY'+CAST(ROW_NUMBER() OVER(PARTITION BY ITEMS ORDER BY [LEVEL] DESC)-1 AS VARCHAR(4)) COLS,
ROW_NUMBER() OVER(PARTITION BY ITEMS ORDER BY [LEVEL] DESC)-1 [LEVEL] 
INTO #NEWTABLE
FROM CTE
ORDER BY ITEMS,[LEVEL]
OPTION(MAXRECURSION 0)

Here is the result from the above query

enter image description here

Explanation

  • Black Forest comes under Cake.
  • Cake comes under Bakery.
  • Bakery comes under Food.

Like this you can create children or parent for any number of levels. Now if you want to add a parent to Food and Beverage, for eg, Food Industry, just add Food Industry to Category table and keep Food Industry's Id as ParentId for Food and Beverage. Thats all.

Now if you want do pivoting, you can follow the below procedures.

1. Get values from column to show those values as column in pivot

DECLARE @cols NVARCHAR (MAX)

SELECT @cols = COALESCE (@cols + ',[' + COLS + ']', '[' + COLS + ']')
               FROM    (SELECT DISTINCT COLS,[LEVEL] FROM #NEWTABLE) PV 
               ORDER BY [LEVEL] 

2. Now use the below PIVOT query

DECLARE @query NVARCHAR(MAX)
SET @query = 'SELECT * FROM 
             (
                 SELECT ITEMS, CATEGORY, COLS  
                 FROM #NEWTABLE
             ) x
             PIVOT 
             (
                 MIN(CATEGORY)
                 FOR COLS IN (' + @cols + ')
            ) p
            ORDER BY ITEMS;' 

EXEC SP_EXECUTESQL @query
  • Click here to view result

You will get the below result after the pivot

enter image description here

NOTE

  1. If you want all the records irrespective of an item, remove the WHERE clause inside CTE. Click here to view result.

  2. Now I have provided order of columns in pivot table as DESC ie, its shows top-level parent.....Item's parent. If you want to show Item's parent first followed be next level and top-level parent at last, you can change DESC inside the ROW_NUMBER() to ASC. Click here to view result.

like image 118
Sarath KS Avatar answered Oct 22 '22 22:10

Sarath KS


According to your schema there's no relationship between 'main category' and 'sub category' but your sample data suggests there would be a relationship, i.e. Alcohol IS A Beverage etc. This sounds like a hierarchy of categories, in which case you could you a single self-referencing Category table instead;

create table dbo.Category (
    CategoryID int not null constraint PK_Category primary key clustered (CategoryID),
    ParentCategoryID int not null,
    CategoryName varchar(100) not null
)

alter table dbo.Category add constraint FK_Category_Category foreign key(ParentCategoryID) references dbo.Category (CategoryID)

insert dbo.Category values (1, 1, 'Beverages')
insert dbo.Category values (2, 1, 'Soft Drink')
insert dbo.Category values (3, 1, 'Alcohol')

This way you can create as many levels of category as you want. Any category where ParentCategoryID = CategoryID is a top level category.

Hope this helps,

Rhys

like image 29
Rhys Jones Avatar answered Oct 22 '22 21:10

Rhys Jones