Skip to content

g-miroslav/Hierarchy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 

Repository files navigation

Introduction

Hierarchy is a very useful funcion in Qlik Sense, that can transform a parent-child hierarchy table. A similar funcionality can be replicated with a recursive CTE in T-SQL. A hierarchy where a child node has a single parent can also be transformed using Parent and Child functions, such as PATH in DAX in Power BI, however these functions cannot handle data where a child node might have multiple parents. This is often the case with components and assemblies in a Bill of Materials, because a single component might be required for multiple assemblies.

Qlik Sense

Hierarchy syntax

Hierarchy (NodeID, ParentID, NodeName, [ParentName, [ParentSource, [PathName, [PathDelimiter, Depth]]]])(loadstatement | selectstatement) 

input

NodeID ParentID NodeName
1 4 London
2 3 Munich
3 5 Germany
4 5 UK
5 Europe

Qlik function

Hierarchy(NodeID, ParentID, NodeName, ParentName, NodeName, PathName, '\', Depth)

output

NodeID ParentID NodeName NodeName1 NodeName2 NodeName3 ParentName PathName Depth
1 4 London Europe UK London UK Europe\UK\London 3
2 3 Munich Europe Germany Munich Germany Europe\Germany\Munich 3
3 5 Germany Europe Germany - Europe Europe\Germany 2
4 5 UK Europe UK - Europe Europe\UK 2
5 Europe Europe - - - Europe 1

AdventureWorks

This demonstration utilizes a sample dataset, that is provided by Microsoft, and it can be downloaded here: AdventureWorks

Qlik Sense

The solution is Qlik is fairly straight-forward as all the work is done by the Hierarchy function. The BOMLevel is adjusted by 1, so that it aligns with the Depth in the output. BOMLevel starts at 0 and Depth starts counting the levels in the hierarchy at 1.

BOM_Hierarchy:
Hierarchy(ComponentID, ProductAssemblyID, ComponentName, AssemblyName, ComponentName, PathName, ' <- ', Depth)
SQL
SELECT
    BillOfMaterialsID
    , ProductAssemblyID
    , ComponentID
    , Product.Name as ComponentName
    , BOMLevel + 1 as BOMLevel
    , PerAssemblyQty
    , UnitMeasureCode
FROM
    Production.BillOfMaterials
    INNER JOIN Production.Product
        ON BillOfMaterials.ComponentID = Product.ProductID
WHERE
    EndDate IS NULL;

T-SQL

1. Load Data into CTE

The first step is to load it into a CTE. This step can be omitted, but it is helpful to see, that the query is the same as in Qlik so far.

WITH BOM_CTE as (
SELECT
    BillOfMaterialsID
    , ProductAssemblyID
    , ComponentID
    , Product.Name as ComponentName
    , BOMLevel + 1 as BOMLevel
    , PerAssemblyQty
    , UnitMeasureCode
FROM
    Production.BillOfMaterials
    INNER JOIN Production.Product
        ON BillOfMaterials.ComponentID = Product.ProductID
WHERE
    EndDate IS NULL
)

2. Recursive_CTE

The recursive CTE is able to flatten the hierarchy. This step adds the name of the parent node - AssemblyName, complete path - PathName, as well as Depth. PathJson is a slightly modified step of PathName, which then allows us to use the JSON_VALUE in the last step.

, Recursive_CTE as (
SELECT
    *
    , CAST(NULL as varchar(50)) as AssemblyName
    , CAST(ComponentName as varchar(max)) as PathName
    , '"' + CAST(ComponentName as varchar(max)) + '"' as PathJson
    , 1 as Depth
FROM
    BOM_CTE
WHERE
    ProductAssemblyID IS NULL
UNION ALL
SELECT
    t.BillOfMaterialsID
    , t.ProductAssemblyID
    , t.ComponentID
    , t.ComponentName
    , t.BOMLevel
    , t.PerAssemblyQty
    , t.UnitMeasureCode
    , CAST(Recursive_CTE.ComponentName as varchar(50)) as AssemblyName
    , Recursive_CTE.PathName + ' <- ' + CAST(t.ComponentName as varchar(max)) as PathName
    , Recursive_CTE.PathJson + ', "' + CAST(t.ComponentName as varchar(max)) + '"' as PathJson
    , Recursive_CTE.Depth + 1 as Depth
FROM
    BOM_CTE as t
    INNER JOIN Recursive_CTE
        ON t.ProductAssemblyID = Recursive_CTE.ComponentID
)

3. Final output table

T-SQL lacks proper functionality for splitting of a string by a delimiter. A workaround I found utilizes the JSON_VALUE, which is able to extract a value by its index. All that is required is to add double quoutes " and a comma , between each string, and then encapsulate the result in square brackets [ ]. Howerver, with this approach, it is necessary to know the maximum number of levels in the Hierarchy. The function in Qlik, on the other hand, can dynamically determine the number of levels and it add the appropriate number of extra columns, one for each level.

SELECT 
    BillOfMaterialsID
    , ProductAssemblyID
    , ComponentID
    , ComponentName
    , BOMLevel
    , PerAssemblyQty
    , UnitMeasureCode
    , JSON_VALUE('[' + PathJson + ']', '$[0]') as ComponentName1
    , JSON_VALUE('[' + PathJson + ']', '$[1]') as ComponentName2
    , JSON_VALUE('[' + PathJson + ']', '$[2]') as ComponentName3
    , JSON_VALUE('[' + PathJson + ']', '$[3]') as ComponentName4
    , JSON_VALUE('[' + PathJson + ']', '$[4]') as ComponentName5
    , AssemblyName
    , PathName
    , Depth
FROM
    Recursive_CTE;

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published