CDS Associations
RAP / CDS / ODataintermediate

CDS Associations Part 5: Troubleshooting Duplicates, Missing Data and Slow Queries

Key takeaway

Fix the three typical association problems — too many rows, missing rows and slow queries — with a repeatable method: check cardinality, join type, filters and the generated SQL.

Explanation

📌 KEY POINTS
✅ Too many rows → a to-many path or wrong cardinality.

✅ Missing rows → an inner join path, a filter on the target in WHERE, or access control.

✅ Slow → unnecessary joins, to-many paths in lists, functions in ON conditions.

💡 Always compare row counts with and without each path before changing code.

━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE

Parts 1–4 built associations correctly. In real projects you inherit views that return strange results. This lesson is the troubleshooting method.

━━━━━━━━━━━━━━━━━━━━
▶ 1. SYMPTOM: TOO MANY ROWS (DUPLICATES)

Check in this order:

  1. Is any to-many path used in the element list or WHERE? Remove it temporarily and compare counts.
  2. Is a to-one association really to-one in the data? Count target rows per key (code example, query A).
  3. Is a text or validity association missing its language/date condition?

Fix: filtered path with "1:", aggregation view, or add the missing condition. Do not add DISTINCT to hide it.

━━━━━━━━━━━━━━━━━━━━
▶ 2. SYMPTOM: MISSING ROWS

  1. Is a path declared as [inner]? Inner paths drop source rows without a target.
  2. Is there a WHERE condition on a target field? "WHERE _Customer.Country = 'DE'" excludes rows without a customer (NULL is not 'DE') — that may be correct or not.
  3. Is the ON condition too strict (wrong key, client, language, type mismatch with leading zeros)?
  4. Is CDS access control filtering the rows for this user? Test with a privileged read or another user (CDS Views Part 4).

━━━━━━━━━━━━━━━━━━━━
▶ 3. SYMPTOM: SLOW QUERY

  1. Which paths are actually used by the consumer? Associations cost nothing unless used — check the consumer query and OData $expand/$select.
  2. Are to-many paths in list views creating huge intermediate results?
  3. Are there functions or calculations in ON conditions (e.g. CONCAT, SUBSTRING) that prevent efficient joins?
  4. Is cardinality correct so the optimizer can prune joins?

Measure with ST05 and the explain plan; look at the generated SQL.

━━━━━━━━━━━━━━━━━━━━
▶ 4. TOOLS

  • ADT Data Preview with SQL console — run count queries quickly.
  • Show SQL CREATE Statement — see the generated joins.
  • ST05 SQL trace — see the statement a real consumer sends.
  • Dependency Analyzer in ADT — see the view stack and all joined sources.

━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP

  1. Reproduce with the exact consumer query.
  2. Count rows with and without each suspicious path.
  3. Check cardinality with the count queries in the code example.
  4. Fix the model, re-run the counts, then measure runtime.

✅ CHECKPOINT: you have a method for every association defect. Next topic: CDS Annotations.

Code example

ABAP Code
" ===== Query A: is the "to-one" association really to-one? =====" (run in the ADT SQL console / Data Preview)SELECT SalesOrder, COUNT(*) AS targets  FROM zi_salesorderitem  WHERE SalesOrderItem = '000010'  GROUP BY SalesOrder  HAVING COUNT(*) > 1  INTO TABLE @DATA(not_to_one).          " any row = cardinality promise broken " ===== Query B: row count with and without a path =====SELECT COUNT(*) FROM zc_salesorderlist INTO @DATA(with_paths).SELECT COUNT(*) FROM zi_salesorder     INTO @DATA(base_rows)." with_paths > base_rows  -> some path multiplies rows " ===== Query C: which source rows have NO target? (missing rows) =====SELECT SalesOrder FROM zi_salesorder  WHERE \_Customer-Customer IS NULL  INTO TABLE @DATA(orders_without_customer)." Such rows vanish with [inner] paths or WHERE on customer fields " ===== Query D: access control or data? =====SELECT COUNT(*) FROM zi_salesorder WITH PRIVILEGED ACCESS INTO @DATA(all_rows)." all_rows > visible rows -> DCL is filtering (expected?), not the association // ===== Typical fixes =====// duplicates : _Item[ 1: SalesOrderItem = '000010' ].Material  or aggregation view// missing    : remove [inner]; move target filter to the consumer on purpose// texts      : add  and _Text.Language = $session.system_language// slow       : remove unused to-many paths from list views; no functions in ON

Real project scenario

Users reported that a customer-order report "lost" about 300 orders. The developer suspected a database problem. Following the method, Query C showed exactly 300 orders without a customer master record — created by a migration with an old customer number format. The view used _Customer[inner].CustomerName, so those orders were dropped.
The fix was twofold: the path went back to the default LEFT OUTER semantics so the orders appeared with an empty customer name, and the migration team corrected the customer numbers. The investigation took 30 minutes instead of days because the team counted before changing code.

Common mistakes

  • Adding DISTINCT to hide duplicates.
  • Changing code before reproducing and counting.
  • Forgetting that WHERE conditions on target fields exclude rows without a target.
  • Blaming associations for rows filtered by access control.
  • Leading-zero or type mismatches in ON conditions.
  • Measuring with SELECT * instead of the real consumer query.

Best practices

  • Classify the symptom first: too many, missing, or slow.
  • Count with and without each path; verify cardinality with GROUP BY … HAVING.
  • Check join type, target filters, ON conditions and DCL separately.
  • Use the SQL console, SQL CREATE statement, ST05 and the dependency analyzer.
  • Re-count and re-measure after every fix.

Interview angle

Questions: "A CDS view returns duplicates — how do you analyze it?", "Why could rows be missing?", "How do you prove cardinality is wrong?"
Strong answer: classify the symptom; for duplicates look for to-many paths, broken to-one cardinality (GROUP BY … HAVING COUNT > 1) and missing language/date conditions; for missing rows check [inner] paths, WHERE on target fields, strict ON conditions and access control (compare with privileged access); for slowness check used paths, to-many in lists and functions in ON. Always reproduce with the consumer query and count before and after.

Practice this lesson

Quick check

1. An OData list returns 2,400 rows where 800 are expected. What is the recommended first step?

2. A CDS view uses a path expression such as '_Supplier.Country = \'DE\'' inside its WHERE clause. What does this pattern accomplish?

3. An association is declared [0..1], but the target sometimes has two rows per key. What happens?

Interview questions

1. An OData list returns 2,400 rows where the business expects 800. How do you analyze it?

Answer it in your own words first, then compare.

2. Users say a CDS-based report 'lost' some records. What are the possible causes related to associations, and how do you tell them apart?

Answer it in your own words first, then compare.

Practice more with Interview AI →

Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.

More lessons in this topic

Interview questions on this topic

Related SAP topics