SOQL (Salesforce Object Query Language) is how you read data out of a Salesforce org. If you have ever written SQL, the shape will feel familiar — SELECT fields FROM an object WHERE a condition is true — but SOQL is its own language with its own rules, and the way you run it has changed a lot since the early Developer Console days.
This guide is a current, 2026 walkthrough for admins and developers: what SOQL is, how it differs from SQL and SOSL, every place you can run a query today, the syntax that matters (relationship queries, aggregates, date literals), how to use SOQL safely inside Apex, and the governor limits that will bite you if you ignore them.
What SOQL is (and how it is different)
SOQL queries return records from one Salesforce object at a time (plus that object's related records). The result of a query is always a list of sObjects — for example a List<Account> in Apex — never a free-form result set.
SOQL vs SQL
If you come from a relational database background, keep these differences in mind:
- No
SELECT *. You must name every field you want. There is no wildcard, partly because field-level security and selectivity matter on the platform. - One object per
FROM. A single SOQL statement targets one base object. You reach related data through relationship queries (below), not arbitrary table joins. - No
JOINkeyword. Relationships are predefined by your schema (lookup and master-detail fields), so you traverse them by name with dot notation or nested subqueries instead of writing joins. - Governed, not unlimited. Every query runs against shared infrastructure, so row counts and query counts are capped per transaction (see governor limits).
SOQL vs SOSL
SOQL and SOSL (Salesforce Object Search Language) are often confused:
- Use SOQL when you know which object holds the data and you want precise, filtered records — e.g. "all Opportunities closing this quarter over $50k".
- Use SOSL when you want a full-text search across many objects at once — e.g. "find 'Acme' anywhere in Accounts, Contacts, and Leads". SOSL searches a text index and returns a list of lists (one list per object). It is the right tool for global search boxes; SOQL is the right tool for reporting and business logic.
Where to run SOQL
You rarely write a query in just one place. Here are the tools that matter in 2026, from quickest to most programmatic.
Developer Console Query Editor
Still the no-install option. From Setup or the gear menu, open Developer Console, then the Query Editor tab at the bottom. Type a query, click Execute, and results appear in a grid. The Query History panel keeps recent queries so you can re-run them. If a query has a syntax error, the message appears under the results pane.
Salesforce Inspector Reloaded (browser extension)
A free Chrome/Edge extension that has become the everyday tool for many admins and developers. It adds a popup on any Salesforce page where you can run SOQL against the current org, export results to CSV/Excel, and inspect field API names without leaving the record you are on. It is the successor to the original (now unmaintained) Salesforce Inspector.
Salesforce CLI — sf data query
For scripting and CI, the modern Salesforce CLI (the sf executable) is the standard. Note that sf supersedes the older sfdx commands.
# Run a query against your default org
sf data query --query "SELECT Id, Name FROM Account WHERE Industry = 'Technology' LIMIT 5"
# Export to CSV for a specific org
sf data query --query "SELECT Id, Name, AnnualRevenue FROM Account" \
--target-org my-prod-alias --result-format csv > accounts.csv
# Use the Bulk API for very large result sets
sf data query --query "SELECT Id FROM Contact" --bulk --wait 10
VS Code — Salesforce Extensions & SOQL Builder
With the Salesforce Extension Pack installed, you can run queries straight from VS Code. The SOQL Builder lets you build a query visually (pick the object, check fields, add filters and ordering) and run it without memorizing syntax, then save the .soql file in your project. You can also run any .soql file with "SFDX: Execute SOQL Query".
Workbench and the REST / Bulk Query APIs
Workbench is a web tool for ad‑hoc queries, metadata, and data loads. For applications, query programmatically via the REST Query resource (/services/data/vXX.0/query/?q=...) or, for millions of rows, the Bulk API 2.0 query jobs. If you are moving data in or out at scale, pair these with Data Loader.
Core SOQL syntax
The basic shape is:
SELECT field1, field2, ...
FROM ObjectName
WHERE condition
ORDER BY field [ASC|DESC] [NULLS FIRST|LAST]
LIMIT n
OFFSET m
A concrete example:
SELECT Id, Name, Industry, AnnualRevenue
FROM Account
WHERE Industry = 'Technology' AND AnnualRevenue > 1000000
ORDER BY AnnualRevenue DESC
LIMIT 50
Filtering, ordering, and paging
WHEREsupports=,!=,<,>,<=,>=,LIKE(with%and_wildcards),IN,NOT IN, andAND/OR/NOT.ORDER BYsorts the results; addNULLS FIRST/NULLS LASTto control where blanks land.LIMITcaps the number of rows;OFFSET(max 2,000) skips rows for simple paging — though for large data sets, keyset paging onIdor query locators scale better thanOFFSET.
Date literals
SOQL has built-in date literals so you do not hard-code dates:
SELECT Id, Subject, ActivityDate
FROM Task
WHERE ActivityDate = TODAY
OR CreatedDate = LAST_N_DAYS:30
OR CloseDate = THIS_QUARTER
Common literals include TODAY, YESTERDAY, THIS_WEEK, THIS_MONTH, THIS_QUARTER, THIS_YEAR, LAST_N_DAYS:n, NEXT_N_DAYS:n, and LAST_N_QUARTERS:n.
Aggregate functions and GROUP BY
SOQL can summarize data with aggregate functions: COUNT(), COUNT(field), COUNT_DISTINCT(field), SUM(), AVG(), MIN(), and MAX(). Use GROUP BY to bucket rows and HAVING to filter the buckets.
SELECT StageName, COUNT(Id) numDeals, SUM(Amount) totalAmount
FROM Opportunity
WHERE CloseDate = THIS_YEAR
GROUP BY StageName
HAVING SUM(Amount) > 100000
ORDER BY SUM(Amount) DESC
In Apex, aggregate queries return List<AggregateResult>, and you read each value with get('alias'):
List<AggregateResult> results = [
SELECT StageName, SUM(Amount) totalAmount
FROM Opportunity
GROUP BY StageName
];
for (AggregateResult ar : results) {
String stage = (String) ar.get('StageName');
Decimal total = (Decimal) ar.get('totalAmount');
System.debug(stage + ' => ' + total);
}
A bare SELECT COUNT() FROM Account WHERE ... returns an integer count and does not count toward your 50,000-row limit the way returning records does — handy for "how many match?" checks.
Relationship queries (the SOQL replacement for joins)
Instead of joins, SOQL walks the relationships defined by your lookup and master-detail fields.
Child-to-parent (dot notation)
Going up to a parent, use dots. For standard relationships the name is the parent object (Account.Name); for custom relationships replace the field's __c suffix with __r:
SELECT Id, LastName, Account.Name, Account.Owner.Email
FROM Contact
WHERE Account.Industry = 'Finance'
Parent-to-child (nested subquery)
Going down to children, nest a subquery using the child relationship name (plural, e.g. Contacts; custom relationships use the __r form):
SELECT Name, (SELECT LastName, Email FROM Contacts)
FROM Account
WHERE Name LIKE 'Acme%'
Each parent-to-child subquery counts as an extra query against limits, and these queries have their own row caps, so keep them tight.
Polymorphic relationships and TYPEOF
Some fields (like Task.WhatId or Event.WhoId) can point to different object types. Use TYPEOF to select different fields depending on the related record's type:
SELECT Subject,
TYPEOF What
WHEN Account THEN Name, Industry
WHEN Opportunity THEN Amount, StageName
ELSE Name
END
FROM Task
Using SOQL in Apex
Inside Apex you embed SOQL in square brackets — this is inline SOQL, and the compiler validates it at save time.
List<Account> techAccounts = [
SELECT Id, Name FROM Account WHERE Industry = 'Technology'
];
Binding Apex variables
Reference an Apex variable inside a query with a colon (:variable). The platform binds it safely, so this is the correct, injection-safe way to parameterize a static query:
String targetIndustry = 'Technology';
Set<Id> ownerIds = getActiveOwnerIds();
List<Account> accts = [
SELECT Id, Name
FROM Account
WHERE Industry = :targetIndustry AND OwnerId IN :ownerIds
];
Dynamic SOQL — and avoiding SOQL injection
When the query string itself is built at runtime, use Database.query(). If any part of that string comes from user input, do not concatenate it — that is how SOQL injection happens. Use Database.queryWithBinds() (or String.escapeSingleQuotes() for legacy code) so bind variables are passed separately:
// Safe dynamic SOQL with bind map
Map<String, Object> binds = new Map<String, Object>{
'industry' => userSuppliedIndustry
};
String q = 'SELECT Id, Name FROM Account WHERE Industry = :industry';
List<Account> accts = Database.queryWithBinds(q, binds, AccessLevel.USER_MODE);
SOQL for loops for large data sets
If a query could return many records, iterate with a SOQL for loop. Apex processes the results in batches of 200 internally, which keeps your heap small and helps you stay under limits:
for (Account a : [SELECT Id, Name FROM Account WHERE Industry = 'Technology']) {
// process each record; batched automatically
}
For working with the lists these queries return, see list methods in Apex.
Governor limits and best practices
Salesforce is multi-tenant, so every transaction runs inside governor limits. The ones that touch SOQL most:
- 100 SOQL queries per synchronous Apex transaction (200 for asynchronous contexts such as Batch, Queueable, and
@future). - 50,000 rows retrieved by SOQL queries in a single transaction.
- Parent-to-child subqueries and aggregate queries have their own additional caps.
To stay safe and fast:
- Never put SOQL inside a loop. This is the number-one cause of "Too many SOQL queries: 101" errors. Query once before the loop and work from the collection.
- Bulkify. Write code that handles 1 or 200 records the same way — filter with
IN :setOfIdsinstead of querying per record. - Select only the fields you need and always add a
WHEREclause; broad queries are slow and risk hitting row limits. - Make queries selective. Filter on indexed fields (Id, Name, external IDs, lookups, and audit fields are indexed by default) so the optimizer can use an index instead of a full scan. For very large objects, custom indexes and skinny tables keep queries performant.
For the full picture across all limit types, see our guide to Salesforce governor limits.
If you would rather have a team handle complex Salesforce data models, integrations, and Apex for you, MicroPyramid's Salesforce services can help.
Frequently Asked Questions
What is the difference between SOQL and SOSL?
SOQL queries one object (and its relationships) when you know exactly where the data lives and want precise, filtered records. SOSL performs a full-text search across multiple objects at once and is meant for global "search everywhere" features. Use SOQL for reporting and business logic; use SOSL for search boxes.
Why can't I use SELECT * in SOQL?
SOQL has no wildcard — you must list every field you want. This is by design: it keeps queries selective, respects field-level security, and avoids pulling unnecessary data on a governed, multi-tenant platform. Tools like Salesforce Inspector Reloaded or the VS Code SOQL Builder can generate the full field list for you.
How do I query related records without a JOIN?
SOQL replaces joins with relationship queries. To read a parent's fields, use dot notation (Account.Name, or Custom__r.Field__c for custom relationships). To read child records, nest a subquery using the child relationship name, e.g. SELECT Name, (SELECT LastName FROM Contacts) FROM Account.
What are the SOQL governor limits?
In a single transaction you can issue up to 100 SOQL queries synchronously (200 asynchronously) and retrieve up to 50,000 rows total. The most common way to break these is putting a query inside a loop, so always query once and bulkify your logic.
How do I run a SOQL query from the command line?
Use the modern Salesforce CLI: sf data query --query "SELECT Id, Name FROM Account LIMIT 5". Add --target-org to pick an org, --result-format csv to export, and --bulk for very large result sets. The sf command supersedes the older sfdx force:data:soql:query.
How do I prevent SOQL injection in Apex?
For static queries, bind Apex variables directly with a colon (WHERE Industry = :value) — the platform handles them safely. For dynamic queries built at runtime, never concatenate user input; use Database.queryWithBinds() with a bind map (or String.escapeSingleQuotes() in older code).