CDS Views Deep Dive: From Basic to Analytics (2025)

Category
Deep Dive
Published
Author
Johannes

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

FeatureClassic Views (SE11)CDS Views
SyntaxABAP DictionarySQL-like (DDL)
PerformanceGoodBetter (DB-optimized)
AnnotationsNoneExtensive
AssociationsNoYes
AnalyticsLimitedExtensive
ReusabilityLowHigh

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 check
  • define view: View definition
  • as 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
}

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 data
SELECT 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_country

Usage:

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.dats

5. 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: #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
/* 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 kna1

3. Unions without necessity:

/* Slow if avoidable */
define view Z_Combined
as select from tab1
union all
select from tab2

11. 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

FeatureSyntaxUse Case
Basic Viewdefine view ... as select fromSimple data query
Joininner/left join ... onLink data
Associationassociation [0..*] toLazy loading, performance
Parameterswith parameters p_x : typeDynamic filters
Virtual Elements@ObjectModel.virtualElementCalculated fields (ABAP)
Access Control@AccessControl + DCLAuthorizations
Hierarchies@Hierarchy.parentChildOrg structures
Analytics@Analytics.dataCategory: #CUBEReporting

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!