Hiérarchies CDS : Modéliser efficacement les structures parent-enfant

Catégorie
CDS
Publié
Auteur
Johannes

Les hiérarchies CDS permettent la représentation efficace des structures parent-enfant directement dans les vues CDS. Avec la syntaxe DEFINE HIERARCHY, les relations récursives comme les structures organisationnelles, les nomenclatures ou les hiérarchies de centres de coûts peuvent être modélisées élégamment et visualisées dans Fiori Elements.

Concept de base

Les données hiérarchiques sont omniprésentes dans SAP. Les hiérarchies CDS offrent une solution native :

Approche classiqueHiérarchies CDS
Logique ABAP récursiveDéfinition déclarative
Calcul manuel des niveauxAttributs de hiérarchie automatiques
Intensif en performanceOptimisé par DB-Pushdown
Difficile à maintenirDéfini de manière centralisée
Pas d’intégration UITreeTable Fiori Elements

Cas d’utilisation

  • Structures organisationnelles : Hiérarchie d’entreprise, centres de coûts, centres de profit
  • Nomenclatures (BOM) : Relations matériau-composants
  • Hiérarchies de projet : Éléments OTP, lots de travaux
  • Catégories : Catégories de produits, classes de documents
  • Workflows d’approbation : Relations manager-employé

Modèle de données pour les hiérarchies

Table de base

Une table de hiérarchie typique contient une auto-référence :

@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;
}

Vue d’interface

@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
}

Définition de hiérarchie

La hiérarchie proprement dite est définie avec DEFINE HIERARCHY :

@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,
// Attributs de hiérarchie générés automatiquement
$node.hierarchy_rank,
$node.hierarchy_tree_size,
$node.hierarchy_parent_rank,
$node.hierarchy_level,
$node.hierarchy_is_cycle,
$node.hierarchy_is_orphan
}

Éléments de syntaxe

ÉlémentDescription
sourceVue de base avec les données de hiérarchie
child to parent associationAssociation vers le noeud parent
start whereCondition pour les noeuds racine
siblings order byTri des noeuds frères

L’association _Parent

Dans la vue de base, une association vers le parent doit être définie :

@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
}

Attributs de hiérarchie ($node)

Les hiérarchies CDS fournissent des attributs calculés automatiquement :

AttributTypeDescription
$node.hierarchy_rankINT8Numéro de ligne unique dans l’arbre
$node.hierarchy_tree_sizeINT8Nombre de noeuds dans le sous-arbre
$node.hierarchy_parent_rankINT8Rang du noeud parent
$node.hierarchy_levelINT4Profondeur dans l’arbre (Root = 1)
$node.hierarchy_is_cycleINT11 si le noeud fait partie d’un cycle
$node.hierarchy_is_orphanINT11 si le parent n’existe pas

Utilisation des attributs

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,
// Pour l'affichage UI
$node.hierarchy_level as HierarchyLevel,
// Pour la vérification d'autorisation (tous les subordonnés)
$node.hierarchy_tree_size as SubordinateCount,
// Vérifier la qualité des données
$node.hierarchy_is_orphan as IsOrphan,
$node.hierarchy_is_cycle as HasCycle
}

Hierarchy Directory

Pour les scénarios complexes avec plusieurs hiérarchies par source de données, un Hierarchy Directory est utilisé :

@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
}

Vue Hierarchy Directory

Le directory permet la sélection entre différentes versions de hiérarchie :

@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"

Plusieurs hiérarchies

-- Table pour plusieurs versions de hiérarchie
@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 : Hiérarchie Parent-to-Child

En plus de child to parent, il existe aussi parent to child :

define hierarchy ZI_BOMHierarchy
as parent child hierarchy(
source ZI_BillOfMaterial
parent to child association _Components
start where MaterialType = 'FERT' -- Produits finis comme point de départ
siblings order by ItemNumber
)
{
key Material,
key BOMItem,
ComponentMaterial,
Quantity,
Unit,
$node.hierarchy_level
}

Vue de base avec association Child

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
}

Consommation dans Fiori Elements

Annotation TreeTable

Pour l’affichage en TreeTable dans 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 pour Fiori

@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
@UI.headerInfo: {
typeName: 'Centre de coûts',
typeNamePlural: 'Centres de coûts',
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
}

Définition de service

@EndUserText.label: 'Cost Center Hierarchy Service"
define service ZUI_COSTCENTER_HIER_O4 {
expose ZC_CostCenterHierarchy;
}

Fonctions de hiérarchie en ABAP

Interroger la hiérarchie

" Lire toute la hiérarchie
SELECT * FROM zi_costcenterhierarchy
INTO TABLE @DATA(lt_hierarchy).
" Uniquement certains niveaux
SELECT * FROM zi_costcenterhierarchy
WHERE hierarchy_level <= 3
INTO TABLE @DATA(lt_top_levels).
" Trouver les subordonnés d'un noeud
SELECT * FROM zi_costcenterhierarchy
WHERE hierarchy_parent_rank = @lv_parent_rank
INTO TABLE @DATA(lt_children).

Déterminer le chemin vers la racine

METHOD get_path_to_root.
DATA: lt_path TYPE TABLE OF zi_costcenterhierarchy.
" Lire le noeud actuel
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. " Racine atteinte
ENDIF.
SELECT SINGLE * FROM zi_costcenterhierarchy
WHERE costcenter = @ls_current-parentcostcenter
INTO @ls_current.
ENDWHILE.
rt_path = lt_path.
ENDMETHOD.

Agrégation de sous-arbre

" Somme sur tous les centres de coûts subordonnés
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).

Hiérarchie avec tranches temporelles

Pour les hiérarchies dépendantes du temps :

@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

Vérifier la qualité des données

Trouver les noeuds orphelins

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

Détecter les cycles

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

Conseils de performance

Index sur le champ parent

-- Index pour une navigation hiérarchique rapide
@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);
...
}
-- Index secondaire sur parent_cc recommandé

Vues de hiérarchie matérialisées

Pour les très grandes hiérarchies, une matérialisation peut être pertinente :

" Écrire la hiérarchie dans une table tampon
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.

Limitations

LimitationContournement
Max. 100 niveaux de hiérarchieGénéralement suffisant
Pas d’agrégation dans HierarchyVue séparée pour l’agrégation
Performance avec >1 Mio. noeudsMatérialisation ou filtre

Bonnes pratiques

RecommandationJustification
child to parent pour les hiérarchies standardNavigation plus intuitive vers le haut
parent to child pour les nomenclaturesExplosion de haut en bas
Implémenter la vérification d’orphelinsAssurer la qualité des données
Index sur le champ parentOptimisation de la performance
Tranches temporelles pour les données temporellesPermettre les vues historiques
Utiliser les attributs $node pour l’UIVisualiser niveau et taille
Hierarchy Directory pour les versionsGérer plusieurs versions de hiérarchie

Résumé

Les hiérarchies CDS offrent :

  • Définition déclarative : Structure de hiérarchie définie directement dans CDS
  • Attributs automatiques : Niveau, rang, taille sans calcul
  • Qualité des données : Détection des orphelins et cycles intégrée
  • Intégration Fiori : Support natif TreeTable
  • Performance : Optimisation par DB-Pushdown

Avec les hiérarchies CDS, des structures parent-enfant complexes peuvent être modélisées élégamment et interrogées efficacement - des structures organisationnelles aux nomenclatures multiniveaux.

Sujets connexes