SQL Server Performance for ERP Reporting

Reports that crawl usually have a cause, not a mystery. The method below finds it, and the risk is almost never in the fix.

A slow ERP report is reported as “the dashboard is slow,” which is an accurate description of the symptom and tells you nothing about the cause. The useful first move is never to optimise the query. It is to establish whether the database is reading the rows it needs or reading everything and discarding most of it — because those two situations look identical from the outside and have completely different fixes.

The single most useful reframe: when a report is fast on a short date range and dies on a longer one, that is not evidence of a volume problem. It is evidence of an access-path problem that volume has made visible. The distinction matters because the volume interpretation leads to hardware, archiving and partitioning conversations, and the access-path interpretation frequently leads to one index.

The two patterns behind most of it

Pattern 01

The filter column nobody indexed

The report filters history on a date column. The indexes that exist on that table cover the order number, a history identifier and line, a lot number, a row identifier — every access path except the one the filter actually needs. So the date range forces a scan of the whole history table.

The symptom scales with the date range, which is exactly why it gets misdiagnosed as data volume. It is also why the short-range version of the same report is often instant: it frequently reads a different, far smaller table altogether.

Pattern 02

Predicates that make an existing index unusable

An index only helps if the optimiser can seek on it, and wrapping the indexed column in an expression prevents that. Date arithmetic applied around the column in a WHERE clause is the usual offender. Rewriting so the column sits bare on one side of the comparison and the arithmetic moves to the constant makes the same index usable again — with no schema change at all.

The close relative: scalar functions in a SELECT list, which silently force row-by-row execution across the whole result. Inlining them is ugly and fast, and ugly-and-fast is the correct trade in a report nobody reads the source of.

Both of these are worth trying first for a reason that has nothing to do with elegance: they touch nothing the vendor owns.

The method

StepWhat it establishes
Reproduce the symptom against real data, at each period settingThat the complaint is real, specific and repeatable — and which setting is the boundary. A complaint you cannot reproduce is not yet a problem you can fix.
Capture the baseline before touching anythingRuntime at each setting, logical reads, table sizes, and the current index state. You cannot go back for this. Every engagement that skipped it ended up unable to prove its own result.
Read the plan, not the queryWhether the expensive operator is a scan where a seek was expected, and which predicate is responsible. This is the step that separates a diagnosis from a guess.
Try the changes that touch nothing firstPredicate rewrites and function inlining need no permission, no window, and no rollback plan.
Design the index, then negotiate itKey columns in the right order, included columns to cover the query, and an honest estimate of the write cost it adds. Then take it to whoever owns the platform.
Apply it in a window, and measure againSame measurements, same conditions, compared against the baseline — and the build duration recorded so the next window can be planned rather than guessed.

Measure logical reads, not seconds. Runtime moves with server load, cache state and whoever else is on the system, so the same query can look twice as fast on a quiet afternoon and you will believe you fixed something. Logical reads count the pages actually touched and do not move with load. It is the honest number, and it is the one to put in front of anyone who has to approve the change.

Why the fix needs more care than the diagnosis

In ERP work the tables shipped with the product. That single fact drives everything about how a change gets applied:

When it genuinely is volume

Sometimes the access path is correct and the table really is too big for the question being asked. That case is recognisable rather than assumed: the plan already shows a seek, logical reads are proportionate to rows actually returned, and the report is slow in a way that scales smoothly rather than falling off a cliff at a particular date range.

That is a different engagement — archiving strategy, pre-aggregation, or moving reporting off the transactional system. It is worth naming here because jumping to it first is the expensive mistake, and it is the one an outside firm has the most incentive to recommend.

Worth measuring if you run this

There are no percentages on this page. A figure is only worth quoting if somebody captured the before state, and in most operational work nobody did. These are the numbers that make the work assessable:

Frequently asked questions

Is a slow report always a missing index?

No, but an unindexed filter column is the most common single cause in ERP reporting and the most consistently misdiagnosed, because the symptom scales with the date range and therefore looks like data volume. Check the access path before assuming volume.

Why measure logical reads instead of runtime?

Runtime moves with server load, cache state and who else is on the system, so the same query can look twice as fast on a quiet afternoon. Logical reads count pages actually touched and do not move with load, which makes them the honest before-and-after measure.

Can you tune without adding indexes?

Often yes. Rewriting a predicate so the indexed column appears bare on one side restores index usability with no schema change at all, and inlining a scalar function removes row-by-row execution. Those are usually tried first precisely because they touch nothing the vendor owns.

How long does a diagnosis take?

Confirming whether a specific report is an access-path problem is usually about ten minutes against a restore or replica. Designing and safely applying the fix takes longer, and most of that time is scheduling rather than work.

Will this void our ERP support?

That is the platform owner's call, not ours, which is exactly why the conversation happens before anything is applied. Many vendors permit added indexes; some do not. Predicate rewrites and reporting-layer changes avoid the question entirely.

Related

Got a report that dies on longer date ranges?

Bring the report and the period setting where it falls over. Confirming whether it is an access-path problem takes about ten minutes, and that conversation costs nothing.

Book a call · Read the worked example