Apex SOQL is the use of Salesforce Object Query Language within Apex code to retrieve records from Salesforce objects. A SOQL query identifies the object to query, the fields to return, and any conditions used to filter, sort, group, or limit the results.
SOQL resembles SQL, but it is designed for the Salesforce object model. It works with objects, fields, and relationships rather than directly querying database tables and columns.
Apex SOQL query structure
A basic SOQL query contains a SELECT clause and a FROM clause. Optional clauses can filter, group, sort, and limit the returned records.
SELECT field_list
FROM object_name
WHERE filter_condition
GROUP BY grouping_field
HAVING aggregate_condition
ORDER BY sorting_field ASC|DESC
LIMIT number_of_records
OFFSET number_of_records
Only SELECT and FROM are required. The other clauses are included when the query needs them.
Writing a basic SOQL query in Apex
Static SOQL is written inside square brackets in Apex. The query result is assigned to an sObject variable or a collection.
List<Account> accounts = [
SELECT Id, Name, Industry
FROM Account
];
This query returns the selected fields from Account records. SOQL does not automatically retrieve every field. Each field required by the Apex code should be named in the SELECT clause.
The records can then be processed with a loop:
List<Account> accounts = [
SELECT Id, Name, Industry
FROM Account
ORDER BY Name
];
for (Account accountRecord : accounts) {
System.debug(accountRecord.Name + ' - ' + accountRecord.Industry);
}
Filtering Apex SOQL records with WHERE
The WHERE clause restricts the result to records matching specified conditions. The fields used in the filter do not always need to appear in the selected field list.
SELECT Id, Name, AnnualRevenue
FROM Account
WHERE Industry = 'Technology'
ORDER BY AnnualRevenue DESC
LIMIT 20
Common SOQL comparison and logical operators include:
| Operator | Purpose | Example |
|---|---|---|
= | Equal to | Industry = 'Banking' |
!= | Not equal to | Status__c != 'Closed' |
>, >= | Greater than or greater than/equal to | Amount >= 50000 |
<, <= | Less than or less than/equal to | CloseDate < TODAY |
LIKE | Pattern matching with wildcards | Name LIKE 'Acme%' |
IN | Match any value in a collection | Industry IN ('Energy', 'Utilities') |
NOT IN | Exclude values in a collection | Status__c NOT IN ('Closed', 'Cancelled') |
INCLUDES | Match values in a multi-select picklist | Services__c INCLUDES ('Support') |
AND | Require both conditions | IsActive__c = true AND Region__c = 'South' |
OR | Require either condition | Rating = 'Hot' OR Rating = 'Warm' |
Use parentheses when combining AND and OR conditions so that the intended evaluation order is clear.
SELECT Id, Name, Rating
FROM Account
WHERE IsActive__c = true
AND (Rating = 'Hot' OR Rating = 'Warm')
Using Apex bind variables in SOQL
A static SOQL query can use an Apex variable by placing a colon before the variable name. This is called a bind expression. Bind expressions keep the query readable and avoid manually inserting Apex values into a query string.
String selectedIndustry = 'Healthcare';
Decimal minimumRevenue = 1000000;
List<Account> accounts = [
SELECT Id, Name, Industry, AnnualRevenue
FROM Account
WHERE Industry = :selectedIndustry
AND AnnualRevenue >= :minimumRevenue
ORDER BY AnnualRevenue DESC
];
Collections can also be bound to an IN condition. This is useful when querying records related to a set of IDs.
Set<Id> accountIds = new Set<Id>();
for (Account accountRecord : Trigger.new) {
accountIds.add(accountRecord.Id);
}
List<Contact> contacts = [
SELECT Id, FirstName, LastName, AccountId
FROM Contact
WHERE AccountId IN :accountIds
];
Querying a single Salesforce record in Apex
A SOQL result can be assigned directly to one sObject variable. However, direct assignment throws a QueryException when the query returns no records or more than one record.
Account accountRecord = [
SELECT Id, Name
FROM Account
WHERE Id = :accountId
LIMIT 1
];
When a record might not exist, query into a list and check whether the list is empty:
List<Account> matchingAccounts = [
SELECT Id, Name
FROM Account
WHERE External_Id__c = :externalId
LIMIT 1
];
if (!matchingAccounts.isEmpty()) {
Account accountRecord = matchingAccounts[0];
System.debug(accountRecord.Name);
}
LIMIT 1 restricts the number of returned rows, but it does not establish which matching record is selected. Add an appropriate ORDER BY clause when the choice must be deterministic.
Apex SOQL relationship queries
SOQL follows relationships defined in the Salesforce data model. A child-to-parent query retrieves fields from a related parent record with dot notation.
Child-to-parent SOQL query
SELECT Id, FirstName, LastName, Account.Name, Account.Industry
FROM Contact
WHERE Account.Industry = 'Manufacturing'
Here, Contact is the child object and Account is the parent. Apex can access the selected parent value through contactRecord.Account.Name, provided the relationship exists.
Parent-to-child SOQL subquery
A parent-to-child query uses a nested query and the child relationship name. The relationship name is not always identical to the child object’s API name.
SELECT Id, Name,
(SELECT Id, FirstName, LastName, Email
FROM Contacts
ORDER BY LastName)
FROM Account
WHERE Industry = 'Education'
For custom relationships, the parent reference commonly uses a name ending in __r, and a custom child relationship uses the relationship name configured on the field. Schema describe information or a query-building tool can be used to confirm the correct relationship name.
Apex SOQL aggregate queries and COUNT
Aggregate functions summarize records instead of returning each record individually. SOQL supports functions such as COUNT(), COUNT(fieldName), COUNT_DISTINCT(), SUM(), AVG(), MIN(), and MAX().
SELECT StageName, COUNT(Id) opportunityCount, SUM(Amount) totalAmount
FROM Opportunity
WHERE IsClosed = false
GROUP BY StageName
HAVING COUNT(Id) > 2
ORDER BY SUM(Amount) DESC
Grouped aggregate queries return AggregateResult values in Apex. Aliases make those values easier to retrieve.
List<AggregateResult> summaries = [
SELECT StageName stage,
COUNT(Id) opportunityCount,
SUM(Amount) totalAmount
FROM Opportunity
WHERE IsClosed = false
GROUP BY StageName
];
for (AggregateResult summary : summaries) {
String stage = (String) summary.get('stage');
Integer recordCount = (Integer) summary.get('opportunityCount');
Decimal totalAmount = (Decimal) summary.get('totalAmount');
System.debug(stage + ': ' + recordCount + ', ' + totalAmount);
}
For a simple count, the result can be assigned directly to an integer:
Integer openOpportunityCount = [
SELECT COUNT()
FROM Opportunity
WHERE IsClosed = false
];
Semi-joins and anti-joins in SOQL
A semi-join returns records whose IDs appear in a related subquery. For example, the following query returns accounts that have at least one open opportunity:
SELECT Id, Name
FROM Account
WHERE Id IN (
SELECT AccountId
FROM Opportunity
WHERE IsClosed = false
)
An anti-join uses NOT IN. The next query returns accounts that do not have a contact:
SELECT Id, Name
FROM Account
WHERE Id NOT IN (
SELECT AccountId
FROM Contact
WHERE AccountId != null
)
Static SOQL and dynamic SOQL in Apex
Static SOQL is compiled with the Apex class and is suitable when the selected fields, object, and query structure are known in advance. Dynamic SOQL constructs a query as a string and runs it at runtime. Dynamic queries are useful when fields, filters, or sorting options depend on runtime configuration.
| SOQL type | Typical form | Use it when |
|---|---|---|
| Static SOQL | [SELECT Id FROM Account] | The query structure is known when the Apex code is compiled. |
| Dynamic SOQL | Database.query(queryString) or a bind-aware database method | The query structure must be assembled at runtime. |
The following example selects an optional field after confirming that the requested field exists on Account:
String requestedField = 'Industry';
Map<String, Schema.SObjectField> accountFields =
Schema.SObjectType.Account.fields.getMap();
if (!accountFields.containsKey(requestedField)) {
throw new IllegalArgumentException('Unknown Account field');
}
String queryText =
'SELECT Id, Name, ' + requestedField +
' FROM Account ORDER BY Name LIMIT 50';
List<SObject> records = Database.query(queryText);
Object names, field names, and sort directions cannot be treated like ordinary value binds. If they are selected dynamically, compare them with an explicit allowlist or validate them with schema describe information before adding them to a query.
Dynamic SOQL with an IN clause
For a static query, bind a collection directly instead of building a comma-separated list:
Set<String> selectedIndustries = new Set<String>{
'Energy',
'Utilities'
};
List<Account> accounts = [
SELECT Id, Name, Industry
FROM Account
WHERE Industry IN :selectedIndustries
];
When the complete query must be dynamic, bind-aware database methods can keep values separate from the query string:
Set<String> accountNames = new Set<String>{
'Acme Services',
'Northwind Retail'
};
String queryText =
'SELECT Id, Name FROM Account WHERE Name IN :names';
Map<String, Object> bindValues = new Map<String, Object>{
'names' => accountNames
};
List<SObject> records = Database.queryWithBinds(
queryText,
bindValues,
AccessLevel.USER_MODE
);
The availability and behavior of individual database methods can depend on the Apex API version. Check the current official Apex reference when maintaining code compiled with an older API version.
Preventing SOQL injection in Apex
SOQL injection can occur when untrusted text is concatenated directly into a dynamic query. Prefer static SOQL with bind variables. For dynamic SOQL, use bind-aware methods for values and allowlist any field names, object names, or sort directions that must become part of the query structure.
Avoid code that directly inserts a user-supplied search value:
// Unsafe: searchText becomes part of the SOQL statement.
String queryText =
'SELECT Id, Name FROM Account WHERE Name = \'' +
searchText + '\'';
Use a bind expression when the query structure is fixed:
List<Account> accounts = [
SELECT Id, Name
FROM Account
WHERE Name = :searchText
];
Escaping quotes can be relevant in narrowly defined dynamic-query cases, but it is not a substitute for value binding, structural validation, and appropriate access controls.
SOQL access control in Apex
A technically correct query is not automatically a complete security design. Apex developers must consider record sharing, object permissions, field permissions, and the execution mode used by the code.
- Use an appropriate class sharing declaration, such as
with sharing, when record-level sharing must be enforced. - Evaluate user-mode database operations when the operation should enforce the running user’s permissions and sharing.
- Check object and field access when code could expose or modify fields the user is not permitted to use.
- Do not return sensitive queried fields to a client merely because Apex was able to retrieve them.
Sharing declarations primarily address record access. Object-level and field-level access require separate consideration unless the selected operation explicitly enforces them.
Bulk-safe Apex SOQL patterns
Apex transactions are subject to governor limits, including limits related to SOQL queries and returned rows. Queries should normally be placed outside loops and should retrieve all records needed for the current batch.
The following pattern is inefficient because it runs a separate query for each account:
// Avoid SOQL inside a loop.
for (Account accountRecord : Trigger.new) {
List<Contact> contacts = [
SELECT Id, Email
FROM Contact
WHERE AccountId = :accountRecord.Id
];
}
A bulk-safe version collects IDs and runs one query:
Set<Id> accountIds = Trigger.newMap.keySet();
Map<Id, List<Contact>> contactsByAccountId =
new Map<Id, List<Contact>>();
for (Contact contactRecord : [
SELECT Id, Email, AccountId
FROM Contact
WHERE AccountId IN :accountIds
]) {
if (!contactsByAccountId.containsKey(contactRecord.AccountId)) {
contactsByAccountId.put(
contactRecord.AccountId,
new List<Contact>()
);
}
contactsByAccountId.get(contactRecord.AccountId).add(contactRecord);
}
Bulkification reduces query consumption and supports transactions containing multiple records. Query only the fields and records required by the business logic, but do not assume that LIMIT alone fixes an incorrectly designed bulk process.
Handling large Apex SOQL result sets
An Apex SOQL for loop can process query results in batches managed by the platform. It is useful when code needs to iterate through a result without first assigning the entire result to a list.
for (Account accountRecord : [
SELECT Id, Name
FROM Account
WHERE IsActive__c = true
]) {
System.debug(accountRecord.Name);
}
This syntax does not remove transaction limits. Operations involving data volumes beyond a synchronous transaction’s capacity may require Batch Apex, Queueable Apex, pagination, or another asynchronous design.
SOQL date literals and date filters
SOQL provides date literals for relative date filters. They make common time-based conditions easier to read.
SELECT Id, Subject, ActivityDate
FROM Task
WHERE ActivityDate = TODAY
ORDER BY ActivityDate
SELECT Id, Name, CreatedDate
FROM Account
WHERE CreatedDate = LAST_N_DAYS:30
Other available literals cover periods such as weeks, months, quarters, fiscal periods, and years. Confirm the literal’s boundary and timezone behavior when date precision affects the business result.
SOQL vs SQL vs SOSL
| Language | Primary purpose | How it works |
|---|---|---|
| SOQL | Retrieve structured Salesforce records | Queries one primary object and can traverse defined object relationships. |
| SQL | Query relational databases | Works with database tables and supports relational operations defined by the database system. |
| SOSL | Search text across multiple Salesforce objects | Searches configured searchable fields and can return grouped results from several objects. |
SOQL does not support arbitrary table joins in the same way as SQL. Relationships must be defined in the Salesforce schema. Use SOQL when the required object and relationships are known. Consider SOSL when the requirement is to search text across several objects or fields.
Where to run Salesforce SOQL queries
SOQL can be executed in several Salesforce development and administration contexts:
- Apex classes and triggers: Use static or dynamic SOQL as part of application logic.
- Developer Console Query Editor: Run and inspect queries against an authorized Salesforce organization.
- Execute Anonymous: Test Apex statements containing SOQL in a non-production environment.
- Salesforce CLI and development tools: Execute queries from a configured command-line or editor workflow.
- Salesforce APIs: Submit SOQL through supported API query resources.
Use test or development data when learning. A read-only query does not perform DML, but executing unfamiliar Apex around the query can still change data.
Common Apex SOQL errors
| Problem | Likely cause | Correction |
|---|---|---|
| Queried field cannot be accessed in Apex | The field was not included in the SELECT clause. | Add the required field to the query. |
| No rows for assignment | A query assigned directly to one sObject returned no records. | Query into a list and check whether it is empty. |
| More than one row for assignment | A single-sObject assignment returned multiple records. | Make the filter unique or use a list. |
| Invalid relationship name | The query used an object name instead of the configured relationship name. | Check schema describe information or a query builder for the relationship API name. |
| Too many SOQL queries | A query runs repeatedly, often inside a loop. | Collect IDs and retrieve related records with one bulk query. |
| Unexpected dynamic-query behavior | Untrusted or incorrectly escaped text was concatenated into the query. | Use bind values and allowlist structural query elements. |
| Records are visible that should be restricted | The code’s execution and sharing behavior were not designed for the use case. | Review sharing, user mode, CRUD, field access, and the data returned to callers. |
Apex SOQL best practices
- Select only fields required by the current operation.
- Use selective filters and suitable indexed fields when data volume makes query performance significant.
- Place SOQL outside record-processing loops.
- Use collections and
INfilters for bulk operations. - Prefer static SOQL and bind expressions when the query structure is known.
- Use bind-aware methods and allowlists when dynamic SOQL is necessary.
- Add
ORDER BYwhen record order affects processing. - Do not rely on implicit result order.
- Handle empty results when a matching record is not guaranteed.
- Test with multiple records, no matching records, restricted users, and realistic data volumes.
- Review record sharing, object permissions, and field permissions separately.
- Check query plans and official performance guidance before changing a production query solely for perceived optimization.
The official Apex SOQL documentation describes SOQL use in Apex. Salesforce also provides a guided Trailhead module for SOQL queries in Apex.
Apex SOQL FAQs
What is SOQL in Apex?
SOQL is Salesforce Object Query Language. Apex uses it to retrieve records and selected fields from Salesforce objects. Static queries are enclosed in square brackets, while dynamic queries are executed from a query string through an appropriate database method.
How is SOQL different from SQL?
SQL queries relational database tables. SOQL queries Salesforce objects and follows relationships defined in the Salesforce schema. SOQL does not provide arbitrary joins or wildcard field selection equivalent to common SQL patterns such as SELECT *.
Can SOQL query all Salesforce fields with SELECT *?
SOQL does not use the general SQL SELECT * pattern. Name the fields required by the operation. Some contexts support field-grouping features, but explicit field lists remain clearer and help prevent unnecessary data retrieval.
How do I use a list or set in a SOQL IN clause?
Create an Apex collection and bind it with a colon, such as WHERE AccountId IN :accountIds. For a fully dynamic query, use a supported bind-aware database method instead of manually joining untrusted values into the SOQL string.
When should Apex use SOSL instead of SOQL?
Use SOQL when the object, fields, and relationships to retrieve are known. Use SOSL when the requirement is a text search across multiple objects or multiple searchable fields and the matching object may not be known in advance.
Apex SOQL tutorial editorial QA checklist
- Verify that every SOQL example uses valid object, field, relationship, and clause syntax.
- Confirm that every field read by Apex appears in the corresponding
SELECTclause. - Check that single-record examples explain the behavior of zero-row and multiple-row results.
- Ensure that collection filters use Apex bind expressions instead of manually assembled value lists.
- Review trigger examples for SOQL inside loops and other non-bulk-safe patterns.
- Check dynamic SOQL examples for value binding, structural allowlisting, and injection risk.
- Confirm that sharing, object permissions, and field permissions are described as separate access-control concerns.
- Verify relationship names, especially custom
__rrelationships, against the target organization’s schema. - Check current database methods, access-level options, governor limits, and API-version behavior against official Salesforce documentation.
- Test queries with matching records, empty results, bulk input, null relationship values, and restricted user access.
TutorialKart.com