Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
DictionarySSOQL (Salesforce Object Query Language)
DevelopmentAdvanced

SOQL (Salesforce Object Query Language)

SOQL (Salesforce Object Query Language) is the query language used to read records from the Salesforce database.

§ 01

Definition

SOQL (Salesforce Object Query Language) is the query language used to read records from the Salesforce database. It looks similar to SQL SELECT statements but is purpose-built for the multi-tenant Salesforce platform: queries always start from a single sObject, joins use relationship traversal syntax instead of JOIN clauses, and the language has no DML or DDL surface. SOQL queries return strongly-typed sObject results that Apex, Flow, and integration code consume directly.

SOQL is the data-access foundation for everything custom in Salesforce. Apex classes embed inline SOQL: List<Account> accts = [SELECT Id, Name FROM Account WHERE Industry = ''Tech'']. REST and SOAP APIs accept SOQL strings in query endpoints. Reports and list views run SOQL under the hood. The query language supports relationship traversal up to five levels deep, aggregate functions (COUNT, SUM, AVG, MIN, MAX), grouping with GROUP BY, sub-selects, sub-queries via parent-to-child and child-to-parent traversal, and filtering with WHERE clauses that follow familiar SQL semantics. Mastery of SOQL is non-negotiable for any developer or architect serious about Salesforce.

§ 02

How SOQL queries shape data access in Salesforce

Basic syntax and how it differs from SQL

A SOQL query has a SELECT, FROM, WHERE, ORDER BY, and LIMIT clause that look like SQL on the surface. The differences matter. SOQL has no JOIN keyword; relationship queries use dot notation (Account.Name from Contact) or sub-selects ((SELECT Id, Name FROM Contacts) from Account). There is no SELECT *; every field must be listed explicitly. There is no DML through SOQL; INSERT, UPDATE, DELETE happen through Apex DML statements separately. The query language is read-only by design.

Relationship queries and traversal depth

Child-to-parent: SELECT Id, Account.Name FROM Contact returns the related Account name for each Contact. Parent-to-child: SELECT Id, (SELECT Id, Name FROM Contacts) FROM Account returns Account records with nested Contacts. Traversal supports up to five levels in a single query (Opportunity.Account.Owner.Manager.Name). Custom relationships use the relationship API name (My_Object__r in nested queries, My_Object__c for the foreign key field). Cross-object filtering works through the same dot notation: WHERE Account.Industry = ''Tech''.

Aggregate functions and GROUP BY

SOQL supports COUNT, SUM, AVG, MIN, MAX, COUNT_DISTINCT for aggregation. The GROUP BY clause groups results by one or more fields. The HAVING clause filters aggregated results post-grouping. Example: SELECT StageName, COUNT(Id), SUM(Amount) FROM Opportunity GROUP BY StageName HAVING SUM(Amount) > 100000. Aggregate queries return AggregateResult objects rather than sObjects, accessed via .get(''expression_alias'') or aliased fields. GROUP BY ROLLUP and GROUP BY CUBE generate subtotal and grand-total rows automatically, useful for reporting summaries.

Governor limits and indexed fields

SOQL queries count against the 100 queries per transaction governor limit, and each query is capped at 50,000 records returned. Filtering on indexed fields (Id, Name, ForeignKey, External ID, Unique flag) is fast. Filtering on non-indexed fields with high row counts can produce a NonSelectiveQueryException at runtime, telling you the query is too broad. The fix is either to add a more selective filter, add a custom index (via support request), or use a different query strategy. Selective queries matter especially on objects with more than 100,000 rows.

SOQL injection and bind variables

Building SOQL by string concatenation with user input is a security risk because malicious strings can extend the query unexpectedly. The mitigation: use bind variables (:variableName) inside the SOQL literal, or use Database.query with String.escapeSingleQuotes on dynamic strings. Bind variables also let the platform optimize the query plan, often producing faster execution than dynamic strings. Apex security review explicitly flags string-concatenation SOQL because injection through user input is a recurring vulnerability pattern.

Polymorphic relationships and TYPEOF

Some fields like Owner can reference different sObject types (User or Queue for OwnerId on Lead and Case). Polymorphic SOQL handles this with the TYPEOF clause: SELECT Id, TYPEOF Owner WHEN User THEN Email WHEN Group THEN Name END FROM Case. This is more elegant than separate queries per type. Polymorphic queries also work with the WHAT field on Task and Event, which can reference Account, Opportunity, Case, or many other objects.

Bulk APIs, Tooling API, and the query variants

Standard SOQL caps at 50,000 records per query. For larger result sets, use queryMore for cursor-based pagination, or use the Bulk API 2.0 query endpoint that splits the query across multiple chunks. The Tooling API has its own SOQL surface for querying metadata objects (ApexClass, FieldDefinition, EntityDefinition) that the regular API does not expose. Each variant has slightly different syntax and capability; pick the right one based on whether you need data records, metadata records, or bulk extraction.

§ 03

How to write effective SOQL queries

Writing SOQL queries is mechanically simple. Writing queries that scale to production data volumes, pass security review, and avoid governor limits takes practice. Build queries in Developer Console with the Query Editor, profile them with the Query Plan tool, and reduce them to bulk-friendly patterns before embedding in Apex.

  1. Open the Developer Console Query Editor

    Setup > Developer Console > Query Editor tab. The editor provides syntax checking, autocomplete on object and field names, and instant execution. Use it to draft and refine queries before pasting them into Apex.

  2. Start with the SELECT clause and explicit field list

    SELECT Id, Name, AccountId, StageName FROM Opportunity. List every field you need. There is no SELECT * in SOQL, and pulling unnecessary fields increases CPU and heap usage in Apex.

  3. Add the WHERE clause with selective filters

    WHERE StageName = ''Closed Won'' AND CloseDate >= LAST_QUARTER. Filters on indexed fields (Id, Name, ForeignKey, External ID) are fast. Avoid filters on non-indexed fields with high cardinality unless the object is small.

  4. Add relationship traversal as needed

    Child-to-parent: SELECT Id, Account.Industry FROM Contact. Parent-to-child: SELECT Id, (SELECT Id FROM Opportunities) FROM Account. Up to five levels deep. Use the relationship API name for custom relationships.

  5. Test the query in the Developer Console

    Run with Execute. Check the result count, look at the data shape, and refine the filters. Use the Query Plan tool (Tools menu) to see how the platform will execute the query at scale.

  6. Embed in Apex with bind variables for security

    List<Account> accts = [SELECT Id, Name FROM Account WHERE Industry = :industryParam]. Bind variables prevent SOQL injection and let the platform optimize the query plan.

  7. Use Database.query only when truly dynamic

    Database.query(''SELECT Id FROM '' + objectName) works for runtime-determined object names. Always wrap user input with String.escapeSingleQuotes to prevent injection.

  8. Verify selectivity with the Query Plan tool

    Query Plan flags non-selective queries that would scan too many rows. Selective queries use an index and run in milliseconds; non-selective queries can blow the timeout limit on large objects.

Key options
WHERE Clause Filtersremember

Determines which records the query returns. Filtering on indexed fields keeps the query selective and fast.

Relationship Traversalremember

Dot notation for child-to-parent, sub-selects for parent-to-child. Up to five levels of depth supported per query.

Bind Variablesremember

The :variable syntax for embedding Apex values in SOQL. Prevents injection and improves query plan optimization.

Gotchas
  • Standard SOQL caps at 50,000 records per query. Larger result sets need queryMore, Bulk API 2.0 query, or batch processing. Hitting the cap returns a partial result without warning.
  • Non-selective queries on large objects throw NonSelectiveQueryException at runtime. Test queries with realistic production data volumes, not sandbox dev data, to catch this in pre-production.
  • Each SOQL query in Apex counts against the 100-query governor limit per transaction. Queries inside loops are the leading cause of LimitException; always move queries outside loops.
  • String-concatenated SOQL is a security hazard. Use bind variables in static SOQL, or String.escapeSingleQuotes on dynamic strings in Database.query. Security review flags every instance of concatenation.
  • Aggregate queries return AggregateResult objects, not sObjects. Access fields by alias: ar.get(''totalAmount'') rather than ar.Amount. The mistake is common and confusing on first encounter.
§

Trust & references

Sources

Cross-checked against the following references.

Official documentation

Straight from the source - Salesforce's reference material on SOQL (Salesforce Object Query Language).

Keep learning

Hands-on resources to go deeper on SOQL (Salesforce Object Query Language).

Was this entry helpful?
Help us write better definitions. Quick reactions or detailed edit suggestions.

About the Author

Dipojjal Chakrabarti is a B2C Solution Architect with 29 Salesforce certifications and over 13 years in the Salesforce ecosystem. He runs salesforcedictionary.com to help admins, developers, architects, and cert/interview candidates sharpen their fundamentals. More about Dipojjal.

§

Test your knowledge

Q1. What is SOQL?

Q2. What's the maximum relationship traversal depth?

Q3. Where is SOQL used?

§

Discussion

Loading…

Loading discussion…