SQL Running Total and Window Function Query Generator for ERP Tables
Running balances, change from the previous transaction and latest-N-per-item are everyday ERP report needs. This generator writes the window function version so you stop using slow self-joins.
Fill in
Running total per group
SELECT
item_code,
transaction_date,
transaction_id,
quantity_signed,
SUM(quantity_signed) OVER (
PARTITION BY item_code
ORDER BY transaction_date, transaction_id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM dbo.inventory_transactions
WHERE warehouse = 'MAIN'
ORDER BY item_code, transaction_date, transaction_id;Change from previous row
SELECT
item_code,
transaction_date,
quantity_signed,
LAG(quantity_signed) OVER (
PARTITION BY item_code
ORDER BY transaction_date, transaction_id
) AS previous_value,
quantity_signed - LAG(quantity_signed, 1, 0) OVER (
PARTITION BY item_code
ORDER BY transaction_date, transaction_id
) AS change_from_previous
FROM dbo.inventory_transactions
WHERE warehouse = 'MAIN';Latest N rows per group
WITH ranked AS (
SELECT
t.*,
ROW_NUMBER() OVER (
PARTITION BY item_code
ORDER BY transaction_date DESC, transaction_id DESC
) AS rn
FROM dbo.inventory_transactions AS t
WHERE warehouse = 'MAIN'
)
SELECT *
FROM ranked
WHERE rn <= 3
ORDER BY item_code, rn;Share of group total
SELECT
item_code,
transaction_date,
quantity_signed,
SUM(quantity_signed) OVER (PARTITION BY item_code) AS group_total,
CASE WHEN SUM(quantity_signed) OVER (PARTITION BY item_code) = 0
THEN 0
ELSE 100.0 * quantity_signed / SUM(quantity_signed) OVER (PARTITION BY item_code)
END AS pct_of_group
FROM dbo.inventory_transactions
WHERE warehouse = 'MAIN';Written in ANSI-style SQL that runs on SQL Server 2012 and later, Oracle, PostgreSQL and recent MySQL. Table and column names are placeholders for your ERP, so check them in your data dictionary. Test on a small date range first.
Optional report build
Tell us the balance report you need from your ERP and we will outline how to build it.
Optional. The tool and every export work without this. No spam.
How to use the generator
Enter the table or view name, the column that groups rows (item, account, customer), the column that orders them and a unique tie-breaker such as a transaction id. Set the amount column that should accumulate and an optional filter. Copy the query you need into your SQL tool. Replace the placeholders with real names from your ERP, and run first with a narrow filter so you can check the first few rows against the ERP screen.
Why the frame clause and tie-break matter
When ORDER BY is given without a frame, many engines default to RANGE, which treats rows with the same order value as one group. Two transactions posted at the same date would then show the same running total. Writing ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW and adding a unique tie-break column makes the result deterministic, and ROWS is also generally faster. Without a tie-break, results can change between runs.
Reading the output
For an inventory ledger the running total gives the balance after each movement, provided the quantity is signed (receipts positive, issues negative) and the query starts at the first transaction. If you filter by date, the running total starts at zero in the window, so add an opening balance. The latest-N query is useful for the last three receipt costs of an item, and the share of group total shows concentration, such as one customer holding most of the open balance.
Common mistakes
People often filter on a window result in the WHERE clause, which is not allowed because windows run after filtering. Wrap the query in a common table expression, as the latest-N block does. Others forget that NULL amounts propagate in LAG and SUM, apply the filter after the window so balances are wrong, or run it on unindexed tables with millions of rows. An index on the group and order columns helps greatly.
Frequently Asked Questions
Does this work on older SQL Server versions?
Running totals with an ordered window frame need SQL Server 2012 or later. On SQL Server 2008 you would use a correlated subquery or a cursor, which is much slower on large ledgers.
Why is my running total wrong on same-day rows?
Without a unique tie-break in the ORDER BY, rows with equal order values are arbitrary, and a default RANGE frame groups them. Add a unique column and use the ROWS frame as in the generated query.
Can I filter on the running total?
Not directly in the same SELECT. Wrap the query in a CTE or subquery and filter the outer query, for example to find the first date the balance went negative.
How do I start the balance from an opening figure?
Calculate the balance before your start date separately and add it to the running total, or run the window over the full history and filter the outer query by date.
If you want these report queries built against your actual ERP schema and scheduled to a share, we can build them with you.
Related Tools
SQL Monthly Pivot Query Generator (Conditional Aggregation)
Build a month-by-month cross-tab of any ERP amount, such as usage by item or sales by customer, using portable CASE and SUM logic.
Developer ToolsInventory Aging Buckets SQL Generator
Build an inventory aging query with your own table and column names and bucket sizes, ready to paste into SQL Server style SQL.
Developer ToolsERP Reporting Query Quality Assessment
Answer 10 questions about how your team writes, tests, documents and runs SQL reports on ERP data, and get a score with recommendations.
Developer ToolsERP SQL Query Catalog Worksheet
An editable inventory of the queries and reports your team runs, with owner, frequency, runtime and monthly effort.
More Developer Tools tools
See all- SAP Sales Order ABAP SELECT Generator (VBAK, VBAP, VBFA)
- SAP Sales Order Create BAPI ABAP Generator (BAPI_SALESORDER_CREATEFROMDAT2)
- SAP ABAP Open Items SELECT Generator
- SAP ACDOCA SQL Query Generator
- SAP QM Inspection Lot ABAP SELECT Generator
- SAP Quality Notification Pareto SQL Query Generator
- SAP QM Usage Decision BAPI Skeleton
- ABAP Custom Exception Class Generator
- ABAP Unit Test Class Generator