soql Query Guide | Syntax and Examples | SalesforceTutorial

Written by Prasanth Kumar Published on Updated on

Soql is the query language Salesforce provides for reading records from standard objects, custom objects, and related records. A SOQL query lets an admin, developer, or architect select fields, filter rows, sort results, count records, and fetch parent or child data without building a report type for every investigation.

This guide explains the syntax, the places where you can run queries, the difference between LIKE and a “contains” search, how the IN clause works, and how to write secure Apex that does not hit governor limits. The examples use standard objects where possible so you can paste them into a sandbox and adapt them to your org.

What is soql?

SOQL stands for Salesforce Object Query Language. It looks similar to SQL because it uses a SELECT statement, but it is built for Salesforce metadata and record relationships, not for direct database tables. The official Salesforce query reference describes SOQL as the language for constructing query strings against Salesforce data.

The first difference to learn is that SOQL reads from objects and fields. You query Account, Contact, Opportunity, User, PermissionSetAssignment, or a custom object such as Invoice__c. You select field API names such as Name, OwnerId, CloseDate, or Renewal_Status__c.

The language is read-focused. It does not insert, update, or delete records. Data changes happen through Apex DML, the User Interface API, REST API, Bulk API, Data Loader, Flow, or the Salesforce UI. A query gives those tools the record set they need.

How a query in Salesforce differs from a report

A report is better when business users need a saved view, chart, subscription, dashboard source, or export they can run without touching syntax. A query in Salesforce is better when you need a fast, exact record set, a relationship traversal that a report type does not expose, or a check against setup objects such as FieldPermissions.

Need Use a report when Use SOQL when
Business visibility Users need a shared view, chart, dashboard, or scheduled email. You need a technical investigation, migration extract, or Apex data access pattern.
Object relationships The report type already contains the objects and fields. You need parent fields, child subqueries, semi-joins, or setup objects.
Automation The result is mainly for humans. Apex, REST API, Salesforce CLI, or a deployment script needs records.
Performance control The dataset is small or report filters are enough. You need selective filters, limits, indexed fields, or query plan review.

In enterprise orgs, admins often use reports for recurring operational visibility and SOQL for one-time analysis, release validation, permission audits, and data cleanup preparation.

How to write a soql query

A basic query needs two parts: a field list and an object name. Most production work also needs a WHERE clause and a row limit. Salesforce documents this syntax with optional clauses such as WHERE, WITH, GROUP BY, ORDER BY, LIMIT, and OFFSET in the SELECT syntax documentation.

SELECT Id, Name, Industry, BillingCountry
FROM Account
WHERE BillingCountry = 'India'
ORDER BY Name ASC
LIMIT 100

This query reads four Account fields, returns only accounts with BillingCountry equal to India, sorts by account name, and stops after 100 rows. The field list should include only the fields your task needs. Selecting unnecessary fields increases response size and can make Apex heap usage worse.

How to write a soql query with SELECT and WHERE

The SELECT clause lists fields. The FROM clause names the object. The WHERE clause filters rows. The filter expression uses a field name, an operator, and a value.

SELECT Id, Name, StageName, Amount, CloseDate
FROM Opportunity
WHERE StageName = 'Prospecting'
AND Amount > 25000
AND CloseDate = THIS_QUARTER
ORDER BY CloseDate ASC
LIMIT 200

Date literals such as TODAY, YESTERDAY, LAST_N_DAYS:30, and THIS_QUARTER keep queries readable and avoid hardcoded calendar dates. Be careful with relative dates in scheduled jobs because the result changes with the job run date.

Where to run a query in Salesforce

You can run queries in several supported contexts. Developers usually run them in Apex, anonymous Apex, REST API calls, Salesforce CLI, or integration code. Admins often use Developer Console Query Editor, Salesforce CLI, or a browser extension in a sandbox. For repeatable work, prefer Salesforce CLI or Apex tests because those options can be reviewed and version-controlled.

  • Developer Console: Useful for quick validation in a sandbox, but not a deployment artifact.
  • Salesforce CLI: Useful for scripted checks, exports, CI validation, and repeatable admin work.
  • Apex: Required when a trigger, controller, service class, scheduled job, or LWC-backed method needs records.
  • REST API: Useful for external systems that need query results from Salesforce.

For related tooling on this site, see Salesforce Inspector for query exploration, Data Loader for Salesforce exports and imports, and Salesforce reports for user-facing analytics.

How soql contains works with LIKE

Many users search for soql contains because they expect an operator named CONTAINS. For normal text fields in a SOQL WHERE clause, use LIKE with wildcard characters instead. Salesforce documents the LIKE operator for string matching in its SOQL comparison operators documentation.

SELECT Id, Name, Website
FROM Account
WHERE Name LIKE '%cloud%'
LIMIT 50

The percent sign means zero or more characters. The query above returns accounts where the name has “cloud” anywhere in the value. Use a trailing wildcard for starts-with behavior:

SELECT Id, Name
FROM Account
WHERE Name LIKE 'Acme%'
ORDER BY Name
LIMIT 50

A leading wildcard such as '%cloud%' can be expensive on large objects because Salesforce has less help from indexes. In large data volume orgs, ask whether a starts-with search, an exact filter, a custom indexed field, SOSL, or a search-specific architecture would serve the user better.

Soql contains versus SOSL search

Use a contains-style LIKE filter when you know the object and field. Use SOSL when the user enters search text and you need to search across multiple objects or multiple text fields. For example, an internal account lookup can use SOQL against Account.Name, while a global search box across Accounts, Contacts, Leads, and Cases usually fits SOSL better.

Do not build query strings by concatenating raw user input. In Apex, bind variables avoid injection for values. If you must build a dynamic query because the field list or sort field changes at runtime, whitelist those identifiers before adding them to the query string.

How to use the soql in clause

The soql in clause checks whether a field value is in a list. It is common in admin queries, Apex triggers, controller methods, and integration filters.

SELECT Id, Name, BillingCountry
FROM Account
WHERE BillingCountry IN ('India', 'United States', 'United Kingdom')
ORDER BY BillingCountry, Name
LIMIT 200

In Apex, the safer and more common pattern is to collect IDs first and bind a Set<Id> into the query. This lets one query fetch all related rows for a batch of records.

public with sharing class ContactLookupService {
    public static Map<Id, List<Contact>> contactsByAccount(Set<Id> accountIds) {
        Map<Id, List<Contact>> results = new Map<Id, List<Contact>>();

        if (accountIds == null || accountIds.isEmpty()) {
            return results;
        }

        for (Contact c : [
            SELECT Id, FirstName, LastName, Email, AccountId
            FROM Contact
            WHERE AccountId IN :accountIds
            WITH USER_MODE
            ORDER BY LastName, FirstName
        ]) {
            if (!results.containsKey(c.AccountId)) {
                results.put(c.AccountId, new List<Contact>());
            }
            results.get(c.AccountId).add(c);
        }

        return results;
    }
}

This code runs one query for the whole set of account IDs. It also guards against a null or empty input so the transaction does not spend a query when no IDs exist.

Soql in clause with a subquery

The IN operator can also use a subquery. This pattern is called a semi-join. It returns records from one object based on matching records from another object.

SELECT Id, Name
FROM Account
WHERE Id IN (
    SELECT AccountId
    FROM Opportunity
    WHERE StageName = 'Closed Won'
    AND CloseDate = LAST_N_DAYS:90
)
ORDER BY Name

Use semi-joins when you need “accounts that have matching opportunities” without returning the child records themselves. If you need the child data in the result, use a relationship query instead.

How relationship queries work in SOQL

Salesforce relationships are part of metadata, so queries use relationship names instead of arbitrary SQL joins. The official relationship query documentation covers child-to-parent and parent-to-child traversal.

Child-to-parent query example

Child-to-parent traversal uses dot notation. From Contact, you can read fields from the parent Account through Account.Name and Account.Owner.Name, subject to relationship limits and permissions.

SELECT Id, FirstName, LastName, Email, Account.Name, Account.Owner.Name
FROM Contact
WHERE AccountId != null
AND Email != null
LIMIT 100

For custom lookup or master-detail fields, the relationship name usually ends in __r. If a Contact had a custom lookup field named Primary_Partner__c, the parent name might be queried through Primary_Partner__r.Name. Always confirm the relationship name in Object Manager, the schema describe result, or the generated WSDL because admins can define a custom child relationship name.

Parent-to-child query example

Parent-to-child traversal uses a subquery in the field list. From Account, the standard child relationship name for contacts is Contacts.

SELECT Id, Name,
    (SELECT Id, FirstName, LastName, Email
     FROM Contacts
     WHERE Email != null
     ORDER BY LastName
     LIMIT 10)
FROM Account
WHERE Type = 'Customer'
ORDER BY Name
LIMIT 50

Parent-to-child queries can return nested data that reports may not expose through an existing report type. In API version 58.0 and later, Salesforce supports parent-to-child relationship queries up to five levels deep. That does not mean every nested query is a good design. Deep nesting can produce large payloads, harder error handling, and more complicated UI state.

How to query fields without SELECT *

The language does not support SELECT *. When you need to inspect fields, use explicit field names or the FIELDS() function. The Salesforce docs show FIELDS(ALL), FIELDS(CUSTOM), and FIELDS(STANDARD). FIELDS(ALL) and FIELDS(CUSTOM) require a LIMIT of 200 rows.

SELECT FIELDS(STANDARD)
FROM Account
LIMIT 25
SELECT Id, Name, FIELDS(CUSTOM)
FROM Account
LIMIT 200

Use FIELDS() for exploration, not for application code that backs a component or integration. Production code should list fields so reviewers can verify security, heap impact, and API contract behavior.

How to secure SOQL in Apex

Apex data security has two separate concerns: record access and field/object access. The with sharing keyword helps with record-level sharing. It does not replace object permission or field-level security checks. Salesforce’s LWC security guidance recommends WITH USER_MODE for queries that back Apex controllers.

For Summer ’26 / API version 67.0 and later, Salesforce release notes state that Apex classes using WITH SECURITY_ENFORCED do not compile and should use WITH USER_MODE instead. Existing lower-version code should be reviewed before you change the class API version because security behavior can change with versioned Apex updates.

public with sharing class AccountSearchController {
    @AuraEnabled(cacheable=true)
    public static List<Account> findAccounts(String nameFragment, List<String> countries) {
        String cleanedName = String.isBlank(nameFragment) ? '' : nameFragment.trim();

        Set<String> countrySet = new Set<String>();
        if (countries != null) {
            for (String country : countries) {
                if (!String.isBlank(country)) {
                    countrySet.add(country.trim());
                }
            }
        }

        if (String.isBlank(cleanedName) && countrySet.isEmpty()) {
            return new List<Account>();
        }

        String namePattern = '%' + cleanedName + '%';

        if (countrySet.isEmpty()) {
            return [
                SELECT Id, Name, BillingCountry, Industry
                FROM Account
                WHERE Name LIKE :namePattern
                WITH USER_MODE
                ORDER BY Name
                LIMIT 200
            ];
        }

        if (String.isBlank(cleanedName)) {
            return [
                SELECT Id, Name, BillingCountry, Industry
                FROM Account
                WHERE BillingCountry IN :countrySet
                WITH USER_MODE
                ORDER BY Name
                LIMIT 200
            ];
        }

        return [
            SELECT Id, Name, BillingCountry, Industry
            FROM Account
            WHERE Name LIKE :namePattern
            AND BillingCountry IN :countrySet
            WITH USER_MODE
            ORDER BY Name
            LIMIT 200
        ];
    }
}

This method avoids a blank LIKE '%%' search, uses bind variables for user-provided values, limits the result set, and runs the query in user mode. It is still the developer’s job to return only data the UI needs and to add tests for users with different permissions.

Dynamic query safety

Use dynamic queries only when the query shape must change at runtime. You cannot bind a field API name or an ORDER BY direction as a normal value, so you must whitelist those pieces before concatenating them.

public with sharing class AccountSortService {
    public static List<Account> recentAccounts(String requestedSortField) {
        Set<String> allowedSortFields = new Set<String>{
            'Name',
            'CreatedDate',
            'LastModifiedDate'
        };

        String sortField = allowedSortFields.contains(requestedSortField)
            ? requestedSortField
            : 'Name';

        String queryText =
            'SELECT Id, Name, CreatedDate, LastModifiedDate ' +
            'FROM Account ' +
            'WHERE CreatedDate = LAST_N_DAYS:90 ' +
            'WITH USER_MODE ' +
            'ORDER BY ' + sortField + ' DESC ' +
            'LIMIT 100';

        return Database.query(queryText);
    }
}

Do not add a raw field name, object name, sort direction, or filter fragment from a request parameter. Bind values where possible and whitelist metadata identifiers where binding is not supported.

Best practices for SOQL performance and limits

Query limits matter most in Apex, integrations, and large data volume orgs. Salesforce’s Apex governor limit documentation lists a synchronous limit of 100 SOQL queries per transaction, and the platform also enforces limits for rows, heap, CPU time, and query runtime. The practical rule is simple: query once for the set of records you need, then process the results in memory with maps and sets.

Practice Why it matters Example
Do not query inside loops A trigger can process up to 200 records at once, so one query per record can exceed limits. Collect AccountId values into a Set<Id>, then query contacts with WHERE AccountId IN :ids.
Filter early Large unfiltered queries consume rows, heap, and time. Use WHERE IsClosed = false or a date literal instead of filtering after the query.
Select only needed fields Each extra field increases payload size and heap usage. Select Id, Name, StageName, not every Opportunity field.
Prefer selective filters Large objects need filters Salesforce can execute efficiently. Filter by indexed fields such as Id, lookup fields, audit fields, or supported custom indexes where appropriate.
Use SOQL for loops for large reads SOQL for loops retrieve records in batches and help reduce heap pressure. for (List<Account> batch : [SELECT Id FROM Account]) { ... }

Aggregate query examples

Aggregate queries help when you need counts, sums, minimums, or maximums without retrieving every record. They return AggregateResult rows in Apex.

SELECT StageName, COUNT(Id) totalDeals, SUM(Amount) totalAmount
FROM Opportunity
WHERE CloseDate = THIS_FISCAL_QUARTER
GROUP BY StageName
HAVING COUNT(Id) > 5
ORDER BY StageName

Aggregate queries still count against limits and row limits. Use them to reduce payload size, not to bypass governance.

Use Query Plan for large objects

For large objects, the Query Plan tool helps you inspect whether filters are selective enough. Review the leading operation type, cardinality, and relative cost before you put a query into production automation. If the plan shows a table scan on a large object, revisit filters, indexed fields, date ranges, or data model assumptions before deploying.

Common SOQL errors and fixes

Error or symptom Likely cause Fix
MALFORMED_QUERY near CONTAINS SOQL does not use CONTAINS for normal text filters. Use LIKE '%value%' for a single string field or use SOSL for full-text search.
Didn't understand relationship The parent or child relationship name is wrong. Check the relationship name. Custom parent relationships usually use __r; child relationship names can be customized.
Too many SOQL queries: 101 Apex is querying inside a loop or across repeated helper calls. Bulkify the code. Collect IDs, run one query with IN :ids, and use maps for lookup.
No such column The field API name is wrong, not available on the object, or not visible in the current context. Confirm the field API name in Object Manager and check permissions when running in user mode.
Slow query on a large object The filter is not selective, or the query uses a leading wildcard. Add selective filters, reduce the date range, avoid LIKE '%term%' where possible, and review Query Plan.
Too much heap in Apex The query returns too many rows or fields at once. Select fewer fields, add filters, use a SOQL for loop, or move the work to Batch Apex if the process is truly large.

Practical admin use cases

Admins do not need to become Apex developers to benefit from queries. They do need to understand field API names, object names, filters, and data access. Always run exploratory queries in a sandbox first if the result will drive a bulk update or deletion.

Find permission sets that grant field access

The FieldPermissions object can help identify where field-level access comes from. This is useful during permission set cleanup, profile reduction, and least-privilege reviews.

SELECT Parent.Name, SObjectType, Field, PermissionsRead, PermissionsEdit
FROM FieldPermissions
WHERE SObjectType = 'Account'
AND Field = 'Account.AnnualRevenue'
AND (PermissionsRead = true OR PermissionsEdit = true)
ORDER BY Parent.Name

The result shows permission set or profile containers that grant read or edit access. Review assignments separately before removing access because a user can receive permissions from multiple sources.

Find stale opportunities before a pipeline review

SELECT Id, Name, Owner.Name, StageName, Amount, CloseDate, LastModifiedDate
FROM Opportunity
WHERE IsClosed = false
AND CloseDate < TODAY
ORDER BY CloseDate ASC
LIMIT 500

This query gives sales operations a focused list of open opportunities with close dates in the past. A report may be better for weekly manager review, but a query is useful for one-time cleanup before a forecast process change.

Validate data after an import

SELECT BillingCountry, COUNT(Id) recordCount
FROM Account
WHERE CreatedDate = LAST_N_DAYS:7
GROUP BY BillingCountry
ORDER BY BillingCountry

Run validation queries after Data Loader jobs, Bulk API loads, or middleware deployments. Save the query with the migration runbook so the team can repeat the same checks in UAT and production.

Checklist before production use

  • Confirm object and field API names. Labels can change and are not used in query syntax.
  • Use filters that match the business question. A broad query can return correct data and still be unsafe for automation.
  • Limit rows in UI-backed methods. Components should not request every matching record.
  • Use WITH USER_MODE for user-facing Apex queries. Review classes before moving to API v67.0 or later.
  • Move queries out of loops. Use sets and maps for bulk processing.
  • Test with realistic data volume. A query that passes with 20 sandbox records can fail against millions of production rows.
  • Document assumptions. Note the expected selectivity, relationship names, and permission model in code comments or runbooks.

Related SalesforceTutorial resources

Official Salesforce documentation referenced

Frequently Asked Questions

What is SOQL in Salesforce?

SOQL is Salesforce Object Query Language. It retrieves records and field values from Salesforce objects with SELECT, FROM, WHERE, ORDER BY, LIMIT, relationship queries, and aggregate functions.

Does SOQL contains syntax exist?

SOQL does not use a CONTAINS operator for normal text fields. Use LIKE with % wildcards for a contains-style filter, such as WHERE Name LIKE '%Acme%'. Use SOSL when you need full-text search across multiple objects or fields.

How does the SOQL IN clause work?

The SOQL IN clause compares a field with a list of values, a bound Apex collection, or a subquery. In Apex, the common pattern is WHERE AccountId IN :accountIds, where accountIds is a Set<Id> collected before the query.

Can I use SOQL in Lightning Web Components?

Lightning Web Components do not run SOQL in browser JavaScript. Call an Apex method, enforce sharing and field access in Apex, and return only the fields the component needs.

What is the limit for SOQL queries in Apex?

A synchronous Apex transaction can run 100 SOQL queries. Salesforce also enforces row, heap, CPU, and query time limits, so bulkified code should query once for a collection of records instead of querying inside loops.

When should I use SOSL instead of a SOQL query?

Use a SOQL query when you know the object and fields you need. Use SOSL when the user enters search text and you need to search multiple objects or multiple text fields.