SAP technical topicObjectCDS associations versus joinsModuleRAP_CDS_ODATA

CDS Associations vs Joins and When Each Fires

A CDS join is compiled into the generated SQL of the view that declares it and always contributes to that view's result set. A CDS association is only converted into an actual join when a consumer navigates it through a path expression, an exists filter, or an OData expand. An unused association costs nothing at runtime; an unused join still runs.

This page covers the structural difference between a CDS join, which is fixed at authoring time inside one view, and a CDS association, which is a declared but lazily evaluated relationship that only becomes a join when a consumer actually uses it. It focuses on how that laziness changes performance and design decisions across stacked views, exists filters, and OData expand.

Published 16 Sept 2026· 1,417 words

What it is

A join inside a CDS view entity is resolved when the view is activated: the database has to combine the tables every time that view is read, whether or not the joined columns end up in the final result. An association is different. It is a named, typed relationship declared with a cardinality and an ON condition, but it produces no SQL by itself. It only turns into a real join at the moment some consumer writes a path expression against it, filters with an exists predicate that references it, or an OData client asks for an expand on it. If nothing ever touches the association, the view runs exactly as if the association were not declared. The confusion almost always comes from treating the two as interchangeable syntax for 'related data' when only one of them is evaluated eagerly and the other is evaluated on demand, sometimes several view layers away from where it was declared.

When to use it

Use a join when the combined data is genuinely required on every read of that view, for example a sales order line that always needs its plant and material description flattened into the result for a report that never runs without them. Use an association when the related data is optional, consumer-specific, or only needed for drill-down, filtering, or expand: business partner address details on an object page, a status text only some consumers display, or a parent-child hierarchy navigated occasionally. Reaching for a join out of habit when the field is rarely selected is the mistake that inflates every query against that view. Reaching for an association when every single consumer needs the field anyway just defers work that will fire on every call regardless, adding a layer of indirection with no benefit.

How it fits the stack

Below both sits the physical or virtual data source: database tables, other CDS view entities, or a table function. A join operates within one view definition and is fixed there for every consumer of that view. An association sits above that same layer but is published outward: a consuming CDS view, a RAP behavior definition, or an OData service can extend the path further, filter through it with exists, or request it as an expand without the publishing view having to pre-join anything. Associations are what make CDS view composition and RAP business object composition workable at scale, because each layer only declares relationships rather than paying for them. Classic ABAP Open SQL joins and nested SELECTs are what associations and CDS joins together are meant to replace; associations specifically replace the pattern of building several near-identical flat views just to expose different combinations of joined fields.

A worked example

A sales order header CDS view entity declares an association to business partner, cardinality to-one, ON condition matching the sold-to party. A Fiori elements list report consumes that header view for the list screen and only selects order number, date, and status; the association is never touched in the list query, so no join to business partner runs and the list query stays cheap regardless of table size. The same view is consumed by the object page, where the UI annotation exposes the association for expand; the moment a user opens a single order, the OData request navigates the association and the framework generates the join at that point, scoped to one order key. Contrast this with an earlier version of the same view that joined business partner directly into the header select: the list report query would then always carry that join, even though the list screen never displayed a partner field, and the extra cost would show up in the list report's response time as the order table grew, not in the object page where the data was actually needed.

How to choose

  • Is the related data needed by every consumer of this view, or only by some. Every consumer needing it argues for a join; some-but-not-all argues strongly for an association.
  • Will the related entity be filtered on rather than displayed. If so, prefer an association consumed through exists, which avoids row duplication from cardinality fan-out that a naive join would introduce.
  • What is the cardinality of the relationship. To-many associations joined naively multiply header rows; confirm whether the consumer needs one row per header or is prepared to aggregate, and design the ON condition and exists usage accordingly.
  • Does the association need to be reachable from further up the stack, for RAP behavior implementations or for an OData expand. If yes, it must be explicitly published from the exposing view; a private association that is never re-exposed is invisible to consumers even though it compiles.
  • How many view layers sit between the base table and the final consumer. A chain of joins fires at every layer regardless of use; a chain of associations only fires once, at the layer where the path is finally resolved, which is the main reason deeply stacked CDS models favor associations.
  • Do not rely on the database optimizer to eliminate an unused join for you. Some optimizers can drop a provably redundant join, but this is engine- and query-shape-dependent and should not be treated as a design guarantee.

Common pitfalls

  • Filtering directly on a field reached through a to-many association without wrapping it in exists, which silently duplicates the outer rows once real data with multiple children shows up, passing fine in a development system with one test order per customer.
  • Assuming an association declared in a lower-level view is automatically expandable from a Fiori app several layers up. It has to be explicitly re-published at each intermediate layer or the path is simply unreachable from the top.
  • Stacking many CDS views that each use plain joins instead of associations for optional fields, so a simple list query ends up executing a dozen joins nobody asked for, a pattern that looks harmless with a few thousand test rows and becomes the dominant cost once the base table reaches production volume.
  • Getting the ON condition on an association wrong in a way that only manifests as fan-out once combined with real transactional data density, because a small development dataset rarely exposes a one-to-many relationship that behaves like one-to-one in test data.
  • Confusing the presence of an association in the CDS definition with it actually being usable for OData navigation; the annotation that exposes it for expand is a separate design decision from declaring the association itself, and skipping it is a common cause of a $expand request returning nothing.

ECC, S/4HANA and clean core

Association-based composition is the pattern S/4HANA and clean core guidance actively favor: build narrow, reusable CDS view entities connected by associations rather than wide flat views stitched together with joins and nested selects against transparent tables. RAP business object composition assumes associations as the mechanism for reaching child and related entities, and Fiori elements relies on published associations for expand behavior on object pages. Classic ABAP joins against database tables directly, and older DDIC views that flatten multiple tables permanently, are the pattern this approach is meant to move away from, particularly where extensibility or reuse across several apps is expected. No specific release restricts either construct; the shift is architectural guidance, not a version gate.

Whose problem this is

This is developer territory at the point of writing the CDS view, but the decision of which associations get published for expand or exists usage is an architectural call, because it determines what downstream consumers, including RAP behavior implementations and Fiori apps, are allowed to reach. Handover to functional teams should identify which associations carry cardinality risk if used without exists.

Related SAP objects

Reviewed pages this object connects to in the ERPClimb knowledge graph.

Source: ERPClimb — https://erpclimb.com/sap-technical-topics/cds-associations-versus-joinsERPClimb is an independent platform and is not affiliated with SAP SE. Reference pages are written and reviewed by SAP consultants for learning and troubleshooting.