# SAP BKPF BSEG Join: Four Keys, SHKZG and ACDOCA

Source: https://gravam.com/blog/02-bkpf-bseg-join
Author: Tan Gravam
Published: 2026-05-13
Updated: 2026-09-01
Reviewed: 2026-09-01
Summary: Join SAP BKPF to BSEG on MANDT, BUKRS, BELNR and GJAHR, flip amounts with SHKZG, and know when to read ACDOCA instead — the join that quietly fans out.

In SAP FI, an accounting document is split across two tables: `BKPF` holds the header (one row per document) and `BSEG` holds the line items (many rows per document). Get the join keys wrong and you either duplicate rows or silently drop postings from other company codes.

## The four keys

A document is only unique across **all four** of these together:

- `MANDT` — client
- `BUKRS` — company code
- `BELNR` — document number
- `GJAHR` — fiscal year

Leaving out `BUKRS` or `GJAHR` is the classic mistake: `BELNR` is only unique _within_ a company code and fiscal year, so a two-field join fans out across every company code that happens to reuse the same document number.

Header joined to line items on the full key

```sql
SELECT
  h.bukrs,
  h.belnr,
  h.gjahr,
  h.blart,   -- document type
  h.budat,   -- posting date
  l.buzei,   -- line item number
  l.hkont,   -- G/L account
  l.shkzg,   -- debit/credit indicator (S/H)
  l.dmbtr    -- amount in local currency
FROM bkpf AS h
JOIN bseg AS l
  ON  l.mandt = h.mandt
  AND l.bukrs = h.bukrs
  AND l.belnr = h.belnr
  AND l.gjahr = h.gjahr
WHERE h.bukrs = '1000'
  AND h.gjahr = '2026'
ORDER BY h.belnr, l.buzei;
```

## Amounts are unsigned — read SHKZG

`DMBTR` is always stored as a positive number. The debit/credit direction lives in `SHKZG` (`S` = debit, `H` = credit). If you sum `DMBTR` directly you get gross turnover, not a net balance.

Signed balance per G/L account

```sql
SELECT
  l.hkont,
  SUM(CASE WHEN l.shkzg = 'H' THEN -l.dmbtr ELSE l.dmbtr END) AS net_local
FROM bkpf AS h
JOIN bseg AS l
  ON  l.mandt = h.mandt
  AND l.bukrs = h.bukrs
  AND l.belnr = h.belnr
  AND l.gjahr = h.gjahr
WHERE h.bukrs = '1000'
  AND h.gjahr = '2026'
GROUP BY l.hkont;
```

> `BSEG` is a cluster table in classic SAP — you can't always query it directly with open SQL. On S/4HANA read from the `ACDOCA` universal journal instead, where each line already carries `RBUKRS`, `BELNR`, `GJAHR`, and a signed amount in `HSL`.

## When not to use it

For reporting, don't reconstruct balances from `BKPF`/`BSEG` line by line — that's what `ACDOCA` (S/4) or the summary tables (`GLT0`, `FAGLFLEXT` in classic) are for. Reach for the header/line join when you need document-level detail: audit trails, drill-downs, or reconciling a specific posting — tracing what an [electronic bank statement run](https://gravam.com/blog/electronic-bank-statement-processing-in-sap) actually posted is the everyday example. For the treasury-side view of these tables and the modules that write to them, see the [SAP treasury tables reference](https://gravam.com/blog/sap-treasury-tables) and the [SAP Treasury hub](https://gravam.com/topics/sap-treasury).

## Questions this article answers

**Q: Which fields join BKPF to BSEG in SAP FI?**

All four together — MANDT (client), BUKRS (company code), BELNR (document number), and GJAHR (fiscal year). BELNR is only unique within a company code and fiscal year, so a two-field join fans out across every company code that reuses the same document number.

**Q: Why can't I just sum DMBTR to get an account balance?**

DMBTR is always stored as a positive number; the debit/credit direction lives in SHKZG (S = debit, H = credit). Summing DMBTR directly gives gross turnover, not a net balance — flip the sign for H rows, e.g. SUM(CASE WHEN shkzg = 'H' THEN -dmbtr ELSE dmbtr END).

**Q: Should I still join BKPF and BSEG on S/4HANA?**

On S/4HANA read from the ACDOCA universal journal instead, where each line already carries RBUKRS, BELNR, GJAHR and a signed amount (HSL). Reserve the BKPF/BSEG join for document-level detail such as audit trails and drill-downs.
