CDS Hierarchien ermöglichen die effiziente Abbildung von Parent-Child-Strukturen direkt in CDS Views. Mit der DEFINE HIERARCHY Syntax können rekursive Beziehungen wie Organisationsstrukturen, Stücklisten oder Kostenstellenhierarchien elegant modelliert und in Fiori Elements visualisiert werden.
Grundkonzept
Hierarchische Daten sind in SAP allgegenwärtig. CDS Hierarchien bieten eine native Lösung:
| Klassischer Ansatz | CDS Hierarchien |
|---|---|
| Rekursive ABAP-Logik | Deklarative Definition |
| Manuelle Level-Berechnung | Automatische Hierarchie-Attribute |
| Performance-intensiv | DB-Pushdown optimiert |
| Schwer wartbar | Zentral definiert |
| Keine UI-Integration | Fiori Elements TreeTable |
Anwendungsfälle
- Organisationsstrukturen: Unternehmenshierarchie, Kostenstellen, Profit Center
- Stücklisten (BOM): Material-Komponenten-Beziehungen
- Projekthierarchien: PSP-Elemente, Arbeitspakete
- Kategorien: Produktkategorien, Dokumentklassen
- Genehmigungsworkflows: Manager-Mitarbeiter-Beziehungen
Datenmodell für Hierarchien
Basistabelle
Eine typische Hierarchietabelle enthält einen Selbstverweis:
@EndUserText.label: 'Cost Center'define table zcostcenter { key client : abap.clnt not null; key cost_center: abap.char(10) not null; parent_cc : abap.char(10); description : abap.char(40); responsible : abap.char(12); valid_from : abap.dats; valid_to : abap.dats;}Interface View
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Cost Center'define view entity ZI_CostCenter as select from zcostcenter{ key cost_center as CostCenter, parent_cc as ParentCostCenter, description as Description, responsible as Responsible, valid_from as ValidFrom, valid_to as ValidTo}Hierarchy Definition
Die eigentliche Hierarchie wird mit DEFINE HIERARCHY definiert:
@AccessControl.authorizationCheck: #NOT_REQUIREDdefine hierarchy ZI_CostCenterHierarchy as parent child hierarchy( source ZI_CostCenter child to parent association _Parent start where ParentCostCenter is initial siblings order by CostCenter ){ key CostCenter, ParentCostCenter, Description, Responsible, ValidFrom, ValidTo,
// Automatisch generierte Hierarchie-Attribute $node.hierarchy_rank, $node.hierarchy_tree_size, $node.hierarchy_parent_rank, $node.hierarchy_level, $node.hierarchy_is_cycle, $node.hierarchy_is_orphan}Syntax-Elemente
| Element | Beschreibung |
|---|---|
source | Basis-View mit den Hierarchiedaten |
child to parent association | Assoziation zum Parent-Knoten |
start where | Bedingung für Wurzelknoten |
siblings order by | Sortierung der Geschwisterknoten |
Die _Parent Assoziation
Im Basis-View muss eine Assoziation zum Parent definiert werden:
@AccessControl.authorizationCheck: #CHECK@EndUserText.label: 'Cost Center with Parent Association'define view entity ZI_CostCenter as select from zcostcenter association [0..1] to ZI_CostCenter as _Parent on $projection.ParentCostCenter = _Parent.CostCenter{ key cost_center as CostCenter, parent_cc as ParentCostCenter, description as Description, responsible as Responsible, valid_from as ValidFrom, valid_to as ValidTo,
_Parent}Hierarchie-Attribute ($node)
CDS Hierarchien stellen automatisch berechnete Attribute bereit:
| Attribut | Typ | Beschreibung |
|---|---|---|
$node.hierarchy_rank | INT8 | Eindeutige Zeilennummer im Baum |
$node.hierarchy_tree_size | INT8 | Anzahl Knoten im Teilbaum |
$node.hierarchy_parent_rank | INT8 | Rank des Parent-Knotens |
$node.hierarchy_level | INT4 | Tiefe im Baum (Root = 1) |
$node.hierarchy_is_cycle | INT1 | 1 wenn Knoten Teil eines Zyklus |
$node.hierarchy_is_orphan | INT1 | 1 wenn Parent nicht existiert |
Verwendung der Attribute
define hierarchy ZI_OrgUnitHierarchy as parent child hierarchy( source ZI_OrgUnit child to parent association _Parent start where ParentOrgUnit is initial siblings order by OrgUnitID ){ key OrgUnitID, ParentOrgUnit, OrgUnitName,
// Für UI-Darstellung $node.hierarchy_level as HierarchyLevel,
// Für Berechtigungsprüfung (alle untergeordneten) $node.hierarchy_tree_size as SubordinateCount,
// Datenqualität prüfen $node.hierarchy_is_orphan as IsOrphan, $node.hierarchy_is_cycle as HasCycle}Hierarchy Directory
Für komplexe Szenarien mit mehreren Hierarchien pro Datenquelle wird ein Hierarchy Directory verwendet:
@AccessControl.authorizationCheck: #NOT_REQUIREDdefine hierarchy ZI_CostCenterHierDir with hierarchy directory ZI_HierarchyDirectory as parent child hierarchy( source ZI_CostCenter child to parent association _Parent start where ParentCostCenter is initial siblings order by CostCenter ){ key CostCenter, ParentCostCenter, Description, $node.hierarchy_level}Hierarchy Directory View
Das Directory ermöglicht die Auswahl zwischen verschiedenen Hierarchieversionen:
@AccessControl.authorizationCheck: #NOT_REQUIREDdefine view entity ZI_HierarchyDirectory as select from zhierdir{ key hierarchy_id as HierarchyID, key valid_from as ValidFrom, valid_to as ValidTo, hierarchy_name as HierarchyName, is_active as IsActive}where is_active = 'X'Mehrere Hierarchien
-- Tabelle für mehrere Hierarchieversionen@EndUserText.label: 'Cost Center Hierarchy Versions'define table zcc_hier_versions { key client : abap.clnt not null; key hierarchy_id : abap.char(10) not null; key cost_center : abap.char(10) not null; parent_cc : abap.char(10); valid_from : abap.dats; valid_to : abap.dats;}Alternative: Parent-to-Child Hierarchie
Neben child to parent gibt es auch parent to child:
define hierarchy ZI_BOMHierarchy as parent child hierarchy( source ZI_BillOfMaterial parent to child association _Components start where MaterialType = 'FERT' -- Fertigerzeugnisse als Start siblings order by ItemNumber ){ key Material, key BOMItem, ComponentMaterial, Quantity, Unit, $node.hierarchy_level}Basis-View mit Child-Assoziation
define view entity ZI_BillOfMaterial as select from zbom association [0..*] to ZI_BillOfMaterial as _Components on $projection.Material = _Components.ParentMaterial{ key material as Material, key bom_item as BOMItem, parent_material as ParentMaterial, component as ComponentMaterial, quantity as Quantity, unit as Unit,
_Components}Consumption in Fiori Elements
TreeTable Annotation
Für die Darstellung als TreeTable in Fiori Elements:
@Metadata.layer: #CUSTOMERannotate view ZC_CostCenterHierarchy with{ @UI.lineItem: [{ position: 10, importance: #HIGH }] CostCenter;
@UI.lineItem: [{ position: 20 }] Description;
@UI.lineItem: [{ position: 30 }] Responsible;
@UI.lineItem: [{ position: 40 }] HierarchyLevel;}Projection für Fiori
@AccessControl.authorizationCheck: #NOT_REQUIRED@Metadata.allowExtensions: true@UI.headerInfo: { typeName: 'Kostenstelle', typeNamePlural: 'Kostenstellen', title.value: 'CostCenter', description.value: 'Description'}define view entity ZC_CostCenterHierarchy as projection on ZI_CostCenterHierarchy{ @UI.facet: [{ id: 'General', type: #IDENTIFICATION_REFERENCE, position: 10 }]
@UI.identification: [{ position: 10 }] key CostCenter,
@UI.identification: [{ position: 20 }] ParentCostCenter,
@UI.identification: [{ position: 30 }] Description,
@UI.identification: [{ position: 40 }] Responsible,
@UI.hidden: true HierarchyLevel,
@UI.hidden: true SubordinateCount}Service Definition
@EndUserText.label: 'Cost Center Hierarchy Service'define service ZUI_COSTCENTER_HIER_O4 { expose ZC_CostCenterHierarchy;}Hierarchie-Funktionen in ABAP
Hierarchie abfragen
" Gesamte Hierarchie lesenSELECT * FROM zi_costcenterhierarchy INTO TABLE @DATA(lt_hierarchy).
" Nur bestimmte EbenenSELECT * FROM zi_costcenterhierarchy WHERE hierarchy_level <= 3 INTO TABLE @DATA(lt_top_levels).
" Untergeordnete eines Knotens findenSELECT * FROM zi_costcenterhierarchy WHERE hierarchy_parent_rank = @lv_parent_rank INTO TABLE @DATA(lt_children).Pfad zum Root ermitteln
METHOD get_path_to_root. DATA: lt_path TYPE TABLE OF zi_costcenterhierarchy.
" Aktuellen Knoten lesen SELECT SINGLE * FROM zi_costcenterhierarchy WHERE costcenter = @iv_cost_center INTO @DATA(ls_current).
WHILE ls_current IS NOT INITIAL. APPEND ls_current TO lt_path.
IF ls_current-parentcostcenter IS INITIAL. EXIT. " Root erreicht ENDIF.
SELECT SINGLE * FROM zi_costcenterhierarchy WHERE costcenter = @ls_current-parentcostcenter INTO @ls_current. ENDWHILE.
rt_path = lt_path.ENDMETHOD.Teilbaum-Aggregation
" Summe über alle untergeordneten KostenstellenSELECT h~costcenter, SUM( b~amount ) AS total_budget FROM zi_costcenterhierarchy AS h INNER JOIN zbudget AS b ON h~costcenter = b~cost_center WHERE h~hierarchy_rank BETWEEN @lv_start_rank AND @lv_start_rank + @lv_tree_size - 1 GROUP BY h~costcenter INTO TABLE @DATA(lt_aggregated).Hierarchie mit Zeitscheiben
Für zeitabhängige Hierarchien:
@AccessControl.authorizationCheck: #CHECKdefine view entity ZI_CostCenterTemporal as select from zcostcenter_temp association [0..1] to ZI_CostCenterTemporal as _Parent on $projection.ParentCostCenter = _Parent.CostCenter and $projection.ValidFrom <= _Parent.ValidTo and $projection.ValidTo >= _Parent.ValidFrom{ key cost_center as CostCenter, key valid_from as ValidFrom, valid_to as ValidTo, parent_cc as ParentCostCenter, description as Description,
_Parent}define hierarchy ZI_CostCenterHierTemporal as parent child hierarchy( source ZI_CostCenterTemporal child to parent association _Parent start where ParentCostCenter is initial siblings order by CostCenter ){ key CostCenter, key ValidFrom, ValidTo, ParentCostCenter, Description, $node.hierarchy_level}where ValidFrom <= $session.system_date and ValidTo >= $session.system_dateDatenqualität prüfen
Verwaiste Knoten finden
@AccessControl.authorizationCheck: #NOT_REQUIREDdefine view entity ZI_OrphanCostCenters as select from ZI_CostCenterHierarchy{ CostCenter, ParentCostCenter, Description}where $node.hierarchy_is_orphan = 1Zyklen erkennen
@AccessControl.authorizationCheck: #NOT_REQUIREDdefine view entity ZI_CyclicCostCenters as select from ZI_CostCenterHierarchy{ CostCenter, ParentCostCenter, Description}where $node.hierarchy_is_cycle = 1Performance-Tipps
Index auf Parent-Feld
-- Index für schnelle Hierarchie-Navigation@AbapCatalog.sqlViewAppendName: 'ZSQLV_CC'define table zcostcenter { key client : abap.clnt not null; key cost_center: abap.char(10) not null; @AbapCatalog.foreignKey.screenCheck: false parent_cc : abap.char(10); ...}-- Secondary index on parent_cc empfohlenMaterialized Hierarchy Views
Für sehr große Hierarchien kann eine Materialisierung sinnvoll sein:
" Hierarchie in Puffertabelle schreibenMETHOD materialize_hierarchy. DELETE FROM zhier_buffer WHERE hierarchy_id = @iv_hierarchy_id.
SELECT * FROM zi_costcenterhierarchy INTO TABLE @DATA(lt_hierarchy).
MODIFY zhier_buffer FROM TABLE @lt_hierarchy.ENDMETHOD.Einschränkungen
| Einschränkung | Workaround |
|---|---|
| Max. 100 Hierarchieebenen | Meist ausreichend |
| Keine Aggregation in Hierarchy | Separaten View für Aggregation |
| Performance bei >1 Mio. Knoten | Materialisierung oder Filter |
Best Practices
| Empfehlung | Begründung |
|---|---|
child to parent für standard Hierarchien | Intuitivere Navigation nach oben |
parent to child für Stücklisten | Explosion von oben nach unten |
| Orphan-Check implementieren | Datenqualität sicherstellen |
| Index auf Parent-Feld | Performance-Optimierung |
| Zeitscheiben für temporale Daten | Historische Sichten ermöglichen |
$node-Attribute für UI nutzen | Level und Größe visualisieren |
| Hierarchy Directory für Versionen | Mehrere Hierarchieversionen verwalten |
Zusammenfassung
CDS Hierarchien bieten:
- Deklarative Definition: Hierarchie-Struktur direkt in CDS definiert
- Automatische Attribute: Level, Rank, Größe ohne Berechnung
- Datenqualität: Orphan- und Cycle-Erkennung eingebaut
- Fiori-Integration: Native TreeTable-Unterstützung
- Performance: Optimierung durch DB-Pushdown
Mit CDS Hierarchien können komplexe Parent-Child-Strukturen elegant modelliert und effizient abgefragt werden - von Organisationsstrukturen bis zu mehrstufigen Stücklisten.
Weiterführende Themen
- CDS Views Grundlagen - Basis-Konzepte von CDS
- CDS Access Control (DCL) - Berechtigungen für Hierarchien
- CDS Entity Buffer - Performance-Optimierung