Calculated Fields, CASE, CAST, and SQL Functions in CDS
Learn to create calculated fields in CDS views using CASE expressions, CAST type conversions, and built-in SQL functions for real-world data transformations.
Calculated Fields, CASE, CAST, and SQL Functions
What You'll Learn
- How to create calculated/derived fields in CDS views
- CASE expressions for conditional logic
- CAST for type conversion
- Built-in SQL functions (string, date, numeric)
- Aggregation with GROUP BY
Why Calculated Fields?
Raw database data rarely matches business needs. TechMart stores order status as a single character (N, P, C, X), but users want to see "New", "In Process", "Completed", "Cancelled". Classic ABAP handled this in the application layer. In CDS, you handle it in the view — on the database.
CASE Expressions
DEFINE VIEW ENTITY ZI_TM_SalesOrder
AS SELECT FROM ztm_salesorder
{
key order_id AS OrderId,
customer_id AS CustomerId,
order_date AS OrderDate,
status AS StatusCode,
-- Translate status code to readable text
CASE status
WHEN 'N' THEN 'New'
WHEN 'P' THEN 'In Process'
WHEN 'C' THEN 'Completed'
WHEN 'X' THEN 'Cancelled'
ELSE 'Unknown'
END AS StatusText,
-- Categorize by amount
CASE
WHEN amount > 10000 THEN 'Enterprise'
WHEN amount > 1000 THEN 'Premium'
WHEN amount > 100 THEN 'Standard'
ELSE 'Basic'
END AS OrderTier,
amount AS Amount,
currency AS Currency
}
Expected Output (Data Preview)
| OrderId | StatusCode | StatusText | OrderTier | Amount |
|---------|------------|-------------|------------|----------|
| 1001 | N | New | Premium | 5499.99 |
| 1002 | C | Completed | Enterprise | 24000.00 |
| 1003 | P | In Process | Standard | 299.50 |
CAST — Type Conversion
CDS is strict about types. When mixing types or exposing fields to OData, you often need explicit conversion:
{
-- Convert integer to decimal for calculation
CAST( stock_quantity AS abap.dec(10,2) ) AS StockDecimal,
-- Convert amount for display
CAST( amount AS abap.curr(15,2) ) AS FormattedAmount,
-- Convert date to timestamp type
CAST( order_date AS abap.dats ) AS OrderDate,
-- String length control
CAST( name AS abap.char(50) ) AS ShortName
}
Common types in CDS CAST:
abap.char(n)— character stringabap.int4— 4-byte integerabap.dec(p,s)— packed decimalabap.curr(p,s)— currency amountabap.dats— date (YYYYMMDD)abap.string— variable-length string
Built-in SQL Functions
String Functions
{
-- Concatenate first and last name
CONCAT( first_name, CONCAT( ' ', last_name ) ) AS FullName,
-- Alternative: concat_with_space
CONCAT_WITH_SPACE( first_name, last_name, 1 ) AS FullName2,
-- Substring
SUBSTRING( product_id, 1, 3 ) AS ProductPrefix,
-- Length
LENGTH( name ) AS NameLength,
-- Upper/lower case
UPPER( category ) AS CategoryUpper,
LOWER( name ) AS NameLower,
-- Replace
REPLACE( description, 'old_text', 'new_text' ) AS CleanDescription,
-- Left pad (e.g., pad order numbers with zeros)
LPAD( order_id, 10, '0' ) AS PaddedOrderId
}
Date Functions
{
-- Extract year, month, day
SUBSTRING( order_date, 1, 4 ) AS OrderYear,
SUBSTRING( order_date, 5, 2 ) AS OrderMonth,
-- Days between dates
DATS_DAYS_BETWEEN( order_date, delivery_date ) AS LeadTimeDays,
-- Add days to a date
DATS_ADD_DAYS( order_date, 30 ) AS PaymentDueDate,
-- Current date
$session.system_date AS TodayDate
}
Numeric Functions
{
-- Arithmetic
quantity * price AS LineTotal,
amount * CAST( '0.18' AS abap.dec(3,2) ) AS TaxAmount,
-- Rounding
ROUND( amount * CAST('1.18' AS abap.dec(3,2)), 2 ) AS TotalWithTax,
-- Absolute value, ceiling, floor
ABS( balance ) AS AbsoluteBalance,
CEIL( average_rating ) AS RoundedUpRating,
FLOOR( discount_percent ) AS RoundedDownDiscount,
-- Division (careful with zero)
DIVISION( total_amount, quantity, 2 ) AS AveragePrice
}
Aggregation with GROUP BY
DEFINE VIEW ENTITY ZI_TM_SalesByCategory
AS SELECT FROM ztm_salesorder AS so
INNER JOIN ztm_soitem AS item
ON so.order_id = item.order_id
INNER JOIN ztm_product AS prod
ON item.product_id = prod.product_id
{
key prod.category AS Category,
COUNT(*) AS OrderCount,
SUM( item.quantity * item.price ) AS TotalRevenue,
AVG( item.price ) AS AveragePrice,
MIN( item.price ) AS MinPrice,
MAX( item.price ) AS MaxPrice
}
GROUP BY prod.category
Expected Output
| Category | OrderCount | TotalRevenue | AveragePrice | MinPrice | MaxPrice |
|-------------|------------|--------------|--------------|----------|----------|
| ELECTRONICS | 342 | 156780.50 | 89.99 | 29.99 | 499.99 |
| GADGETS | 128 | 45230.00 | 153.50 | 49.99 | 399.99 |
Common Mistakes
- CAST to wrong type —
CAST(amount AS abap.char(10))will truncate decimals. Use the appropriate type for the data. - Division by zero —
DIVISION(a, b, 2)handles zero safely, but plaina / bcrashes. Always useDIVISION()or add a CASE check. - CASE without ELSE — If no WHEN matches and there's no ELSE, the result is NULL. Always include ELSE.
- Mixing aggregated and non-aggregated fields — If you use COUNT/SUM/AVG, every non-aggregated field must be in GROUP BY.
- Calculated field performance — Complex calculations in CDS push to the database, which is good. But string manipulation on millions of rows can still be slow. Keep calculations targeted.
Key Takeaways
- CDS calculated fields transform data on the database — no ABAP processing needed.
- CASE expressions replace IF-ELSE logic for field derivation.
- CAST is mandatory when mixing types or exposing data through OData.
- SQL functions (CONCAT, SUBSTRING, DATS_DAYS_BETWEEN, DIVISION) handle common transformations.
- Aggregation views with GROUP BY replace Classic ABAP COLLECT patterns.
Next Lesson
Single entities are useful, but business data has hierarchy. In Lesson 10, we'll define compositions — the parent-child relationships (Sales Order → Line Items) that become the backbone of RAP business objects.