Open SQL Part 5: JOINs — When to Use and When to Avoid
Combine tables in one statement: INNER JOIN vs LEFT OUTER JOIN, ON conditions with keys, aliases and the selector, joins on buffered tables, and when JOIN, FOR ALL ENTRIES or a CDS view is the better choice.
Explanation
📌 KEY POINTS
✅ INNER JOIN returns only rows with a match on both sides; LEFT OUTER JOIN keeps all left rows.
✅ Join on key fields (ideally the full key of the right table) to avoid row multiplication.
✅ Use aliases and the ~ column selector: f~carrid, b~bookid.
⚠️ Joins bypass the table buffer — for small buffered master tables, a separate read can be better.
━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE
Part 3 combined tables via FOR ALL ENTRIES. A JOIN combines them in one database statement — usually faster, especially on HANA.
━━━━━━━━━━━━━━━━━━━━
▶ 1. INNER JOIN
SELECT f~carrid f~connid f~fldate b~bookid b~customid
FROM sflight AS f INNER JOIN sbook AS b
ON b~carrid = f~carrid AND b~connid = f~connid AND b~fldate = f~fldate
INTO TABLE gt_result
WHERE f~carrid = p_carr.
Flights without bookings are not returned.
━━━━━━━━━━━━━━━━━━━━
▶ 2. LEFT OUTER JOIN
Keep all flights, with booking fields empty where no booking exists:
FROM sflight AS f LEFT OUTER JOIN sbook AS b ON …
Classic restrictions (older releases): WHERE conditions on the right table are not allowed — put them into ON. Newer releases relax this, but ON vs WHERE still changes the meaning:
- Condition in ON → filters the right side, left rows stay.
- Condition in WHERE on the right table → removes left rows without match (turns it into an inner join).
━━━━━━━━━━━━━━━━━━━━
▶ 3. ROW MULTIPLICATION
One flight with 200 bookings produces 200 result rows. That is correct for booking lists but wrong if you then sum flight-level fields (e.g. seatsmax) — sum at the right level or aggregate in SQL (Part 6).
━━━━━━━━━━━━━━━━━━━━
▶ 4. HOW MANY TABLES?
Joins of 2–4 tables on key fields are normal. Very large joins over many tables become hard to read and to optimize — model them as CDS views (see RAP/CDS module).
━━━━━━━━━━━━━━━━━━━━
▶ 5. JOIN VS FOR ALL ENTRIES VS SEPARATE READS
- Both tables in DB, related by keys → JOIN (one round trip).
- Driver created in ABAP → FOR ALL ENTRIES.
- Small buffered customizing/master table (e.g. SCARR) → separate SELECT (buffer) + hashed table can be faster than joining.
- Reused data model → CDS view.
━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP
Run the code example: inner join flights–bookings, left outer join showing flights without bookings, and a 3-table join with airline names.
✅ CHECKPOINT: you choose and write joins that return the rows you expect.
Code example
REPORT zosql_part5. TYPES: BEGIN OF ty_row, carrid TYPE s_carr_id, connid TYPE s_conn_id, fldate TYPE s_date, bookid TYPE s_book_id, customid TYPE s_customer, END OF ty_row, BEGIN OF ty_all, carrid TYPE s_carr_id, connid TYPE s_conn_id, fldate TYPE s_date, bookid TYPE s_book_id, END OF ty_all, BEGIN OF ty_named, carrname TYPE s_carrname, connid TYPE s_conn_id, fldate TYPE s_date, seatsocc TYPE s_seatsocc, END OF ty_named. DATA: gt_result TYPE STANDARD TABLE OF ty_row, gt_all TYPE STANDARD TABLE OF ty_all, gs_all TYPE ty_all, gt_named TYPE STANDARD TABLE OF ty_named. PARAMETERS p_carr TYPE s_carr_id DEFAULT 'LH'. START-OF-SELECTION.* ===== 1. INNER JOIN: only flights WITH bookings ===== SELECT f~carrid f~connid f~fldate b~bookid b~customid FROM sflight AS f INNER JOIN sbook AS b ON b~carrid = f~carrid AND b~connid = f~connid AND b~fldate = f~fldate INTO TABLE gt_result UP TO 100 ROWS WHERE f~carrid = p_carr AND b~cancelled = space. * ===== 2. LEFT OUTER JOIN: ALL flights, bookings where they exist =====* right-table filter in ON (keeps flights without matching bookings) SELECT f~carrid f~connid f~fldate b~bookid FROM sflight AS f LEFT OUTER JOIN sbook AS b ON b~carrid = f~carrid AND b~connid = f~connid AND b~fldate = f~fldate AND b~cancelled = space INTO TABLE gt_all UP TO 100 ROWS WHERE f~carrid = p_carr. LOOP AT gt_all INTO gs_all WHERE bookid IS INITIAL. WRITE: / 'no bookings:', gs_all-connid, gs_all-fldate. ENDLOOP. * ===== 3. Three tables: add airline name ===== SELECT c~carrname f~connid f~fldate f~seatsocc FROM scarr AS c INNER JOIN sflight AS f ON f~carrid = c~carrid INTO TABLE gt_named UP TO 20 ROWS WHERE c~carrid = p_carr. * NOTE: SCARR is small and buffered - reading it separately into a* HASHED table and READ TABLE may be faster than joining it everywhere.* Note: in older releases, constants in the ON condition of a LEFT OUTER JOIN* may need strict syntax; if the syntax check complains, filter after the SELECT.Real project scenario
A report of open orders per customer joined order headers with items and then summed the header's freight charge. Customers with 10 items showed 10 times the freight. The join itself was correct — the aggregation happened at the wrong level.
The team summed item values from the joined result and read header freight separately per order (or aggregated items per order in SQL first), and added a test case with multi-item orders.
Common mistakes
- Right-table conditions in WHERE of a LEFT OUTER JOIN (unintended inner join).
- Joining on non-key fields and multiplying rows.
- Summing header fields after joining items.
- Very large multi-table joins instead of a CDS model.
- Joining small buffered tables unnecessarily.
Best practices
- Join on key fields with aliases and ~.
- Put right-table filters of outer joins into ON.
- Aggregate at the correct level.
- Use CDS views for reusable multi-table models.
- Read buffered master tables separately when appropriate.
Interview angle
Questions: "INNER vs LEFT OUTER JOIN?", "Where do you put conditions on the right table of an outer join?", "JOIN vs FOR ALL ENTRIES?"
Strong answer: inner joins return only matching rows, left outer joins keep all left rows with initial right fields; right-table filters belong into ON, otherwise the outer join behaves like an inner join; joins bypass the buffer; use JOINs for DB-resident related tables, FAE for ABAP-built drivers, CDS for reusable models; beware row multiplication when aggregating.
Practice this lesson
Quick check
1. In a LEFT OUTER JOIN, where should a filter on the right table go to keep all left rows?
2. A join of order items and partners doubles item amounts in totals. What is the cause?
3. Which join returns only matching rows on both sides?
Interview questions
1. A SELECT statement returns duplicate rows due to a JOIN with a table that has multiple matches. How would you identify this issue and resolve it?
Answer it in your own words first, then compare.
2. A sales report joins header, items and partners, and totals are too high. What is the likely cause?
Answer it in your own words first, then compare.
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
- Open SQL Part 1: How ABAP Reads the Database
- Open SQL Part 10: Open SQL in Real Reports and Interfaces
- Open SQL Part 2: SELECT Basics — Field Lists, WHERE and INTO
- Open SQL Part 3: FOR ALL ENTRIES — Correct Use
- Open SQL Part 4: SELECT SINGLE vs UP TO 1 ROWS
- Open SQL Part 6: Aggregates, GROUP BY and HAVING
- Open SQL Part 7: PACKAGE SIZE and Large Data Processing
- Open SQL Part 8: Performance Analysis with ST05, SAT and SQL Monitor