Select-Statement Best Practices in Dynamics 365 Finance & Operations
Question 26 — Writing efficient X++ SQL: field lists, firstOnly, group by, exists join and clean where clauses.
Interview Question
firstOnly, group by,
exists join, and why you must avoid method calls in the where clause
and nested selects in loops.
Model Answer (Short)
Efficient X++ selects fetch only the fields you need (a column/field list before the
from), use firstOnly when exactly one record is expected, and use
group by with aggregate functions instead of manual counting. Use
exists join / notexists join when you only need to test for related
records (no field list required there). Crucially, never call a method inside the where
clause — it runs row by row and kills performance — and avoid nested selects inside loops;
convert them to joins or a pre-aggregated view. These rules minimise data transfer and AOS↔SQL
round trips.
Core Best Practices
Field lists & firstOnly
- Specify a field list before
fromwhen a query returns many rows — fetch only the columns you use. - Use
firstOnlywhenever exactly one record is expected, so SQL stops after the first match. - Loop with
while selectwhen multiple rows are expected. - Field lists are not needed for
exists join/notexists join.
Aggregation & joins
- Add
group byto use aggregate functions likesum(),count(). - To join to aggregated data, build a separate aggregated view and join it.
- Prefer a single join over issuing a query for every row inside a loop.
Prerequisites (Rule 5)
- Visual Studio with the Dynamics 365 developer tools.
- A custom model / package for your objects.
- Appropriate indexes supporting your where and order-by fields.
Code Example — Field list + firstOnly
// firstOnly + field list: fetch just what you need, stop at first match
protected void execute(SampleId _sampleId)
{
SampleTable sampleTable;
select firstonly SampleId, SampleValue, SampleStatus from sampleTable
where sampleTable.SampleId == _sampleId;
// business logic
}
group by with an aggregate
// Sum LineAmount per SalesId using group by + join
protected void execute()
{
SalesTable salesTable;
SalesLine salesLine;
while select SalesId from salesTable
group by salesTable.SalesId
join sum(LineAmount) from salesLine
where salesLine.SalesId == salesTable.SalesId
{
info(strFmt("%1 : %2", salesTable.SalesId, salesLine.LineAmount));
}
}
exists join (test for related rows)
// No field list needed on the exists join data source
protected void execute()
{
SalesTable salesTable;
SalesLine salesLine;
while select SalesId from salesTable
exists join salesLine
where salesLine.SalesId == salesTable.SalesId
&& salesLine.SalesStatus == SalesStatus::Invoiced
{
// business logic
}
}
Avoid: method call in where / nested select in loop
// BAD: method call runs row by row -> performance hit
while select SalesId from salesTable
where salesTable.CreatedDateTime >= AbcHelper::getOperationDate() // avoid
{ }
// GOOD: evaluate once, then use the value
AbcOperationDate operationDate = AbcHelper::getOperationDate();
while select SalesId from salesTable
where salesTable.CreatedDateTime >= operationDate
{ }
// BAD: nested select inside the loop (query per row)
while select SalesId from salesTable
{
select firstonly SalesStatus from salesLine
where salesLine.SalesId == salesTable.SalesId; // avoid
}
// GOOD: join / aggregated view instead (see examples above)
Do vs. Avoid
| Situation | Do | Avoid |
|---|---|---|
| Fetching data | Field list of only needed columns | Selecting the whole record unnecessarily |
| Single record | firstOnly |
Looping when one row is expected |
| Related-row test | exists join / notexists join |
Nested select in a loop |
| where clause | Pre-computed variables | Method calls inside where |
| Aggregation | group by / aggregated view |
Manual counting in X++ |
Points the interviewer wants to hear
- Use a field list to fetch only required columns.
- Use
firstOnlyfor single-record reads. - Use
group by+ aggregates; join an aggregated view when needed. - Never call methods in a
whereclause. - Replace nested selects in loops with joins or
exists join.
Likely Follow-up Questions
- Why does a method call in the
whereclause hurt performance? - When do you not need a field list?
- How do you aggregate and join without a nested select?
- What is the difference between
exists joinand an inner join?
Key Takeaway
Write lean selects: field lists for only needed columns, firstOnly
for single reads, group by for aggregation, and exists join
to test relationships. Keep methods out of the where clause and turn
nested selects into joins for maximum performance.