- Library to handle hierarchyid type in SQL Server and go.
- Generation and parsing of hierarchyid type in go.
- Type wrapper for usage with gorm ORM.
- The hierarchyid is data to represent a position in a hierarchy in SQL Server.
- It is a variable length type with reduced storage requirements.
- Encodes the position in the hierarchy as a list of indexes
- For example in the tree below the path to
E
is /1/1/2/
- Indexes can be used to sort elements inside of a tree level.
- The
HierarchyID
is defined as a []int64
in go.
- When serialized into JSON a textual representation is used for readability.
- Represented as list separated by
/
. (e.g. /1/2/3/4/5/
)
- Each element in the slice represents a level in the hierarchy.
- An empty slice represents the root of the hierarchy.
- Elements placed in the root should not use an empty list.
- They should instead by represented by
/1/
, /2/
, etc.
- The library can be installed using
go get
.
go get github.com/tentone/hierarchyid
- Declare
HierarchyID
type in your gorm model, there is no need to specify the DB data type.
- Is is recommended to also mark the field as
unique
to avoid duplicates.
- The library will handle the serialization and deserialization of the field to match the SQL Server
hierarchyid
type.
type Model struct {
gorm.Model
Path hierarchyid.HierarchyID `gorm:"unique;not null;"`
}
- In some scenarios it might be usefull to also keep a tradicional relationship to the parent.
- This can be done by adding a
ParentID
field to the model.
- It ensures that the tree is consistent and that actions (e.g. delete) are cascaded to the children.
- Some operations might also be easier to perform with the parent relationship.
type Model struct {
gorm.Model
Path hierarchyid.HierarchyId `gorm:"unique;not null;"`
ParentID uint `gorm:"index"`
Parent *TestParentsTable `foreignKey:"parent_id;references:id;constraint:OnUpdate:NO ACTION,OnDelete:CASCADE;"`
}
- Elements can be added to the tree as regular entries
- Just make sure that the tree indexes are filled correctly, indexes dont need to be sequential.
db.Create(&Table{Path: hierarchyid.HierarchyID{Data: []int64{1}}})
db.Create(&Table{Path: hierarchyid.HierarchyID{Data: []int64{1, 1}}})
db.Create(&Table{Path: hierarchyid.HierarchyID{Data: []int64{1, 1, 2}}})
- To get all parents of a node use the
GetAncestors
method.
- The method will return a slice with all the parents of the node. This can be used as param for a query.
db.Model(&Table{}).Where("[path] IN (?)", child.Path.GetAncestors()).Find(&parents)
- Its also possible to get parents with the SQL version of the
GetAncestor
method.
- Example on getting the parent of an element.
db.Model(&Table{}).Where("[path] = ?.GetAncestor(1)", child.Path).Find(&parent)
- To get all children of a node use the
IsDescendantOf
method in SQL.
- Example on getting all children of a node (including the node itself).
elements := []Table{}
db.Where("[path].IsDescendantOf(?)=1", hierarchyid.HierarchyId{Data: []int64{1, 2}}).Find(&elements)
- It is also possible to filter the children based on sub-levels.
- Example on getting all nodes from root where at least one of the sub-level has a name that contains the text 'de'
SELECT *
FROM "table" as a
WHERE ([path].GetLevel()=1 AND [path].IsDescendantOf('/')=1) AND
(SELECT COUNT(*) FROM "table" AS b WHERE b.path.IsDescendantOf(a.path)=1 AND b.name LIKE '%de%')>0
- The
GetLevel
method can be used to filter nodes based on their level in the hierarchy. Also available in SQL with the same name GetLevel
.
- A more generic version of the same code presented above writen in go.
root := hierarchyid.GetRoot()
subQuery := db.Table("table AS b").Select("COUNT(*)").Where("[b].[path].IsDescendantOf([a].[path])=1 AND [b].[name] LIKE '%de%'")
conn = db.Table("table AS a").
Where("[a].[path].GetLevel()=? AND [a].[path].IsDescendantOf(?)=1 AND (?)>0", root.GetLevel()+1, root, subQuery).
Find(&elements)
- To move a node to a new parent there is the
GetReparentedValue
method that receives the old parent and new parent and calculates the new hierarchyid value.
- Example on moving a node to a new parent.
db.Model(&Table{}).Where("[id] = ?", id).Update("[path]=?", node.Path.GetReparentedValue(oldParent.Path, newParent.Path))
- The project is distributed using a MIT license. Available on the project repository.