CDS Views (Core Data Services) are the heart of modern ABAP development. This deep dive shows you everything from basics to advanced analytics.
What are CDS Views?
CDS Views are semantic data models defined in SQL-like syntax that run directly on the database.
Advantages vs. Classic Views
| Feature | Classic Views (SE11) | CDS Views |
|---|---|---|
| Syntax | ABAP Dictionary | SQL-like (DDL) |
| Performance | Good | Better (DB-optimized) |
| Annotations | None | Extensive |
| Associations | No | Yes |
| Analytics | Limited | Extensive |
| Reusability | Low | High |
1. CDS Basics
Simplest CDS View
@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}Components:
@AbapCatalog.sqlViewName: Name of the generated DB view (max. 16 characters)@AccessControl: Authorization checkdefine view: View definitionas select from: Data source{ ... }: Field list
Activate View
- In ADT:
Ctrl+F3 - Automatically generates DB view
ZV_CUSTOMER
Use View
SELECT Customer, CustomerName, Country FROM Z_Customer WHERE Country = 'DE' INTO TABLE @DATA(lt_customers).2. Joins & 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 (Recommended!)
Better than Joins: Lazy loading, reusable
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,
/* Expose association (without join!) */ _SalesOrders}Usage:
" Load only customer dataSELECT Customer, CustomerName FROM Z_Customer INTO TABLE @DATA(lt_customers).
" With orders (only when needed!)SELECT Customer, CustomerName, \_SalesOrders-vbeln as OrderNumber FROM Z_Customer WHERE Country = 'DE' INTO TABLE @DATA(lt_customer_orders).Advantage: Performance! Orders only loaded when needed.
3. Important Annotations
@Semantics
Gives fields semantic meaning:
define view Z_Product as select from mara{ key matnr as Product,
/* Currency */ @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,
/* User */ @Semantics.user.createdBy: true created_by as CreatedBy}@ObjectModel
For RAP & 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
For analytical queries:
@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 (KPIs) */ @Aggregation.default: #SUM @Semantics.amount.currencyCode: 'Currency' netwr as Revenue,
@Aggregation.default: #COUNT vbeln as OrderCount,
waerk as Currency}4. Parameters
Input parameters for dynamic views:
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_countryUsage:
SELECT Customer, CustomerName FROM Z_CustomerByCountry( p_country = 'DE' ) INTO TABLE @DATA(lt_customers).With default value:
with parameters @Environment.systemField: #SYSTEM_DATE p_date : abap.dats5. Virtual Elements
Calculated fields (not in DB):
define view Z_Product as select from mara{ key matnr as Product,
/* Virtual fields */ @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}Implementation:
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. " Read data DATA lt_products TYPE STANDARD TABLE OF z_product. lt_products = CORRESPONDING #( it_original_data ).
" Stock query LOOP AT lt_products ASSIGNING FIELD-SYMBOL(<product>). SELECT SINGLE labst FROM mard WHERE matnr = <product>-product INTO @<product>-availablestock. ENDLOOP.
" Return ct_calculated_data = CORRESPONDING #( lt_products ). ENDMETHOD.ENDCLASS.6. Access Control (DCL)
Data authorizations at view level:
CDS View:
@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 /* Only own sales org */ (SalesOrg) = aspect pfcg_auth( V_VBAK_VKO, VKORG, ACTVT = '03' );}Result: Users only see SalesOrders from their SalesOrg!
7. Hierarchies
For parent-child structures:
@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,
/* Calculated automatically */ cast( 0 as abap.int1 ) as Level}8. Extending CDS Views
Using other views as basis:
/* Base view */define view Z_Customer_Basic as select from kna1{ key kunnr as Customer, name1 as CustomerName, land1 as Country}
/* Extends base view */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,
/* Additional fields */ _Orders.vbeln as OrderNumber, _Orders.netwr as OrderValue,
_Orders}9. CDS Views for Analytics
Cube View
@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}Query View (Consumes 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. Performance Best Practices
DO
1. Pushdown instead of ABAP logic:
/* Good: Calculate in CDS */define view Z_Customer as select from kna1{ key kunnr,
/* Calculation in DB */ case land1 when 'DE' then 'Germany' when 'AT' then 'Austria' else 'Other Country' end as CountryText}2. Use indexes:
/* Filter on key fields where possible */where vbeln = :p_sales_order /* Key, very fast */3. Activate 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}DON’T
1. Too many joins:
/* Bad: 10 joins */define view Z_ComplexView as select from t1 join t2 on ... join t3 on ... ... /* 10 more */Better: Multiple views with associations
2. SELECT DISTINCT without reason:
/* Slow */define view Z_Customer as select distinct from kna13. Unions without necessity:
/* Slow if avoidable */define view Z_Combined as select from tab1 union all select from tab211. Testing CDS Views
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. " Insert test data environment->insert_test_data( i_data = VALUE #( ( customer = '0001' customername = 'Test AG' country = 'DE' ) ) ).
" Query CDS view 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 AG' ). ENDMETHOD.ENDCLASS.Summary: CDS View Cheat Sheet
| Feature | Syntax | Use Case |
|---|---|---|
| Basic View | define view ... as select from | Simple data query |
| Join | inner/left join ... on | Link data |
| Association | association [0..*] to | Lazy loading, performance |
| Parameters | with parameters p_x : type | Dynamic filters |
| Virtual Elements | @ObjectModel.virtualElement | Calculated fields (ABAP) |
| Access Control | @AccessControl + DCL | Authorizations |
| Hierarchies | @Hierarchy.parentChild | Org structures |
| Analytics | @Analytics.dataCategory: #CUBE | Reporting |
Further Resources
On abapcloud.com:
SAP Documentation:
Best Practices:
- Use associations instead of joins where possible
- Pushdown logic to DB (not ABAP)
- Buffering for master data
- Access Control for authorizations
- Test your CDS views!
Good luck with CDS Views!