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 |
|---|---|---|
| Syntaxe | ABAP Dictionary | SQL-like (DDL) |
| Performance | Bonne | Meilleure (optimisée DB) |
| Annotations | Aucune | Complètes |
| Associations | Non | Oui |
| Analytics | Limitées | Complè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 autorisationsdefine view: Définition de la vueas 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 ClientSELECT 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_countryUtilisation :
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.dats5. 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: #CHECKdefine 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 kna13. Unions sans nécessité :
/* Lent si évitable */define view Z_Combined as select from tab1 union all select from tab211. 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é | Syntaxe | Cas d’utilisation |
|---|---|---|
| Vue de base | define view ... as select from | Requête de données simple |
| Join | inner/left join ... on | Lier les données |
| Association | association [0..*] to | Chargement paresseux, Performance |
| Paramètres | with parameters p_x : type | Filtres dynamiques |
| Virtual Elements | @ObjectModel.virtualElement | Champs calculés (ABAP) |
| Access Control | @AccessControl + DCL | Autorisations |
| Hiérarchies | @Hierarchy.parentChild | Structures organisationnelles |
| Analytics | @Analytics.dataCategory: #CUBE | Reporting |
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 !