CDS Views Deep Dive : De la base aux Analytics (2025)

Catégorie
Deep Dive
Publié
Auteur
Johannes

Les vues CDS (Core Data Services) sont le coeur du développement ABAP moderne. Ce deep dive vous montre tout, des bases jusqu’aux Analytics avancées.

Que sont les vues CDS ?

Les vues CDS sont des modèles de données sémantiques définis dans une syntaxe similaire à SQL, qui s’exécutent directement sur la base de données.

Avantages vs. vues classiques

FonctionnalitéVues classiques (SE11)Vues CDS
SyntaxeABAP DictionarySQL-like (DDL)
PerformanceBonneMeilleure (optimisée DB)
AnnotationsAucuneComplètes
AssociationsNonOui
AnalyticsLimitéesComplètes
RéutilisabilitéFaibleÉlevée

1. Bases CDS

Vue CDS la plus simple

@AbapCatalog.sqlViewName: 'ZV_CUSTOMER"
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Customer View"
define view Z_Customer
as select from kna1
{
key kunnr as Customer,
name1 as CustomerName,
land1 as Country,
ort01 as City
}

Composants :

  • @AbapCatalog.sqlViewName : Nom de la vue DB générée (max. 16 caractères)
  • @AccessControl : Vérification des autorisations
  • define view : Définition de la vue
  • as select from : Source de données
  • { ... } : Liste des champs

Activer la vue

  • Dans ADT : Ctrl+F3
  • Génère automatiquement la vue DB ZV_CUSTOMER

Utiliser la vue

SELECT Customer, CustomerName, Country
FROM Z_Customer
WHERE Country = 'DE"
INTO TABLE @DATA(lt_customers).

2. Joins et Associations

Inner Join

define view Z_SalesOrderWithCustomer
as select from vbak as SalesOrder
inner join kna1 as Customer
on SalesOrder.kunnr = Customer.kunnr
{
key SalesOrder.vbeln as SalesOrder,
SalesOrder.audat as OrderDate,
Customer.name1 as CustomerName,
Customer.land1 as Country
}

Left Outer Join

define view Z_CustomerWithOrders
as select from kna1 as Customer
left outer join vbak as SalesOrder
on Customer.kunnr = SalesOrder.kunnr
{
key Customer.kunnr as Customer,
Customer.name1 as CustomerName,
SalesOrder.vbeln as SalesOrder,
SalesOrder.netwr as OrderValue
}

Associations (Recommandées !)

Mieux que les Joins : Chargement paresseux, réutilisable

define view Z_Customer
as select from kna1
association [0..*] to vbak as _SalesOrders
on $projection.Customer = _SalesOrders.kunnr
{
key kunnr as Customer,
name1 as CustomerName,
land1 as Country,
/* Exposer l'association (sans Join !) */
_SalesOrders
}

Utilisation :

" Charger uniquement les données Client
SELECT Customer, CustomerName
FROM Z_Customer
INTO TABLE @DATA(lt_customers).
" Avec les commandes (seulement si nécessaire !)
SELECT Customer, CustomerName,
\_SalesOrders-vbeln as OrderNumber
FROM Z_Customer
WHERE Country = 'DE"
INTO TABLE @DATA(lt_customer_orders).

Avantage : Performance ! Les commandes ne sont chargées que si nécessaire.


3. Annotations importantes

@Semantics

Donne une signification sémantique aux champs :

define view Z_Product
as select from mara
{
key matnr as Product,
/* Devise */
@Semantics.amount.currencyCode: 'Currency"
price as Price,
@Semantics.currencyCode: true
waers as Currency,
/* Unité */
@Semantics.quantity.unitOfMeasure: 'Unit"
menge as Quantity,
@Semantics.unitOfMeasure: true
meins as Unit,
/* Date */
@Semantics.businessDate.from: true
valid_from as ValidFrom,
@Semantics.businessDate.to: true
valid_to as ValidTo,
/* Utilisateur */
@Semantics.user.createdBy: true
created_by as CreatedBy
}

@ObjectModel

Pour RAP et UI :

@ObjectModel.representativeKey: 'Product"
@ObjectModel.usageType.serviceQuality: #A
@ObjectModel.usageType.sizeCategory: #M
@ObjectModel.usageType.dataClass: #MASTER
define view Z_Product as select from mara
{
key matnr as Product,
/* Text-Element */
@ObjectModel.text.element: ['ProductName']
matnr as ProductId,
maktx as ProductName
}

@Analytics

Pour les requêtes analytiques :

@Analytics.query: true
@Analytics.dataCategory: #CUBE
define view Z_SalesAnalytics
as select from vbak
{
/* Dimensions */
@Analytics.dimension: true
kunnr as Customer,
@Analytics.dimension: true
vkorg as SalesOrg,
/* Measures (Indicateurs) */
@Aggregation.default: #SUM
@Semantics.amount.currencyCode: 'Currency"
netwr as Revenue,
@Aggregation.default: #COUNT
vbeln as OrderCount,
waerk as Currency
}

4. Paramètres

Paramètres d’entrée pour des vues dynamiques :

define view Z_CustomerByCountry
with parameters
p_country : land1
as select from kna1
{
key kunnr as Customer,
name1 as CustomerName,
land1 as Country
}
where
land1 = :p_country

Utilisation :

SELECT Customer, CustomerName
FROM Z_CustomerByCountry( p_country = 'DE' )
INTO TABLE @DATA(lt_customers).

Avec valeur par défaut :

with parameters
@Environment.systemField: #SYSTEM_DATE
p_date : abap.dats

5. Virtual Elements

Champs calculés (pas dans la DB) :

define view Z_Product
as select from mara
{
key matnr as Product,
/* Champs virtuels */
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_CALC_STOCK"
cast( 0 as abap.int4 ) as AvailableStock,
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_CALC_PRICE"
cast( 0.0 as abap.curr(16,2) ) as CurrentPrice
}

Implémentation :

CLASS zcl_calc_stock DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_sadl_exit_calc_element_read.
ENDCLASS.
CLASS zcl_calc_stock IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~calculate.
" Lire les données
DATA lt_products TYPE STANDARD TABLE OF z_product.
lt_products = CORRESPONDING #( it_original_data ).
" Requête de stock
LOOP AT lt_products ASSIGNING FIELD-SYMBOL(<product>).
SELECT SINGLE labst FROM mard
WHERE matnr = <product>-product
INTO @<product>-availablestock.
ENDLOOP.
" Retourner
ct_calculated_data = CORRESPONDING #( lt_products ).
ENDMETHOD.
ENDCLASS.

6. Access Control (DCL)

Autorisations de données au niveau vue :

Vue CDS :

@AccessControl.authorizationCheck: #CHECK
define view Z_SalesOrder
as select from vbak
{
key vbeln as SalesOrder,
kunnr as Customer,
vkorg as SalesOrg,
netwr as NetValue
}

DCL (Z_SalesOrder.dcls) :

@EndUserText.label: 'Access Control for Sales Orders"
@MappingRole: true
define role Z_SalesOrder {
grant select on Z_SalesOrder
where
/* Uniquement sa propre Sales Org */
(SalesOrg) = aspect pfcg_auth(
V_VBAK_VKO,
VKORG,
ACTVT = '03"
);
}

Résultat : Les utilisateurs ne voient que les commandes de leur SalesOrg !


7. Hiérarchies

Pour les structures Parent-Child :

@Hierarchy.parentChild: [{ name: 'OrgHierarchy',
recurse: {
parent: 'ParentOrg',
child: 'Organization',
depth: 'Level',
orphans: #ERROR
}
}]
define view Z_OrganizationHierarchy
as select from t001
{
key bukrs as Organization,
parent_bukrs as ParentOrg,
butxt as OrganizationName,
/* Calculé automatiquement */
cast( 0 as abap.int1 ) as Level
}

8. Extension des vues CDS

Utiliser d’autres vues comme base :

/* Vue de base */
define view Z_Customer_Basic
as select from kna1
{
key kunnr as Customer,
name1 as CustomerName,
land1 as Country
}
/* Étend la vue de base */
define view Z_Customer_Extended
as select from Z_Customer_Basic
association [0..*] to vbak as _Orders
on $projection.Customer = _Orders.kunnr
{
Customer,
CustomerName,
Country,
/* Champs supplémentaires */
_Orders.vbeln as OrderNumber,
_Orders.netwr as OrderValue,
_Orders
}

9. Vues CDS pour Analytics

Vue Cube

@Analytics.dataCategory: #CUBE
define view Z_SalesCube
as select from vbak
{
@Analytics.dimension: true
vkorg as SalesOrg,
@Analytics.dimension: true
vtweg as DistributionChannel,
@Analytics.dimension: true
audat as OrderDate,
@Aggregation.default: #SUM
netwr as Revenue,
@Aggregation.default: #AVG
netwr as AvgOrderValue,
@Aggregation.default: #COUNT
vbeln as OrderCount
}

Vue Query (consomme le Cube)

@Analytics.query: true
define view Z_SalesQuery
as select from Z_SalesCube
{
@AnalyticsDetails.query.axis: #ROWS
SalesOrg,
@AnalyticsDetails.query.axis: #ROWS
DistributionChannel,
@AnalyticsDetails.query.axis: #COLUMNS
OrderDate,
Revenue,
AvgOrderValue,
OrderCount
}

10. Bonnes pratiques de performance

A FAIRE

1. Pushdown plutôt que logique ABAP :

/* Bien : Calculer dans CDS */
define view Z_Customer
as select from kna1
{
key kunnr,
/* Calcul dans la DB */
case land1
when 'DE' then 'Allemagne"
when 'AT' then 'Autriche"
else 'Autre pays"
end as CountryText
}

2. Utiliser les index :

/* Filtrer sur les champs clés si possible */
where vbeln = :p_sales_order /* Clé, très rapide */

3. Activer le buffering :

@AbapCatalog.buffering.status: #ACTIVE
@AbapCatalog.buffering.type: #FULL
@AbapCatalog.buffering.numberOfKeyFields: 1
define view Z_Country
as select from t005
{
key land1 as Country,
landx as CountryName
}

A EVITER

1. Trop de Joins :

/* Mauvais : 10 Joins */
define view Z_ComplexView
as select from t1
join t2 on ...
join t3 on ...
... /* 10 autres */

Mieux : Plusieurs vues avec Associations

2. SELECT DISTINCT sans raison :

/* Lent */
define view Z_Customer
as select distinct from kna1

3. Unions sans nécessité :

/* Lent si évitable */
define view Z_Combined
as select from tab1
union all
select from tab2

11. Tester les vues CDS

CLASS ltc_cds_test DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS test_customer_view FOR TESTING.
ENDCLASS.
CLASS ltc_cds_test IMPLEMENTATION.
METHOD class_setup.
environment = cl_cds_test_environment=>create(
i_for_entity = 'Z_CUSTOMER"
).
ENDMETHOD.
METHOD class_teardown.
environment->destroy( ).
ENDMETHOD.
METHOD test_customer_view.
" Insérer des données de test
environment->insert_test_data(
i_data = VALUE #(
( customer = '0001' customername = 'Test SA' country = 'DE' )
)
).
" Interroger la vue CDS
SELECT customer, customername
FROM z_customer
WHERE country = 'DE"
INTO TABLE @DATA(lt_result).
" Assertions
cl_abap_unit_assert=>assert_equals(
act = lines( lt_result )
exp = 1
).
cl_abap_unit_assert=>assert_equals(
act = lt_result[ 1 ]-customername
exp = 'Test SA"
).
ENDMETHOD.
ENDCLASS.

Résumé : Aide-mémoire CDS View

FonctionnalitéSyntaxeCas d’utilisation
Vue de basedefine view ... as select fromRequête de données simple
Joininner/left join ... onLier les données
Associationassociation [0..*] toChargement paresseux, Performance
Paramètreswith parameters p_x : typeFiltres dynamiques
Virtual Elements@ObjectModel.virtualElementChamps calculés (ABAP)
Access Control@AccessControl + DCLAutorisations
Hiérarchies@Hierarchy.parentChildStructures organisationnelles
Analytics@Analytics.dataCategory: #CUBEReporting

Ressources supplémentaires

Sur abapcloud.com :

Documentation SAP :

Bonnes pratiques :

  • Utilisez les Associations plutôt que les Joins si possible
  • Pushdown de la logique dans la DB (pas ABAP)
  • Buffering pour les données de base
  • Access Control pour les autorisations
  • Testez vos vues CDS !

Bon courage avec les vues CDS !