Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A `SetRange` or `SetFilter` placed before `FindSet` narrows the result set at th

## Best Practice

Move every predicate that can be expressed as an equality or range filter into a `SetRange` or `SetFilter` ahead of the find. Combine with `SetCurrentKey` to choose a key whose first fields match the filter (see `setcurrentkey-aligns-key-with-filters.md`). The loop body should then contain only the work that depends on per-row state.
Move every predicate that can be expressed as an equality or range filter into a `SetRange` or `SetFilter` ahead of the find. Make sure a key (index) exists whose leading fields cover the filter so the optimizer can seek; note that `SetCurrentKey` only sets sort order and is not an index hint (see `setcurrentkey-sets-sort-order-not-index-hint.md`). The loop body should then contain only the work that depends on per-row state.

See sample: `apply-filters-before-iterating.good.al`.

Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
codeunit 50231 "Perf Sample SetCurrentKey Bad"
{
// Misconception: SetCurrentKey does NOT tell SQL Server to use this index.
// The optimizer picks the index from the filters (WHERE clause) and statistics.
// The result order is never used here, so SetCurrentKey only adds an ORDER BY
// the query does not need — and can push the plan toward a sort.
procedure SumRemainingAmount(CustomerNo: Code[20]) Total: Decimal
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetCurrentKey("Customer No.", Open, "Posting Date");
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
CustLedgerEntry.SetRange(Open, true);
CustLedgerEntry.SetAutoCalcFields("Remaining Amount");
if CustLedgerEntry.FindSet() then
repeat
Total += CustLedgerEntry."Remaining Amount";
until CustLedgerEntry.Next() = 0;
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
codeunit 50230 "Perf Sample SetCurrentKey Good"
{
// SetCurrentKey is used because the rows must be processed oldest-first.
// The sort is a functional requirement, so the ORDER BY it adds is justified.
procedure ApplyOldestEntriesFirst(CustomerNo: Code[20])
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetRange("Customer No.", CustomerNo);
CustLedgerEntry.SetRange(Open, true);
CustLedgerEntry.SetCurrentKey("Posting Date");
if CustLedgerEntry.FindSet() then
repeat
// Apply entries in posting-date order ...
until CustLedgerEntry.Next() = 0;
end;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
bc-version: [all]
domain: performance
keywords: [setcurrentkey, sort, order-by, index, key, query-optimizer, hint]
technologies: [al]
countries: [w1]
application-area: [all]
---

# SetCurrentKey only sets sort order — it is not an index hint

## Description

A common misconception is that `SetCurrentKey` tells SQL Server which index to use for a query. It does not. In Business Central, `SetCurrentKey` only changes the `ORDER BY` clause of the generated SQL statement. It does not add an index hint, and the SQL Server query optimizer is free to ignore the named key entirely.

The optimizer picks the index from the `WHERE` clause (your `SetRange`/`SetFilter`) together with table statistics and estimated cost. In practice it almost never chooses an index just because that key appears in `ORDER BY`. So calling `SetCurrentKey` to "steer" the plan toward an index is a no-op for index selection — and can make things worse: an `ORDER BY` that the query does not otherwise need can push the optimizer toward a less selective index or add a Sort operator to the plan.

Selectivity comes from having the right index available (a key on the table whose leading fields cover the filter) and from filtering on those fields — not from `SetCurrentKey`.

## Best Practice

Decide `SetCurrentKey` on one question only: **do I need the result set in a specific order?**

- If yes — you iterate rows in a defined sequence, or rely on `FindFirst`/`FindLast`/`Next` returning a particular row — call `SetCurrentKey` for that sort. The order is a functional requirement, and the `ORDER BY` is justified.
- If no — omit `SetCurrentKey`. Let the optimizer choose the cheapest plan for your filters; it may pick a better index and skip a sort.

To make a filtered read fast, ensure a key (index) exists on the table whose leading fields cover the filter, and filter on those fields with `SetRange`/`SetFilter`. That is what lets the optimizer seek. Defining the key creates the index; `SetCurrentKey` is not required to make the optimizer use it.

See sample: `setcurrentkey-sets-sort-order-not-index-hint.good.al`.

## Anti Pattern

Adding `SetCurrentKey` to a filtered read purely in the belief that it forces SQL Server to seek a particular index, when the code never uses the resulting order. This does nothing for index selection and only appends an `ORDER BY` the query does not need, risking an unnecessary sort. Remove the `SetCurrentKey`; rely on the filters and an existing covering key instead.

See sample: `setcurrentkey-sets-sort-order-not-index-hint.bad.al`.