CDS Hierarchien: Parent-Child-Strukturen effizient abbilden

kategorie
CDS
Veröffentlicht
autor
Johannes

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 AnsatzCDS Hierarchien
Rekursive ABAP-LogikDeklarative Definition
Manuelle Level-BerechnungAutomatische Hierarchie-Attribute
Performance-intensivDB-Pushdown optimiert
Schwer wartbarZentral definiert
Keine UI-IntegrationFiori 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_REQUIRED
define 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

ElementBeschreibung
sourceBasis-View mit den Hierarchiedaten
child to parent associationAssoziation zum Parent-Knoten
start whereBedingung für Wurzelknoten
siblings order bySortierung 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:

AttributTypBeschreibung
$node.hierarchy_rankINT8Eindeutige Zeilennummer im Baum
$node.hierarchy_tree_sizeINT8Anzahl Knoten im Teilbaum
$node.hierarchy_parent_rankINT8Rank des Parent-Knotens
$node.hierarchy_levelINT4Tiefe im Baum (Root = 1)
$node.hierarchy_is_cycleINT11 wenn Knoten Teil eines Zyklus
$node.hierarchy_is_orphanINT11 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_REQUIRED
define 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_REQUIRED
define 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: #CUSTOMER
annotate 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 lesen
SELECT * FROM zi_costcenterhierarchy
INTO TABLE @DATA(lt_hierarchy).
" Nur bestimmte Ebenen
SELECT * FROM zi_costcenterhierarchy
WHERE hierarchy_level <= 3
INTO TABLE @DATA(lt_top_levels).
" Untergeordnete eines Knotens finden
SELECT * 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 Kostenstellen
SELECT 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: #CHECK
define 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_date

Datenqualität prüfen

Verwaiste Knoten finden

@AccessControl.authorizationCheck: #NOT_REQUIRED
define view entity ZI_OrphanCostCenters
as select from ZI_CostCenterHierarchy
{
CostCenter,
ParentCostCenter,
Description
}
where $node.hierarchy_is_orphan = 1

Zyklen erkennen

@AccessControl.authorizationCheck: #NOT_REQUIRED
define view entity ZI_CyclicCostCenters
as select from ZI_CostCenterHierarchy
{
CostCenter,
ParentCostCenter,
Description
}
where $node.hierarchy_is_cycle = 1

Performance-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 empfohlen

Materialized Hierarchy Views

Für sehr große Hierarchien kann eine Materialisierung sinnvoll sein:

" Hierarchie in Puffertabelle schreiben
METHOD 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änkungWorkaround
Max. 100 HierarchieebenenMeist ausreichend
Keine Aggregation in HierarchySeparaten View für Aggregation
Performance bei >1 Mio. KnotenMaterialisierung oder Filter

Best Practices

EmpfehlungBegründung
child to parent für standard HierarchienIntuitivere Navigation nach oben
parent to child für StücklistenExplosion von oben nach unten
Orphan-Check implementierenDatenqualität sicherstellen
Index auf Parent-FeldPerformance-Optimierung
Zeitscheiben für temporale DatenHistorische Sichten ermöglichen
$node-Attribute für UI nutzenLevel und Größe visualisieren
Hierarchy Directory für VersionenMehrere 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