Analytical variable in ABAP CDS query and restricted measures

This blog will demonstrate how to create analytical variable in ABAP CDS and leverage to easily create dimension- restricted / time-restricted measures.

Scenerio

An ABAP CDS query is created based on I_GLAccountLineItemCube that exposes financial documents. Two measures are required in this query:

  • Year-to-date amount in company code currency(time restricted)
  • Balance Sheet amount in company code currency(dimension restricted)

To start with the exercise, you must create the ABAP CDS query below.

@EndUserText.label: 'Test Query to analytical expression'
@VDM.viewType: #CONSUMPTION
@Analytics.query: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.ignorePropagatedAnnotations: true
define view entity YTEST_QUERY
  with parameters

    @Consumption.defaultValue: 'K4'
    @Consumption.hidden: true
    P_FiscalYearVariant : fc_periv

  as select from I_GLAccountLineItemCube as I_GLAccountLineItemCube
{

  @Consumption.filter: {selectionType: #SINGLE, multipleSelections: false, mandatory: true}
  @Consumption.derivation: { lookupEntity: 'I_Ledger',
        resultElement: 'Ledger', binding: [
        { targetElement : 'IsLeadingLedger' , type : #CONSTANT, value : 'X' } ]
       }
  @AnalyticsDetails.query.variableSequence : 10
  @AnalyticsDetails.query.axis: #FREE
  @AnalyticsDetails.query.display: #KEY_TEXT
  Ledger,

  @Consumption.filter: {selectionType: #SINGLE, multipleSelections: true, mandatory: true}
  @AnalyticsDetails.query.variableSequence : 20
  @AnalyticsDetails.query.axis: #ROWS
  @AnalyticsDetails.query.totals: #SHOW
  @AnalyticsDetails.query.display: #KEY_TEXT
  CompanyCode,

  @Consumption.filter: { selectionType: #SINGLE, multipleSelections: true, mandatory: false }
  @AnalyticsDetails.query.variableSequence: 30
  @AnalyticsDetails.query.axis: #ROWS
  @AnalyticsDetails.query.totals: #SHOW
  @AnalyticsDetails.query.display: #KEY_TEXT
  GLAccount,

  @AnalyticsDetails.query.axis: #FREE
  @AnalyticsDetails.query.display: #KEY_TEXT
  ProfitCenter,

  @AnalyticsDetails.query.axis: #FREE
  PostingDate,

  @AnalyticsDetails.query.axis: #FREE
  @Consumption.filter: { selectionType: #RANGE, multipleSelections: true, mandatory: false }
  AccountingDocument,

  ///////////////////////////////////////////////////////////////////////
  // Measures - Amounts
  ///////////////////////////////////////////////////////////////////////
  @AnalyticsDetails.query.axis: #FREE
  CompanyCodeCurrency,

  @Semantics.amount.currencyCode: 'CompanyCodeCurrency'
  @AnalyticsDetails.query.axis: #COLUMNS
  @DefaultAggregation: #SUM
  AmountInCompanyCodeCurrency

};


Year-to-date amount in company code currency(time restricted)

Create Year-to-date range in CDS view

Create a new CDS entity view as below, which extracts year to date range based on the current system date. I_FiscalCalendarDtePrevPeriods contains year-to-date start date(FiscalYearStartDate ) and year-to-date end date(FiscalYearEndDate), depending on the base date field ‘CalendarDate’.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.representativeKey: 'FiscalYearVariant'
@ObjectModel: {
  supportedCapabilities: [#DERIVATION_FUNCTION],
  modelingPattern: #DERIVATION_FUNCTION
  }
@ObjectModel.derivationFunction: {
  applicableFor.dataType:#DATS,
  result: {
    type: #INTERVAL,
    multipleRecords: false,
    element: 'StartDate',
    elementHigh: 'EndDate'
  }
}
@VDM.viewType: #DERIVATION_FUNCTION
@EndUserText.label: 'YTD Range'
define view entity Y_YTD_RANGE
  with parameters

    P_FiscalYearVariant : fis_periv

  as select from I_FiscalCalendarDtePrevPeriods
{

  key CalendarDate,
  key FiscalYearVariant,
      FiscalYearStartDate as StartDate,
      FiscalYearEndDate   as EndDate
}

where
      FiscalYearVariant = $parameters.P_FiscalYearVariant
  and CalendarDate      = $session.system_date

When you preview the data with current date of 27.06.2024, the year-to-date start/end date are correctly mapped.

Create analytical variable and Year-to-date restricted measure

In the ABAP CDS query YTEST_QUERY, add below code as paramter which uses the year-to-date range CDS we just created. This is a variable contianing the interval of year-to-date, therefore it requires two elements(StartDate and EndDate) from Y_YTD_RANGE. We are also passing Fiscal Year Variant, as it is a key field to determine year to date start and end. As a reference element, PostingDate is refered as this is the dimension we will filter with this variable.

    @Consumption.hidden :true
    @AnalyticsDetails.variable: { usageType: #FILTER, referenceElement: 'PostingDate', mandatory: true, selectionType: #INTERVAL, multipleSelections: false }
    @Consumption.derivation: {
       lookupEntity: 'Y_YTD_RANGE',
       resultElement: 'StartDate',
       resultElementHigh: 'EndDate',
       binding : [ { targetParameter : 'P_FiscalYearVariant', type: #PARAMETER, value: 'P_FiscalYearVariant' } ] }

    P_YTD_RANGE         : fis_budat

Now let’s create the restricted measure that only shows the amount if the pointg date is within the range of Year-to-date. Add below code as query fields.

The query field ‘PostingDate’ is filtered with variable range of Year-to-date and ‘AmountInCompanyCodeCurrency’ that meets this condition will be aggregated with annotation ‘@Aggregation.default: #SUM’.

  @AnalyticsDetails.query.axis: #COLUMNS
  @Semantics.amount.currencyCode: 'CompanyCodeCurrency'
  @Aggregation.default: #SUM
  @EndUserText.label: 'CC Amount(restricted to YTD)'
  case
    when PostingDate      = $parameters.P_YTD_RANGE
    then curr_to_decfloat_amount( AmountInCompanyCodeCurrency )
  end as AmountCompanyCodeYTD

Activate all the objects and preview the query in tcode RSRT with WD Grid. You can see that CC amount (restricted to YTD) is displayed. It is a sum of current year postings for each GL account.


Balance Sheet amount in company code currency(dimension restricted)

Now with the same principle, create another CDS view that will contain only Balance Sheet account. This is achived by filtering ska1-xbilk(Balance Sheet flag).

@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.representativeKey: 'saknr'
@ObjectModel: {
  supportedCapabilities: [#DERIVATION_FUNCTION],
  modelingPattern: #DERIVATION_FUNCTION
  }
@VDM.viewType: #DERIVATION_FUNCTION
@EndUserText.label: 'Balance Sheet Account'
define view entity Y_BSAccount

  as select from ska1
{
  key ktopl,
  key saknr
}

where
      ktopl = 'CHVS' //chart of account
  and xbilk = 'X' //BalanceSheet account

Now go to YTEST_QUERY and add another analytical variable using Y_BSAccount. This time it is not time range but rather list of single values therefore selection Type = SINGLE and multipleSelections = true.

    @Consumption.hidden :true
    @AnalyticsDetails.variable: { usageType: #FILTER, referenceElement: 'GLAccount', mandatory: true, selectionType: #SINGLE, multipleSelections: true }
    @Consumption.derivation: {
       lookupEntity: 'Y_BSAccount',
       resultElement: 'saknr' }

    P_BS_ACCOUNT        : saknr,

WIth the same principle, add another restricted measure using variable P_BS_ACCOUNT.

  @AnalyticsDetails.query.axis: #COLUMNS
  @Semantics.amount.currencyCode: 'CompanyCodeCurrency'
  @Aggregation.default: #SUM
  @EndUserText.label: 'CC Amount(restricted to Balance Sheet)'
  case
    when GLAccount      = $parameters.P_BS_ACCOUNT
    then curr_to_decfloat_amount( AmountInCompanyCodeCurrency )
  end as AmountCompanyCodeBS  

Now the whole code looks this in CDS query.

@EndUserText.label: 'Test Query to analytical expression'
@VDM.viewType: #CONSUMPTION
@Analytics.query: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.ignorePropagatedAnnotations: true
define view entity YTEST_QUERY
  with parameters

    @Consumption.defaultValue: 'K4'
    @Consumption.hidden: true
    P_FiscalYearVariant : fc_periv,

    @Consumption.hidden :true
    @AnalyticsDetails.variable: { usageType: #FILTER, referenceElement: 'GLAccount', mandatory: true, selectionType: #SINGLE, multipleSelections: true }
    @Consumption.derivation: {
       lookupEntity: 'Y_BSAccount',
       resultElement: 'saknr' }

    P_BS_ACCOUNT        : saknr,

    @Consumption.hidden :true
    @AnalyticsDetails.variable: { usageType: #FILTER, referenceElement: 'PostingDate', mandatory: true, selectionType: #INTERVAL, multipleSelections: false }
    @Consumption.derivation: {
       lookupEntity: 'Y_YTD_RANGE',
       resultElement: 'StartDate',
       resultElementHigh: 'EndDate',
       binding : [ { targetParameter : 'P_FiscalYearVariant', type: #PARAMETER, value: 'P_FiscalYearVariant' } ] }

    P_YTD_RANGE         : fis_budat

  as select from I_GLAccountLineItemCube as I_GLAccountLineItemCube
{

  @Consumption.filter: {selectionType: #SINGLE, multipleSelections: false, mandatory: true}
  @Consumption.derivation: { lookupEntity: 'I_Ledger',
        resultElement: 'Ledger', binding: [
        { targetElement : 'IsLeadingLedger' , type : #CONSTANT, value : 'X' } ]
       }
  @AnalyticsDetails.query.variableSequence : 10
  @AnalyticsDetails.query.axis: #FREE
  @AnalyticsDetails.query.display: #KEY_TEXT
  Ledger,

  @Consumption.filter: {selectionType: #SINGLE, multipleSelections: true, mandatory: true}
  @AnalyticsDetails.query.variableSequence : 20
  @AnalyticsDetails.query.axis: #ROWS
  @AnalyticsDetails.query.totals: #SHOW
  @AnalyticsDetails.query.display: #KEY_TEXT
  CompanyCode,

  @Consumption.filter: { selectionType: #SINGLE, multipleSelections: true, mandatory: false }
  @AnalyticsDetails.query.variableSequence: 30
  @AnalyticsDetails.query.axis: #ROWS
  @AnalyticsDetails.query.totals: #SHOW
  @AnalyticsDetails.query.display: #KEY_TEXT
  GLAccount,

  @AnalyticsDetails.query.axis: #FREE
  @AnalyticsDetails.query.display: #KEY_TEXT
  ProfitCenter,

  @AnalyticsDetails.query.axis: #FREE
  PostingDate,

  @AnalyticsDetails.query.axis: #FREE
  @Consumption.filter: { selectionType: #RANGE, multipleSelections: true, mandatory: false }
  AccountingDocument,

  ///////////////////////////////////////////////////////////////////////
  // Measures - Amounts
  ///////////////////////////////////////////////////////////////////////
  @AnalyticsDetails.query.axis: #FREE
  CompanyCodeCurrency,

  @Semantics.amount.currencyCode: 'CompanyCodeCurrency'
  @AnalyticsDetails.query.axis: #COLUMNS
  @DefaultAggregation: #SUM
  AmountInCompanyCodeCurrency,

  @AnalyticsDetails.query.axis: #COLUMNS
  @Semantics.amount.currencyCode: 'CompanyCodeCurrency'
  @Aggregation.default: #SUM
  @EndUserText.label: 'CC Amount(restricted to YTD)'
  case
    when PostingDate      = $parameters.P_YTD_RANGE
    then curr_to_decfloat_amount( AmountInCompanyCodeCurrency )
  end as AmountCompanyCodeYTD,
  
  @AnalyticsDetails.query.axis: #COLUMNS
  @Semantics.amount.currencyCode: 'CompanyCodeCurrency'
  @Aggregation.default: #SUM
  @EndUserText.label: 'CC Amount(restricted to Balance Sheet)'
  case
    when GLAccount      = $parameters.P_BS_ACCOUNT
    then curr_to_decfloat_amount( AmountInCompanyCodeCurrency )
  end as AmountCompanyCodeBS  
};

You will see that two restricted measures are add in the report. For ‘CC Amount(restricted to Balance Sheet)’, no value is shown if the GL account is not Balance Sheet.