What are the best practices you follow while creating a query rule in Appian?
What are the best practices you follow while creating a query rule in Appian?
Answer
When creating query rules in Appian, I follow several best practices so they are efficient, reusable, and easy to maintain as the application grows.
My primary goal is to retrieve only the required data, avoid unnecessary database calls, and design query rules that can be reused across interfaces, expression rules, and process models.
In modern Appian applications, prefer a!queryRecordType() for querying Record Types and use a!queryEntity() when direct Data Store Entity access is required.
1.Apply filters at the query level
I apply filters within the query instead of retrieving all records and filtering them afterward with Appian expressions. That keeps unnecessary data retrieval and application-side processing to a minimum.
For example, if an Employee Record Type contains 100,000 records but the interface needs only active employees, I apply the status filter directly.
Code example:
Best practices:
- Filter records within
a!queryRecordType()ora!queryEntity(). - Use
a!queryLogicalExpression()for multiple conditions. - Use appropriate operators based on the field’s data type.
- Validate mandatory filter values before executing the query.
2. Retrieve only the required fields
I don’t retrieve every column when an interface needs only a few fields. If an Employee Record Type contains 20 fields but the interface displays Employee ID, Name, and Department, I select only those three. That cuts processing and improves query performance.
Code example:
Best practices:
- Explicitly select required fields.
- Avoid
a!selectionFields()when all fields are unnecessary. - Avoid unnecessarily fetching large text fields and expensive real-time custom record fields.
- Pass selection fields as a rule input when different callers require different columns.
3. Use proper pagination and batch size
Pagination is essential when retrieving large datasets, and how it is handled depends on how the data is used.
Scenario A: Record-backed read-only grid
When a read-only grid directly uses a Record Type or a!recordData(), Appian manages pagination and sorting automatically. We only configure the grid’s pageSize.
Code example:
Here:
- The grid displays 20 records per page.
- Appian manages paging when users navigate between pages.
- The grid manages sorting.
- No manual
startIndexorbatchSizeis required.
Scenario B: Query rule used outside a grid
If the query is used in an expression rule, process model, dropdown preparation, or other custom logic, I control pagination with a!pagingInfo().
Best practices:
- Let records-powered grids manage their own pagination.
- Use
pageSizeina!gridField()to control visible rows. - Use
a!pagingInfo()for independent query operations. - Parameterize batch size when different callers need different numbers of records.
- Avoid retrieving unnecessarily large datasets.
Important: a!queryRecordType() supports a maximum batch size of 5,000 and does not support batchSize: -1.
4. Avoid unnecessary total count queries
Calculating the total number of matching records adds processing overhead. If a query is used outside a grid and the caller only needs the returned data, I disable total count retrieval.
Code example:
Best practices:
- Use
fetchTotalCount: falsewhen the count is unnecessary. - Enable it when a caller requires the total number of matching records.
- Pass
fetchTotalCountas a Boolean rule input for reusable queries.
Important: For a records-powered grid using a!recordData(), the grid manages its paging and count requirements. We do not manually configure fetchTotalCount inside a!recordData().
For a grid powered by a DataSubset returned from a query, the total count is generally needed for paging controls.
5. Make query rules reusable using parameters
I create reusable query rules rather than separate ones for each requirement. Callers control selection fields, batch size, pagination, filters, and total count.
Example parameters:
For example:
- A dropdown may request only ID and Name with 100 records.
- A dashboard may request Status and Department with 20 records.
- A process model may retrieve a single record without calculating the total count.
All three callers can use the same query rule with different inputs.
Best practices:
- Parameterize frequently changing query requirements.
- Use meaningful rule input names.
- Avoid hardcoding selection fields and batch sizes when reusability is needed.
- Keep the rule focused and avoid unnecessary parameters.
The final practical example has a complete reusable query rule.
6. Minimize unnecessary database calls
I avoid repeatedly executing queries when the required information can be retrieved together. For example, if I need five employees by ID, I fetch them in one query instead of calling a query rule five times inside a!forEach().
Code example:
Assume ri!employeeIds contains {101, 102, 103, 104, 105}.
This retrieves the matching employees in a single query.
Best practices:
- Avoid unnecessary queries inside
a!forEach(). - Retrieve multiple records together when appropriate.
- Reuse already-retrieved data.
- Avoid unnecessary interface refreshes.
- Use relationships when accessing related information.
7. Use Record Type Relationships and Data Sync appropriately
In modern Appian development, I evaluate Record Type Relationships before creating database views solely to join related data.
For example, suppose a Customer Record Type has a one-to-many relationship with Support Ticket records. Rather than retrieving every support ticket for each customer, I can retrieve only the latest three.
Code example:
This can be used in the relatedRecordData parameter of an appropriate record query or records-powered grid. The example assumes the Customer-to-SupportTicket relationship has already been configured.
Best practices:
- Use Record Type Relationships when appropriate.
- Retrieve only necessary related fields.
- Limit one-to-many related data.
- Use Data Sync when suitable for the application’s requirements.
- Apply sync filters to high-volume Record Types when appropriate.
- Avoid expensive real-time custom fields unless they are needed.
Record Type Relationships are not always faster than database views. The best choice depends on the data source and query requirements.
8. Optimize database indexes
When working with a!queryEntity() or unsynced database-backed Record Types, I check whether appropriate database indexes exist. For example, if users frequently search employees by username, an index may improve query performance.
SQL example:
Best practices:
- Review indexes on frequently filtered columns.
- Consider indexes for joins and sorting.
- Analyze database execution plans before adding indexes.
- Avoid creating unnecessary indexes because they increase storage and database write overhead.
For synced Record Types, Appian uses its optimized data service for reads, so source database indexes do not directly improve queries against already-synced data.
9. Monitor and test query performance
I test query rules with realistic data volumes and different input values. Appian provides Query Editor, Performance Details, and Query Performance monitoring to help identify slow queries.
Example: inspect a query result
This shows whether the query succeeded and how many records came back. It does not measure execution time, so for timing and bottlenecks I use Appian’s built-in performance tools. I compare query execution times before and after optimization.
10. Follow naming conventions and validate inputs
I use meaningful rule names and validate mandatory inputs to prevent unexpected query behavior. For example, if employee status is required, I avoid executing a broad query when the status is missing.
Code example:
If the status is missing, the caller returns an empty list instead of executing the query.
Best practices:
- Follow naming conventions such as
APP_QR_GetEmployees. - Define appropriate rule input types.
- Validate mandatory inputs.
- Return consistent result structures.
- Keep query logic simple and maintainable.
Final practical example: an optimized, reusable query rule
Consider an Employee Management application where different components need employee information. We will implement two approaches:
- A reusable query rule for expression rules, process models, dropdown preparation, and other custom queries.
- A records-powered grid that manages its own pagination automatically.
Part A: Create a reusable query rule
Expression rule: APP_QR_GetEmployees
Configure these rule inputs:
Rule input | Data type | Purpose |
|---|---|---|
| Any Type | Fields to retrieve |
| Integer | Number of records |
| Integer | Starting position |
| Boolean | Whether total count is needed |
| Text | Mandatory status filter |
| Text | Optional department filter |
Final query rule code:
The maximum batch size of 100 is an application-specific safeguard. Appian’s own limit is 5,000. The validation branch returns a custom failure map with STATUS_REQUIRED, so callers should check success before using the data.
Part B: Calling the query rule from an expression
Suppose a dropdown requires 50 active employees from the IT department. It only needs ID and Name, and the total count is unnecessary.
Illustrative result, assuming 80 employees match the filters:
Property | Result |
|---|---|
| First 50 matching employees |
| 1 |
| 50 |
| Not calculated |
| true |
If the dropdown needs to offer all 80 employees, the caller must retrieve the additional results or use a searchable selection component with an appropriate data source.
Part C: Creating a records-powered grid
An Employee Grid doesn’t need the query rule above for pagination. It can use the Employee Record Type directly through a!recordData().
Final grid code:
How this grid works:
- Appian retrieves the records required for the displayed page.
- The grid displays 20 employees per page.
- When the user navigates between pages, Appian handles paging automatically.
- Sorting is managed through the grid.
- Filters are applied through
a!recordData(). - The grid retrieves only the fields configured in the
fieldsparameter. - No manual
startIndex,batchSize, orfetchTotalCountis needed.
Important: If a grid instead uses a reusable a!queryEntity() rule, Appian supports passing fv!pagingInfo to the rule’s PagingInfo input. This allows the grid to supply its page size and sorting information. For an a!queryRecordType() result used as grid data, follow Appian’s documented DataSubset conversion approach using a!dataSubset().
Summary: Choosing the appropriate approach
Requirement | Recommended approach |
|---|---|
Read-only grid using a Record Type |
|
Grid pagination | Let |
Query inside an expression rule |
|
Query inside a process model | Reusable query rule |
Dynamic field selection |
|
Dynamic batch size for custom queries |
|
Optional total count for custom queries |
|
Data Store Entity query used in a grid | Pass |
Large related datasets | Record Type Relationships with appropriate limits |
Interview takeaway
When creating query rules in Appian, I filter at the query level, retrieve only the necessary fields, paginate, skip unnecessary total count queries, and minimize repeated database calls.
I make reusable query rules configurable by passing selection fields, batch size, start index, and fetchTotalCount as rule inputs. Different callers can then customize their queries without changing the underlying expression rule.
For a read-only grid on a Record Type, I let Appian handle pagination through a!gridField() and use its built-in capabilities wherever possible, instead of managing paging variables manually.
I also consider Record Type Relationships, Data Sync, appropriate database indexes, and performance monitoring.
Official Appian documentation
Check yourself
Question 1 of 5, on what the answer above covers.
- 1.What batch size limits apply when you use a!queryRecordType()?
- 2.You are building a read-only grid directly on the Employee Record Type using a!recordData(). How should you handle pagination?
- 3.A dropdown calls your query rule and only needs the returned records, not the number of matches. What should you set?
- 4.You need the names of 5 employees whose IDs are in ri!employeeIds. What is the best approach?
- 5.In the reusable APP_QR_GetEmployees rule, the department filter uses applyWhen: a!isNotNullOrEmpty(ri!department). What happens when a caller passes department: null?