CDS Views (Core Data Services) son el corazón del desarrollo ABAP moderno. Este deep dive te muestra todo, desde lo básico hasta Analytics avanzado.
¿Qué son los CDS Views?
Los CDS Views son modelos de datos semánticos definidos en sintaxis similar a SQL que se ejecutan directamente en la base de datos.
Ventajas vs. views clásicas
| Característica | Views clásicas (SE11) | CDS Views |
|---|---|---|
| Sintaxis | ABAP Dictionary | Similar a SQL (DDL) |
| Rendimiento | Bueno | Mejor (optimizado en BD) |
| Anotaciones | Ninguna | Extensas |
| Asociaciones | No | Sí |
| Analytics | Limitado | Extenso |
| Reutilización | Baja | Alta |
1. Básicos de CDS
CDS View más 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}Componentes:
@AbapCatalog.sqlViewName: Nombre del DB-View generado (máx. 16 caracteres)@AccessControl: Verificación de permisosdefine view: Definición del viewas select from: Fuente de datos{ ... }: Lista de campos
Activar view
- En ADT:
Ctrl+F3 - Genera automáticamente DB-View
ZV_CUSTOMER
Usar 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 (¡Recomendado!)
Mejor que Joins: Carga diferida (lazy loading), reutilizable
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,
/* Exponer asociación (¡sin Join!) */ _SalesOrders}Uso:
" Solo cargar datos de CustomerSELECT Customer, CustomerName FROM Z_Customer INTO TABLE @DATA(lt_customers).
" Con Orders (¡solo cuando se necesita!)SELECT Customer, CustomerName, \_SalesOrders-vbeln as OrderNumber FROM Z_Customer WHERE Country = 'DE' INTO TABLE @DATA(lt_customer_orders).Ventaja: ¡Rendimiento! Los pedidos solo se cargan cuando se necesitan.
3. Anotaciones importantes
@Semantics
Da significado semántico a los campos:
define view Z_Product as select from mara{ key matnr as Product,
/* Moneda */ @Semantics.amount.currencyCode: 'Currency' price as Price,
@Semantics.currencyCode: true waers as Currency,
/* Unidad */ @Semantics.quantity.unitOfMeasure: 'Unit' menge as Quantity,
@Semantics.unitOfMeasure: true meins as Unit,
/* Fecha */ @Semantics.businessDate.from: true valid_from as ValidFrom,
@Semantics.businessDate.to: true valid_to as ValidTo,
/* Usuario */ @Semantics.user.createdBy: true created_by as CreatedBy}@ObjectModel
Para 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,
/* Elemento de texto */ @ObjectModel.text.element: ['ProductName'] matnr as ProductId, maktx as ProductName}@Analytics
Para consultas analíticas:
@Analytics.query: true@Analytics.dataCategory: #CUBE
define view Z_SalesAnalytics as select from vbak{ /* Dimensiones */ @Analytics.dimension: true kunnr as Customer,
@Analytics.dimension: true vkorg as SalesOrg,
/* Measures (Indicadores) */ @Aggregation.default: #SUM @Semantics.amount.currencyCode: 'Currency' netwr as Revenue,
@Aggregation.default: #COUNT vbeln as OrderCount,
waerk as Currency}4. Parámetros
Parámetros de entrada para views dinámicos:
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_countryUso:
SELECT Customer, CustomerName FROM Z_CustomerByCountry( p_country = 'DE' ) INTO TABLE @DATA(lt_customers).Con valor por defecto:
with parameters @Environment.systemField: #SYSTEM_DATE p_date : abap.dats5. Virtual Elements
Campos calculados (no en BD):
define view Z_Product as select from mara{ key matnr as Product,
/* Campos virtuales */ @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}Implementación:
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. " Leer datos DATA lt_products TYPE STANDARD TABLE OF z_product. lt_products = CORRESPONDING #( it_original_data ).
" Consulta de stock LOOP AT lt_products ASSIGNING FIELD-SYMBOL(<product>). SELECT SINGLE labst FROM mard WHERE matnr = <product>-product INTO @<product>-availablestock. ENDLOOP.
" Devolver ct_calculated_data = CORRESPONDING #( lt_products ). ENDMETHOD.ENDCLASS.6. Access Control (DCL)
Permisos de datos a nivel de View:
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 /* Solo propia Sales Org */ (SalesOrg) = aspect pfcg_auth( V_VBAK_VKO, VKORG, ACTVT = '03' );}Resultado: ¡Los usuarios solo ven SalesOrders de su SalesOrg!
7. Hierarchies
Para estructuras padre-hijo:
@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,
/* Se calcula automáticamente */ cast( 0 as abap.int1 ) as Level}8. Extender CDS Views
Usar otros views como base:
/* View base */define view Z_Customer_Basic as select from kna1{ key kunnr as Customer, name1 as CustomerName, land1 as Country}
/* Extiende view 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,
/* Campos adicionales */ _Orders.vbeln as OrderNumber, _Orders.netwr as OrderValue,
_Orders}9. CDS Views para 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 (consume 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. Mejores prácticas de rendimiento
HACER
1. Pushdown en lugar de lógica ABAP:
/* Bueno: Calcular en CDS */define view Z_Customer as select from kna1{ key kunnr,
/* Cálculo en BD */ case land1 when 'DE' then 'Alemania' when 'AT' then 'Austria' else 'Otro país' end as CountryText}2. Usar índices:
/* Donde sea posible filtrar por campos clave */where vbeln = :p_sales_order /* Clave, muy rápido */3. Activar 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}NO HACER
1. Demasiados Joins:
/* Malo: 10 Joins */define view Z_ComplexView as select from t1 join t2 on ... join t3 on ... ... /* 10 más */Mejor: Múltiples views con Associations
2. SELECT DISTINCT sin razón:
/* Lento */define view Z_Customer as select distinct from kna13. Unions sin necesidad:
/* Lento si evitable */define view Z_Combined as select from tab1 union all select from tab211. Testing de 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. " Insertar datos de prueba environment->insert_test_data( i_data = VALUE #( ( customer = '0001' customername = 'Test AG' country = 'DE' ) ) ).
" Consultar 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.Resumen: CDS View Cheat Sheet
| Característica | Sintaxis | Caso de uso |
|---|---|---|
| View básico | define view ... as select from | Consulta de datos simple |
| Join | inner/left join ... on | Vincular datos |
| Association | association [0..*] to | Carga diferida, rendimiento |
| Parámetros | with parameters p_x : type | Filtros dinámicos |
| Virtual Elements | @ObjectModel.virtualElement | Campos calculados (ABAP) |
| Access Control | @AccessControl + DCL | Permisos |
| Hierarchies | @Hierarchy.parentChild | Estructuras organizativas |
| Analytics | @Analytics.dataCategory: #CUBE | Reporting |
Recursos adicionales
En abapcloud.com:
Documentación SAP:
Mejores prácticas:
- Usa Associations en lugar de Joins donde sea posible
- Pushdown de lógica a BD (no ABAP)
- Buffering para datos maestros
- Access Control para permisos
- ¡Testea tus CDS Views!
¡Mucho éxito con CDS Views!