Analytics Catalog/Workday/Payroll to ledger
Explore the catalogPayroll modulePay component mappingModulesCost Center & CompanyHeadcount trendExtraction patternEnterprise model
Workday · Payroll · Report

Payroll does not match the accounting system

Payroll runs person by person. The accounting system receives totals. When someone asks why one department went over, the answer takes a spreadsheet and a morning. This page shows where the detail is lost, and the one query that drills through it.

RuleKeep every payroll line at its own grain, then prove the total against the ledger every period.
Neverreconcile on net pay. It excludes employer cost and can never equal what the ledger carries.
What goes wrongthe plain version, before any technical detail.

Payroll calculates one line per person for every kind of pay: base salary, overtime, a bonus, each deduction, each tax. Thousands of lines a period in a mid-size company.

The accounting system never sees them. The lines are summed by account and posted as a single journal entry. Correct totals, nothing underneath.

Payroll run person by person Journal entry totals only The ledger totals by account What does not travel department, pay type, person, hours
The detail stops at the journal. The ledger is right and cannot explain itself.

Every departmental question after that point is answered by hand, and the working is thrown away each month.

Why the two numbers driftfour causes, each one a wrong answer rather than an error message.
CauseWhat actually happens
Net pay is not the costThe employer also pays its own payroll taxes, its benefit share, and its retirement match. Those sit in the journal and never appear in net pay, so reconciling on it compares two different things.
Cost centers that were never assignedPayroll accounting maps each earning and deduction to an account and a cost center. Where the mapping is missing the amount still posts, so the company total stays right while department numbers go quietly wrong. The Cost Center & Company page carries the code map.
Two different datesA period ending 31 May can post in June. Filter payroll by pay period and the ledger by posting date, and the totals differ by exactly one run.
Corrections after the factPayments outside the normal cycle come in four kinds: one already made elsewhere, an urgent extra, a reversal, a reissue. The wrong kind duplicates or drops accounting entries, and the ledger takes whatever was chosen.

All four share a shape. Nothing errors. The report runs, looks finished, and is wrong. That is the expensive kind, because it surfaces only when somebody who knows the business says that cannot be right.

What the delivered reports give youand the two places they stop.
Delivered reportWhat it does, and where it stops
Gross to NetWalks earnings down through deductions and taxes to net pay. Correct, and the wrong figure for a ledger tie, per the first cause above.
Payroll Results SummaryPeriod totals per pay group. Good for a run check, one period at a time, so trend and variance work stays manual.
Pay Results by OrganizationResult detail by organization, with a costing variant adding cost center and fund. The closest delivered report to what is needed, and it lives inside Workday, so a ledger held elsewhere cannot be joined to it.
Journal previewShows the entry before it is committed. A control worth using, and a one time check, not a record you can query next quarter.

None of these is poor. A tie needs both sides in one place, and one side is always somewhere else.

What we buildevery line kept, and the total proved.

One row per person, per pay component, per period, landed on the customer's own cloud. The ledger lands beside it. The totals are compared on a schedule and the result is kept, so last quarter's check can still be produced.

Payroll run person by person Your warehouse every line kept The period check totals must match The ledger totals by account
Both sides land in one place. The check runs every period and is kept.

Grain, the level of detail one row represents, is the whole decision. A row at person and pay component adds up to any question: by department, by pay type, by month, by legal entity. A row that arrives summarised cannot be taken apart again.

The drill, in one querythe account tie and the department detail returned together.

Two grains in one pass. Grouping sets tell the database to aggregate at more than one level in a single query, so the account subtotal and the cost center breakdown come back together rather than as two reports somebody has to line up.

-- one query, three levels: grand total, account, cost center within account
WITH lines AS (
  SELECT c.ledger_account,
         COALESCE(cc.cost_center, 'none') AS cost_center,
         f.amount
  FROM fct_payroll_result_line f
  JOIN      dim_date          d  ON f.accounting_date_key = d.date_key
  JOIN      dim_pay_component c  ON f.pay_component_key   = c.pay_component_key
  LEFT JOIN dim_cost_center   cc ON f.cost_center_key     = cc.cost_center_key
  WHERE d.fiscal_period = '2026-05'
    AND c.affects_ledger
),
payroll AS (
  SELECT ledger_account, cost_center, SUM(amount) AS payroll_amount
  FROM lines
  GROUP BY GROUPING SETS ((), (ledger_account), (ledger_account, cost_center))
),
ledger AS (
  SELECT a.ledger_account, SUM(j.amount) AS ledger_amount
  FROM fct_gl_journal_line j
  JOIN dim_date    d ON j.accounting_date_key = d.date_key
  JOIN dim_account a ON j.account_key         = a.account_key
  WHERE d.fiscal_period = '2026-05'
    AND j.source_system = 'Payroll'
  GROUP BY GROUPING SETS ((), (a.ledger_account))
)
SELECT COALESCE(p.ledger_account, l.ledger_account, 'total') AS ledger_account,
       COALESCE(p.cost_center, 'all')                        AS cost_center,
       p.payroll_amount,
       CASE WHEN p.cost_center IS NULL THEN l.ledger_amount END AS ledger_amount,
       CASE WHEN p.cost_center IS NULL
            THEN COALESCE(p.payroll_amount, 0) - COALESCE(l.ledger_amount, 0)
       END                                                   AS difference
FROM payroll p
FULL OUTER JOIN ledger l
  ON p.ledger_account IS NOT DISTINCT FROM l.ledger_account
 AND p.cost_center IS NULL
ORDER BY CASE WHEN p.ledger_account IS NULL THEN 0 ELSE 1 END,
         1,
         CASE WHEN p.cost_center IS NULL THEN 0 ELSE 1 END,
         2

Three things carry the weight. Grouping sets produce a null cost center on subtotal rows, so unmapped lines are coalesced to none before grouping: none is a real gap in the data, null is a subtotal, and conflating them hides the gap inside its own total. The full outer join keeps an account that exists on one side only, which is the failure an inner join silently swallows. And both sides filter on the accounting date, which removes the two-dates cause above.

Sample output, May 2026. The account rows open and close; 6000 is shown open:

ledger_accountcost_centerpayroll_amountledger_amountdifference
totalall4,663,142.084,663,142.080.00
6000 · Salaries and wagesall3,997,550.003,997,550.000.00
6100 · Sales61001,842,500.00
6120 · Technology Delivery61201,208,750.00
6300 · Support6300937,880.00
none · Unassignednone8,420.00
+6010 · Employer payroll taxesall305,812.58305,812.580.00
6100 · Sales6100140,932.31
6120 · Technology Delivery612092,469.38
6300 · Support630071,746.76
none · Unassignednone664.13
+6020 · Employer benefit costall359,779.50359,779.500.00
6100 · Sales6100165,825.00
6120 · Technology Delivery6120108,787.50
6300 · Support630084,409.20
none · Unassignednone757.80

Every level rolls up. Each account's cost center rows add to its own subtotal, the three account rows add to the grand total, and the same four departments appear under every account, so one department's full cost is three clicks, not three reports.

The empty cells are the point. The ledger holds no cost center, so those columns can only ever populate at the account level. The 8,420.00 on the none row belongs to a department nobody can name, and the total still ties. Sample values are illustrative, never client data.

Use case
Problem
Payroll cost arrives summarised. Departmental questions are answered by hand each month, and nobody can prove the two systems agree without redoing the work.
What we build
A payroll fact at one row per person, per pay component, per period, on the customer's cloud beside the ledger, sharing the cost center, company, and date dimensions the rest of the catalog uses. The tie runs on a schedule and its result is stored.
What you get
Payroll cost by department, pay type, and month, drillable to the person. A tie that is proved rather than asserted, with unmapped amounts named. Every model, query, and runbook is yours, in your repository.
Want payroll cost that ties on its own?
We build the payroll fact, land the ledger beside it, and publish the tie, reconciled against Workday before go live.
Talk to us
Terms on this page
pay component
One kind of pay or deduction: base salary, overtime, a tax, a benefit premium.
gross pay
Total earnings before any deductions or taxes are taken out.
net pay
What reaches the employee's bank account, after deductions and taxes.
employer cost
Gross pay plus the employer's own taxes and benefit share. What the ledger carries.
journal entry
The accounting record of a transaction, posted to the ledger.
ledger account
The bucket a cost is recorded in, such as salaries or employer taxes.
cost center
The department or team a cost is charged to.
accounting date
The date a transaction is recognised in the books. Not always the pay period end.
off cycle
A payment made outside the normal pay run: prior payment, urgent payment, reversal, reissue.
grain
The level of detail one row represents. One row per person per pay component per period, here.
fact
A table of measured amounts, such as pay, joined to descriptive tables.
dimension
A descriptive table: who, which department, which date, which pay type.
full outer join
A join that keeps rows present on either side, so nothing is silently dropped.
grouping sets
An instruction to aggregate at several levels in one query, giving subtotals and detail together.
subtotal row
A rolled-up line. Here it carries the word all, and is not the same as a missing value.
tie
A check that two independently built totals agree.