generate-analytics-star-schema
Generate a CDS analytical model (star schema — cube + dimension + text views) on top of a RAP business object or a DDIC table. Use when asked to "create a star schema", "generate an analytical cube and dimensions", "make a RAP BO analytical", "build a CDS cube from a table", or "create an analytical model".
How do I install this agent skill?
npx skills add https://github.com/arc-mcp/arc-1 --skill generate-analytics-star-schemaIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill is a specialized developer tool for SAP environments. It automates the generation of CDS analytical models by reading metadata from existing SAP tables, business objects, or views and creating a structured star schema (cubes, dimensions, and text views). The skill uses the provided SAP system tools for all operations and follows standard SAP development patterns.
- Socketwarn
1 alert: gptAnomaly
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
Generate CDS Analytics Star Schema
Generate a CDS analytical model — a star schema consisting of a cube view (@Analytics.dataCategory: #CUBE), one or more dimension views (#DIMENSION), and text views (#TEXT) — on top of a RAP business object or a plain DDIC table.
This skill replicates SAP Joule's "CDS Analytical Model Generation" capability (basic + extended scope) by combining ARC-1 (SAP system access) with mcp-sap-docs (documentation & best practices). Basic scope = cube + reuse existing dimensions. Extended scope = also generate new dimensions, and start from a DDIC table (not just a RAP BO).
Prerequisites — read this first
- Requires SAP_BASIS 7.5x with the analytics annotations. Verify with
SAPManage(action="probe")(rap.available). If RAP/CDS isn't available, stop. - The output is a set of interdependent CDS views (cube → dimensions → texts). They must be created as inactive drafts and activated together so SAP's activator resolves the cross-references in one pass. This skill uses
SAPWrite(action="batch_create", activateAtEnd: true)for exactly that.
Smart Defaults (apply silently, do NOT ask)
| Setting | Default | Rationale |
|---|---|---|
| Object type | DDLS | Cube/dimension/text are all CDS data definitions |
| Package | $TMP | Fast prototyping; ask before a transportable package |
| Activation | batch_create + activateAtEnd: true | Cross-references resolve in one terminal pass |
| Authorization | #NOT_REQUIRED (cube/dimension) | Standard for analytical interface views |
| Naming | ZI_<X>_Cube, ZI_<X>_Dim, ZI_<X>_Txt | Clear star-schema roles (match the casing the templates use) |
Input
The user provides either:
- A RAP business object root entity / behavior pool (e.g.
ZBP_R_TRAVELorZI_Travel), or - A DDIC table (e.g.
ZTRAVEL), or - An interface CDS view to use as the fact source.
Optionally: which numeric fields are measures, which fields are dimensions, target package, and whether to generate new dimension views or reuse existing ones.
Step 1: Resolve and read the source
1a. RAP BO input — find the bound CDS root entity
If the user names a behavior pool class (ZBP_*), read the class metadata to find the root entity it's bound to:
SAPRead(type="CLAS", name="<ZBP_class>", format="structured")
Look for the bound root entity reference. Then read that CDS root entity (next step). If the user already named the CDS root entity / interface view, skip to 1c.
1b. DDIC table input — read the table structure
SAPRead(type="TABL", name="<table>")
Capture the field list with types. Identify candidate keys, dimension fields (foreign keys, characteristics), and numeric measure fields (amounts, quantities, counts).
1c. CDS view input — read source + elements
SAPRead(type="DDLS", name="<view>")
SAPRead(type="DDLS", name="<view>", include="elements")
The element list classifies fields. Decide for each:
- Measure: numeric, additive (amount / quantity / count) → goes in the cube with
@Aggregation.default: #SUM - Dimension: a characteristic you slice by (region, customer, date) → either reuse an existing dimension view or generate a new one
- Currency/Unit: paired with each amount/quantity measure
Step 2: Discover reusable dimensions
For each dimension field, check whether a reusable dimension view already exists before generating a new one:
SAPSearch(query="*<dimension_keyword>*", searchType="object", objectType="DDLS")
If a released #DIMENSION view exists (e.g. I_Country, I_CalendarDate), reuse it via association. Otherwise generate a new ZI_<X>_DIM (extended scope).
Step 3: Look up current analytics annotations
Ground the generation in current docs:
search("CDS analytical model cube Analytics.dataCategory CUBE dimension representativeKey")
search("ObjectModel.foreignKey.association text association Semantics.text")
Cite the doc IDs in your summary. Key facts (7.58):
- Cube:
@Analytics: { dataCategory: #CUBE, internalName: #LOCAL }+@ObjectModel: { supportedCapabilities: [ #ANALYTICAL_PROVIDER ], modelingPattern: #ANALYTICAL_CUBE } - A cube needs ≥1 measure; measures carry
@Aggregation.default: #SUM(or#MIN/#MAX/#AVG); a numeric field WITHOUT@Aggregation.defaultis treated as a dimension - Cube→dimension associations must be
[1..1];@ObjectModel.foreignKey.association: '_Dim'goes on the dimension key field in the cube - Dimension:
@Analytics.dataCategory: #DIMENSION+@ObjectModel: { representativeKey: '<key>', supportedCapabilities: [ #ANALYTICAL_DIMENSION ], modelingPattern: #ANALYTICAL_DIMENSION } - Text view uses
@ObjectModel.dataCategory: #TEXT(theObjectModel.namespace — NOT@Analytics.dataCategory) +@Semantics.text: true+@Semantics.language: true
Step 4: Compose the model
Cube template
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Cube'
@Analytics: {
dataCategory: #CUBE,
internalName: #LOCAL
}
@ObjectModel: {
supportedCapabilities: [ #ANALYTICAL_PROVIDER ],
modelingPattern: #ANALYTICAL_CUBE
}
define view entity ZI_Sales_Cube
as select from zsales_data
association [1..1] to ZI_Region_Dim as _Region
on _Region.RegionId = $projection.RegionId
{
@ObjectModel.foreignKey.association: '_Region'
key region_id as RegionId,
key calyear as CalendarYear,
@Semantics.amount.currencyCode: 'CurrencyCode'
@Aggregation.default: #SUM
amount as SalesAmount,
currency as CurrencyCode,
@Aggregation.default: #SUM
@EndUserText.label: 'Number of Records'
abap.int8'1' as RecordCount,
_Region
}
Dimension template
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Region Dimension'
@Analytics.dataCategory: #DIMENSION
@ObjectModel: {
representativeKey: 'RegionId',
supportedCapabilities: [ #ANALYTICAL_DIMENSION ],
modelingPattern: #ANALYTICAL_DIMENSION
}
define view entity ZI_Region_Dim
as select from zregion
association [0..1] to ZI_Region_Txt as _Text
on _Text.RegionId = $projection.RegionId
{
@ObjectModel.text.association: '_Text'
key region_id as RegionId,
_Text
}
Text view template
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Region - Text'
@ObjectModel.dataCategory: #TEXT
@ObjectModel.representativeKey: 'RegionId'
define view entity ZI_Region_Txt
as select from zregion_t
{
key region_id as RegionId,
@Semantics.language: true
key spras as Language,
@Semantics.text: true
regiontext as RegionName
}
(The text view stands alone — it declares no association, so it carries no @ObjectModel.foreignKey.association. The link is one-directional: the dimension associates to this text view via _Text + @ObjectModel.text.association, not the other way round.)
Composition rules (enforce before writing):
- Cube has ≥1 measure (
@Aggregation.default). - Each amount measure has
@Semantics.amount.currencyCode+ the currency field is projected; each quantity measure has@Semantics.quantity.unitOfMeasure+ the unit field is projected. - Every cube→dimension association is
[1..1]and the dimension key field carries@ObjectModel.foreignKey.association. - Every dimension has a
representativeKey. - Text views use
@ObjectModel.dataCategory: #TEXT+@Semantics.text/language.
Step 5: Show the plan as a tree, then batch-create
Show the user the model tree before writing:
Cube: ZI_Sales_Cube (from zsales_data)
Measures: SalesAmount (SUM, currency CurrencyCode), RecordCount (SUM)
Dimensions:
ZI_Region_Dim (new) → ZI_Region_Txt (new)
On confirmation, create all views in one batch with deferred activation so cross-references resolve together:
SAPWrite(action="batch_create", activateAtEnd=true, objects=[
{ type: "DDLS", name: "ZI_Region_Txt", source: "<text_ddl>", package: "$TMP" },
{ type: "DDLS", name: "ZI_Region_Dim", source: "<dimension_ddl>", package: "$TMP" },
{ type: "DDLS", name: "ZI_Sales_Cube", source: "<cube_ddl>", package: "$TMP" }
])
Order the array dependencies-first (text → dimension → cube) so that even if activateAtEnd falls back to per-object activation on an older release, the chain still resolves. With activateAtEnd: true, ARC-1 writes all three as inactive drafts and fires one terminal activation over the whole graph.
If batch_create with activateAtEnd is not honored (older release), fall back to: create each object with SAPWrite(action="create", ...) then a single SAPActivate(objects=[...]) over all created objects.
Step 6: Verify
SAPRead(type="DDLS", name="ZI_Sales_Cube", include="elements")
Confirm the cube is live and its dimension association + measures resolved.
Step 7: Offer the query layer
The cube is the foundation; the query is what end users consume. Offer:
"Want me to generate an analytical query (the consumable KPI view) on top of this cube? →
generate-cds-analytical-query"
Error Handling
| Error | Cause | Fix |
|---|---|---|
| Cube has no measure | All numeric fields treated as dimensions | Add @Aggregation.default: #SUM to at least one numeric field |
| Association cardinality error | Cube→dimension not [1..1] | Change cardinality to [1..1] |
foreignKey.association placement | Annotation on the association instead of the key field | Move it to the dimension key field in the cube |
| Text view rejected | Used @Analytics.dataCategory instead of @ObjectModel.dataCategory for #TEXT | Text views use the ObjectModel namespace |
| Cross-reference unresolved | Created objects activated individually before siblings existed | Use batch_create with activateAtEnd: true |
| Representative key missing | Dimension has no representativeKey | Add @ObjectModel.representativeKey |
Notes
- Basic vs extended scope: Basic = reuse existing dimensions (Step 2 finds released
#DIMENSIONviews). Extended = also generate newZI_*_DIM+ZI_*_TXTand accept a DDIC table as the fact source (Step 1b). - This skill builds the model; the consumable query is a separate step (
generate-cds-analytical-query). - For a transactional RAP service (not analytics), use
generate-rap-service. - Reuse SAP standard dimensions where they exist (
I_CalendarDate,I_Country,I_Currency) instead of regenerating them —SAPSearchin Step 2.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/arc-mcp/arc-1/generate-analytics-star-schema">View generate-analytics-star-schema on skillZs</a>