# Generate Cds Analytical Query

> Generate an analytical CDS query (transient projection view with PROVIDER CONTRACT ANALYTICAL_QUERY) on top of an existing analytical cube. Use when asked to "create an analytical query", "build a KPI query on a cube", "generate an ANALYTICAL_QUERY view", or "expose a cube for analytics/embedded analytics".

- Skill: `arc-mcp/generate-cds-analytical-query` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arc-mcp/generate-cds-analytical-query`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arc-mcp/generate-cds-analytical-query/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: arc-mcp (https://skillmd.com/u/arc-mcp)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/arc-mcp/generate-cds-analytical-query

---


# Generate CDS Analytical Query

Generate an analytical CDS query — a **transient projection view** with `provider contract analytical_query` — on top of an existing analytical cube (`@Analytics.dataCategory: #CUBE`).

This skill replicates SAP Joule's "CDS Analytical Query Generation" capability by combining ARC-1 (SAP system access) with mcp-sap-docs (documentation & best practices). The query view is what end users consume in analytical clients (SAP Analytics Cloud, Analysis for Office, embedded analytics) — it selects measures and dimensions from a cube and arranges them on rows/columns/free axes.

## Prerequisites — read this first

- **Requires SAP_BASIS 7.57+ (S/4HANA 2022+).** The `provider contract analytical_query` form is unavailable on 7.56 and earlier. Note that `SAPManage(action="probe")` `rap.available` is **necessary but not sufficient** — RAP shipped in 7.54, so a `true` does not confirm the 7.57 floor. To gate reliably, check the release directly, e.g. `SAPQuery(sql="SELECT release FROM cvers WHERE component = 'SAP_BASIS'")` and require `>= 757`; otherwise be ready for the provider-contract syntax error to surface at activation (Step 5) and stop there.
- **You need an existing cube.** This skill projects ON a cube. If no cube exists yet, run the `generate-analytics-star-schema` skill first to build the cube + dimensions, then come back here.

## Smart Defaults (apply silently, do NOT ask)

| Setting | Default | Rationale |
|---|---|---|
| Object type | `DDLS` | Analytical queries are CDS data definitions |
| Package | `$TMP` | Fast prototyping; ask before a transportable package |
| Authorization | `#NOT_ALLOWED` | **Mandatory** on analytical queries — any other value fails activation |
| Axis layout | Dimensions → `#ROWS`, measures → `#COLUMNS` | Sensible default grid; user can rearrange |
| ATC | No | Only run if user asks about code quality |

## Input

The user provides a cube name (e.g., `ZI_Sales_Cube`) or a business description ("revenue by region and month"). Only the **cube** is strictly required; if the user gives a description, find the cube via Step 1.

Optionally:
- **Query name** (default: derive from the cube, e.g. `ZC_SalesQuery`; **must be ≤ 28 characters** — the analytical engine prepends `2C` to the runtime name)
- **Package** (default `$TMP`)
- **Measures / dimensions to expose** (default: all measures + key dimensions from the cube)

## Step 1: Resolve and read the cube

### 1a. Find the cube (if the user gave a description, not a name)

```
SAPSearch(query="<keyword>*", searchType="object", objectType="DDLS")
```

Pick the candidate whose name/description matches an analytical cube. If multiple match, list them and ask.

### 1b. Read the cube source and confirm it IS a cube

```
SAPRead(type="DDLS", name="<cube>")
```

**Verify** the source contains `@Analytics.dataCategory: #CUBE` (or `@Analytics: { dataCategory: #CUBE ... }`). If it does NOT, stop: an analytical query can only project on a cube or a dimension. Suggest `generate-analytics-star-schema` to build a cube first.

### 1c. Get the structured element list

```
SAPRead(type="DDLS", name="<cube>", include="elements")
```

This returns every field with key markers and types. Use it to classify each element:
- **Measures** — numeric fields carrying `@Aggregation.default` (e.g. `#SUM`) in the cube source
- **Dimensions** — key fields and foreign-key-association fields
- **Units/currencies** — fields referenced by `@Semantics.amount.currencyCode` / `@Semantics.quantity.unitOfMeasure`

## Step 2: Look up current analytical-query syntax

Ground the generation in current docs — do NOT rely on memory for annotation names:

```
search("CDS analytical query provider contract analytical_query transient view")
search("AnalyticsDetails.query.axis ROWS COLUMNS FREE")
```

Cite the doc IDs you used in your final summary. Key facts the docs confirm (7.58):
- Header form: `define transient view entity <name> provider contract analytical_query as projection on <cube>`
- `@AccessControl.authorizationCheck: #NOT_ALLOWED` is **mandatory** (queries can't be read via ABAP SQL → no DCL allowed; the cube's access control applies)
- The `transient` keyword is mandatory (no HANA SQL view is generated)
- You cannot define new associations or use `KEY` in a query view
- A calculated measure (arithmetic / CASE-on-a-measure) needs `@Aggregation.default: #FORMULA`

## Step 3: Compose the analytical query

Pick the template that matches the request.

### Template A — simple projection (1+ dimensions on rows, 1+ measures on columns)

```abap
@EndUserText.label: 'Sales Analytical Query'
@AccessControl.authorizationCheck: #NOT_ALLOWED
define transient view entity ZC_SalesQuery
  provider contract analytical_query
  as projection on ZI_Sales_Cube
{
      @AnalyticsDetails.query.axis: #ROWS
  RegionId,

      @AnalyticsDetails.query.axis: #FREE
  CalendarYear,

      @AnalyticsDetails.query.axis: #COLUMNS
      @Semantics.amount.currencyCode: 'CurrencyCode'
  SalesAmount,

  CurrencyCode
}
```

### Template B — restricted + calculated measures

```abap
@EndUserText.label: 'Sales KPI Query'
@AccessControl.authorizationCheck: #NOT_ALLOWED
define transient view entity ZC_SalesKpiQuery
  provider contract analytical_query
  as projection on ZI_Sales_Cube
{
      @AnalyticsDetails.query.axis: #ROWS
  RegionId,

      // restricted measure: selection-related CASE — WHEN must reference a DIMENSION
      @Semantics.amount.currencyCode: 'CurrencyCode'
  case when CalendarYear = '2025' then SalesAmount else null end as SalesAmount2025,

      // calculated measure: formula CASE — needs @Aggregation.default: #FORMULA
      @Aggregation.default: #FORMULA
  case when SalesAmount is initial then abap.int8'0' else abap.int8'1' end as HasSales,

  CurrencyCode
}
```

### Template C — query with input parameters (filter prompt)

```abap
@EndUserText.label: 'Sales Query (parameterized)'
@AccessControl.authorizationCheck: #NOT_ALLOWED
define transient view entity ZC_SalesParamQuery
  provider contract analytical_query
  with parameters
    p_year : abap.numc(4)
  as projection on ZI_Sales_Cube
{
      @AnalyticsDetails.query.axis: #ROWS
  RegionId,
      @Semantics.amount.currencyCode: 'CurrencyCode'
  SalesAmount,
  CurrencyCode
}
where CalendarYear = $parameters.p_year
```

**Composition rules** (enforce before writing):
- Every element you **project directly** from the cube must use the cube's exact element name (you read them in Step 1c). Calculated and restricted measures (Template B) are the exception — they introduce new aliases (`... as SalesAmount2025`) that need not exist in the cube.
- Every amount measure needs a `@Semantics.amount.currencyCode: '<field>'` and that currency field must also be projected.
- Every quantity measure needs `@Semantics.quantity.unitOfMeasure: '<field>'` and that unit field must be projected.
- Query name ≤ 28 chars.

## Step 4: Show the plan, then write

Show the user the composed DDL and the axis layout (which fields are rows / columns / free) before writing. On confirmation:

```
SAPWrite(action="create", type="DDLS", name="<query>", source="<ddl>", package="$TMP")
```

For a transportable package, pass `package="<pkg>"` and `transport="<TR>"` (the user must supply the TR; create one with `SAPTransport` if needed).

## Step 5: Activate

```
SAPActivate(name="<query>", type="DDLS")
```

If activation fails, surface the diagnostics **verbatim** and let the user re-prompt. Do NOT silently retry. Common failures:
- `@AccessControl.authorizationCheck` not `#NOT_ALLOWED` → fix the annotation
- Element not found in cube → you used a name not in the cube (re-check Step 1c)
- Measure missing currency/unit reference → add the `@Semantics.*` annotation + project the currency/unit field
- Name > 28 chars → shorten the query name

## Step 6: Verify

```
SAPRead(type="DDLS", name="<query>", include="elements")
```

Confirm the query is live and the projected elements appear. Report success with the runtime name (`2C<query>`).

## Error Handling

| Error | Cause | Fix |
|---|---|---|
| Projection target is not a cube | Tried to project on a plain view | Build a cube first via `generate-analytics-star-schema` |
| `provider contract` syntax error | SAP_BASIS < 7.57 | Stop — analytical_query unsupported on this release |
| Authorization check error on activate | Used a value other than `#NOT_ALLOWED` | Set `@AccessControl.authorizationCheck: #NOT_ALLOWED` |
| Element does not exist | Element name typo vs cube | Re-read the cube elements (Step 1c) |
| Name too long | Query name > 28 chars | Shorten it |

## Notes

- This skill **only creates the query layer**. To build the underlying cube + dimensions + texts, use `generate-analytics-star-schema` first — the two skills chain: star-schema produces the cube, this skill projects on it.
- For a transactional (RAP) service rather than analytics, use `generate-rap-service` instead.
- BTP ABAP and on-prem 7.58 both support this; on 7.57 verify the exact provider-contract form via `search()` first.

