{"id":1622,"date":"2026-06-21T13:28:17","date_gmt":"2026-06-21T11:28:17","guid":{"rendered":"https:\/\/www.flip-design.de\/?p=1622"},"modified":"2026-06-21T13:28:17","modified_gmt":"2026-06-21T11:28:17","slug":"implementing-parent-child-hierarchies-in-power-bi-using-path-dax-udfs-and-tmdl","status":"publish","type":"post","link":"https:\/\/www.flip-design.de\/?p=1622","title":{"rendered":"Implementing Parent-Child Hierarchies in Power BI Using PATH(), DAX UDFs, and TMDL"},"content":{"rendered":"\n<p>In many organizations, organizational structures, cost center hierarchies, or product groups are not stored as traditional hierarchies with fixed levels. Instead, they are modeled as so-called parent-child structures, where each organizational unit only references its immediate parent.<\/p>\n\n\n\n<p>While this approach provides a high degree of flexibility and simplifies maintenance, it can become challenging when visualizing the data in Power BI. Hierarchy slicers, matrix visuals, and drill-down scenarios typically require separate hierarchy levels. In addition, hierarchies with varying depths often result in unwanted <code>(Blank)<\/code> values within reports and visualizations.<\/p>\n\n\n\n<p>In this article, I will demonstrate how a parent-child hierarchy can be transformed into a report-friendly structure by using the <code>PATH()<\/code> function, the new DAX User Defined Functions (UDFs), and TMDL. Furthermore, I will show how to eliminate unwanted blank values while preserving the complete hierarchy structure.<\/p>\n\n\n\n<p>The organizational structure is stored in a simple parent-child table. Each row contains a unique ID and references its parent organizational unit through the <code>ParentID<\/code> column.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-2.png\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"457\" src=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-2.png\" alt=\"\" class=\"wp-image-1623\" srcset=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-2.png 940w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-2-300x146.png 300w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-2-768x373.png 768w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/a><\/figure>\n\n\n\n<p><strong>Example<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Contoso Corporation\n\u2514\u2500 Finance\n   \u2514\u2500 Accounting\n      \u2514\u2500 Accounts Payable\n<\/code><\/pre>\n\n\n\n<p>For example, <strong>Finance<\/strong> has a <code>ParentID<\/code> of <code>1<\/code> and therefore belongs to <strong>Contoso Corporation<\/strong>. Likewise, <strong>Accounting<\/strong> has a <code>ParentID<\/code> of <code>2<\/code> and therefore belongs to <strong>Finance<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>ID<\/th><th>Name<\/th><th>ParentID<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>Contoso Corporation<\/td><td><\/td><\/tr><tr><td>2<\/td><td>Finance<\/td><td>1<\/td><\/tr><tr><td>3<\/td><td>Accounting<\/td><td>2<\/td><\/tr><tr><td>4<\/td><td>Accounts Payable<\/td><td>3<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>To make the hierarchy usable in Power BI, the complete path from the root node to the current element must be generated.<\/p>\n\n\n\n<p><strong>Hierarchy Structure<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Contoso Corporation &gt; Finance &gt; Accounting &gt; Accounts Payable\n<\/code><\/pre>\n\n\n\n<p>In DAX, this can be achieved using the <code>PATH()<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Path =\nPATH ( ORG&#91;ID], ORG&#91;ParentID] )\n<\/code><\/pre>\n\n\n\n<p>The resulting path contains the IDs of all hierarchy levels:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>ID<\/th><th>Name<\/th><th>ParentID<\/th><th>Path<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>Contoso Corporation<\/td><td><\/td><td>1<\/td><\/tr><tr><td>2<\/td><td>Finance<\/td><td>1<\/td><td>1<\/td><\/tr><tr><td>3<\/td><td>Accounting<\/td><td>2<\/td><td>1<\/td><\/tr><tr><td>4<\/td><td>Accounts Payable<\/td><td>3<\/td><td>1<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>At this stage, the hierarchy is available as a technical path and can be further transformed into report-friendly hierarchy levels.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-3.png\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"505\" src=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-3.png\" alt=\"\" class=\"wp-image-1624\" srcset=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-3.png 940w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-3-300x161.png 300w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-3-768x413.png 768w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/a><\/figure>\n\n\n\n<p>After creating the hierarchy path using the <code>PATH()<\/code> function, the organizational structure initially exists only in a technical format. The path contains nothing but the IDs of the individual hierarchy levels.<\/p>\n\n\n\n<p><strong>Example<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1|2|3|4\n<\/code><\/pre>\n\n\n\n<p>While this representation is ideal for calculations, it is difficult to interpret in reports and visualizations. Business users typically want to see meaningful organizational names rather than technical identifiers.<\/p>\n\n\n\n<p>To make the hierarchy usable for reporting, the individual IDs within the path must be translated back into their corresponding organizational unit names. This is where the new DAX User Defined Functions (UDFs) come into play. Instead of implementing the same logic repeatedly for every hierarchy level, the logic can be defined once and reused throughout the model.<\/p>\n\n\n\n<p>As a result, the technical path:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1|2|3|4\n<\/code><\/pre>\n\n\n\n<p>can be transformed into a user-friendly hierarchy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Level 1 = Contoso Corporation\nLevel 2 = Finance\nLevel 3 = Accounting\nLevel 4 = Accounts Payable\n<\/code><\/pre>\n\n\n\n<p>Using a UDF provides several benefits:<\/p>\n\n\n\n<ul>\n<li>The DAX code becomes significantly easier to read and maintain.<\/li>\n\n\n\n<li>The logic only needs to be maintained in a single location.<\/li>\n\n\n\n<li>Additional hierarchy levels can be added more easily.<\/li>\n\n\n\n<li>The calculations can be reused throughout the entire model.<\/li>\n<\/ul>\n\n\n\n<p>The primary purpose of the function is to transform the technical parent-child structure into a readable hierarchy that can be used in hierarchy slicers, matrix visuals, and other reporting scenarios.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DEFINE\r\n    FUNCTION GetPathName =\r\n    (\r\n        PathValue : STRING,\r\n        LevelNumber : INT64\r\n    ) =>\r\n    VAR CurrentID =\r\n        PATHITEM ( PathValue, LevelNumber, 1 )\r\n    RETURN\r\n        LOOKUPVALUE (\r\n            ORG&#91;Name],\r\n            ORG&#91;ID], CurrentID\r\n        )\r\n\r\nEVALUATE\r\n{\r\n    GetPathName ( \"1|2|3|4\", 2 )\n\n\r\n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-4.png\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"505\" src=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-4.png\" alt=\"\" class=\"wp-image-1625\" srcset=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-4.png 940w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-4-300x161.png 300w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-4-768x413.png 768w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/a><\/figure>\n\n\n\n<p>Before creating the individual hierarchy levels, a reusable DAX User Defined Function (UDF) is defined using TMDL. The purpose of this function is to extract a specific level from the hierarchy path and return the corresponding organizational unit name.<\/p>\n\n\n\n<p>By implementing the logic once within a reusable function, the same code does not have to be repeated for every hierarchy level. This results in a cleaner, more maintainable, and easier-to-extend model.<\/p>\n\n\n\n<p>The following TMDL definition creates the function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>createOrReplace\r\n\r\n    function GetPathName =\r\n        (\r\n            PathValue : STRING,\r\n            LevelNumber : INT64\r\n        ) =>\r\n            VAR CurrentID =\r\n                PATHITEM ( PathValue, LevelNumber, INTEGER )\r\n            RETURN\r\n                LOOKUPVALUE (\r\n                    ORG&#91;Name],\r\n                    ORG&#91;ID], CurrentID\r\n                )\r\n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-5.png\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"505\" src=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-5.png\" alt=\"\" class=\"wp-image-1626\" srcset=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-5.png 940w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-5-300x161.png 300w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-5-768x413.png 768w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/a><\/figure>\n\n\n\n<h3>Why Are the Level Columns Required?<\/h3>\n\n\n\n<p>The <code>PATH()<\/code> function initially stores the hierarchy as a technical path, for example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1|2|3|4|5\n<\/code><\/pre>\n\n\n\n<p>To make the hierarchy usable in Power BI for matrix visuals, slicers, and relationships, the path must be split into individual hierarchy levels:<\/p>\n\n\n\n<ul>\n<li>Level 1 = Company<\/li>\n\n\n\n<li>Level 2 = Department<\/li>\n\n\n\n<li>Level 3 = Team<\/li>\n\n\n\n<li>Level 4 = Sub-Team<\/li>\n\n\n\n<li>Level 5 = Employee<\/li>\n<\/ul>\n\n\n\n<p>As a result, each hierarchy level receives its own column and can be filtered, grouped, and analyzed independently. The purpose of these calculated columns is therefore to transform the technical hierarchy path into a business-friendly organizational structure that can be used in reports and visualizations.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Level 1 = GetPathName ( ORG&#91;Path], 1 )\r\nLevel 2 = GetPathName ( ORG&#91;Path], 2 )\r\nLevel 3 = GetPathName ( ORG&#91;Path], 3 )\r\nLevel 4 = GetPathName ( ORG&#91;Path], 4 )\r\nLevel 5 = GetPathName ( ORG&#91;Path], 5 )\r\n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-6.png\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"505\" src=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-6.png\" alt=\"\" class=\"wp-image-1627\" srcset=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-6.png 940w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-6-300x161.png 300w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-6-768x413.png 768w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-7.png\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"505\" src=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-7.png\" alt=\"\" class=\"wp-image-1628\" srcset=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-7.png 940w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-7-300x161.png 300w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-7-768x413.png 768w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/a><\/figure>\n\n\n\n<h3>Identifying Leaf Nodes and Hiding Blank Values<\/h3>\n\n\n\n<p>Because not every organizational unit extends to the deepest hierarchy level, blank values are inevitable within the level columns. This occurs when different branches of the hierarchy have different depths.<\/p>\n\n\n\n<p>For example, the branch:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Contoso Corporation\n\u2514\u2500 Finance\n   \u2514\u2500 Accounting<\/code><\/pre>\n\n\n\n<p>ends at <strong>Level 3<\/strong>, while another branch may continue to <strong>Level 5<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Contoso Corporation\n\u2514\u2500 Finance\n   \u2514\u2500 Controlling\n      \u2514\u2500 Reporting\n         \u2514\u2500 BI Reporting<\/code><\/pre>\n\n\n\n<p>Since the hierarchy visual expects values for all defined levels, Power BI automatically fills the missing levels with <code>Blank<\/code>. These blanks are then displayed as additional nodes within the hierarchy visual.<\/p>\n\n\n\n<p>To hide these unwanted entries, an additional calculated column can be created:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>IsLeaf = IF ( NOT ISBLANK ( ORG&#91;Level 5] ), 1, 0 )<\/code><\/pre>\n\n\n\n<p>This column identifies all leaf nodes\u2014the lowest elements within a hierarchy branch\u2014with the value <code>1<\/code>. The hierarchy visual can then be filtered using <code>IsLeaf = 1<\/code>.<\/p>\n\n\n\n<p>The advantage of this approach is that the unwanted <code>(Blank)<\/code> entries disappear, while Power BI continues to automatically display all parent levels of the hierarchy. As a result, the organizational structure remains fully intact and the visual becomes significantly cleaner and easier to understand.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-8.png\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"505\" src=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-8.png\" alt=\"\" class=\"wp-image-1629\" srcset=\"https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-8.png 940w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-8-300x161.png 300w, https:\/\/www.flip-design.de\/wp-content\/uploads\/2026\/06\/image-8-768x413.png 768w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/a><figcaption class=\"wp-element-caption\">Conclusion<br>Parent-child hierarchies provide a flexible way to represent complex organizational structures in Power BI. Using the <code>PATH()<\/code> function, the complete hierarchy path is first generated and can then be split into individual hierarchy levels.<br>The use of DAX User Defined Functions (UDFs) results in cleaner and more maintainable code. Instead of implementing the same logic separately for each hierarchy level, it is defined once and reused throughout the model. This makes hierarchies easier to extend and maintain over time.<br>TMDL (Tabular Model Definition Language) further enhances this approach by allowing model objects such as calculated columns, hierarchies, and functions to be defined as code. This improves version control, reusability, and the automation of Power BI models.<br>Because hierarchy branches often have different depths, blank values frequently appear within the level columns. By creating an additional <code>IsLeaf<\/code> column, leaf nodes can be identified and used as a visual filter. This allows unwanted <code>(Blank)<\/code> entries to be hidden while preserving the complete hierarchy structure for report consumers.<br>As a general best practice, hierarchies and relationships should be modeled as early as possible in the data source or data warehouse. Doing so reduces the complexity of the semantic model, improves maintainability, and often results in better performance.<br>The combination of <code>PATH()<\/code>, DAX UDFs, TMDL, and leaf-node filtering provides a clean, reusable, and scalable solution for working with parent-child hierarchies in Power BI.<\/figcaption><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>In many organizations, organizational structures, cost center hierarchies, or product groups are not stored as traditional hierarchies with fixed levels. Instead, they are modeled as so-called parent-child structures, where each organizational unit only references its immediate parent. While this approach &hellip; <a href=\"https:\/\/www.flip-design.de\/?p=1622\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/www.flip-design.de\/index.php?rest_route=\/wp\/v2\/posts\/1622"}],"collection":[{"href":"https:\/\/www.flip-design.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.flip-design.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.flip-design.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.flip-design.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1622"}],"version-history":[{"count":1,"href":"https:\/\/www.flip-design.de\/index.php?rest_route=\/wp\/v2\/posts\/1622\/revisions"}],"predecessor-version":[{"id":1630,"href":"https:\/\/www.flip-design.de\/index.php?rest_route=\/wp\/v2\/posts\/1622\/revisions\/1630"}],"wp:attachment":[{"href":"https:\/\/www.flip-design.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1622"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.flip-design.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1622"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.flip-design.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1622"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}