Salesforce SOQL vs SQL: Key Differences Every Developer Should Know
Author : Cloud shukla2 | Published On : 07 Jul 2026
If you've spent any time with relational databases before stepping into Salesforce development, SOQL will feel familiar enough to be comfortable — and just different enough to trip you up in ways that aren't always obvious at first.
The surface-level resemblance is real. SOQL uses SELECT, FROM, WHERE, ORDER BY, and LIMIT. If you've written SQL queries before, your first SOQL query will probably look reasonable and even run correctly. Then you'll hit something unexpected — a relationship you can't join the way you're used to, a subquery that doesn't work the way SQL subqueries do, a governor limit error that SQL developers have never encountered — and you'll realize the two languages, despite appearances, operate on fundamentally different assumptions.
This post covers those differences systematically. Not just what SOQL can't do that SQL can, but why — and what the SOQL equivalents or workarounds look like in practice. Whether you're an experienced database developer moving into Salesforce or a Salesforce professional trying to sharpen your query skills, this is the comparison guide worth bookmarking.
What Is SOQL? The Foundation Before the Comparison
SOQL stands for Salesforce Object Query Language. It's a query language designed specifically for retrieving records from Salesforce's cloud database — the Force.com platform — and it's the primary way Apex code interacts with Salesforce data.
Like SQL, SOQL lets you retrieve specific fields from specific objects based on filter conditions. Unlike SQL, SOQL operates within the constraints of Salesforce's multi-tenant cloud architecture, which introduces both limitations and features that have no direct equivalent in traditional relational databases.
SOQL is used in:
-
Apex classes and triggers to retrieve records programmatically
-
Developer Console to query Salesforce data directly
-
Salesforce CLI and workbench tools for data inspection
-
Flow and other declarative tools (in the background, even when you don't see the query)
SOQL is read-only. It retrieves records — it does not insert, update, or delete them. That's handled separately via DML (Data Manipulation Language) statements in Apex. This is a fundamental structural difference from SQL, where SELECT, INSERT, UPDATE, and DELETE are all part of the same language.
Side-by-Side: SOQL vs SQL at a Glance
Before diving into specifics, here's a high-level comparison table that maps the conceptual territory:
|
Feature |
SQL |
SOQL |
|
Language purpose |
Query, insert, update, delete |
Query only (read-only) |
|
Data source |
Relational database tables |
Salesforce objects |
|
JOIN support |
Full JOIN support (INNER, LEFT, RIGHT, FULL) |
No traditional JOINs — uses relationship traversal |
|
Subqueries |
Flexible, nested subqueries |
Limited, relationship-based subqueries only |
|
Wildcard field selection |
SELECT * supported |
SELECT * NOT supported — must list fields explicitly |
|
Aggregate functions |
COUNT, SUM, AVG, MIN, MAX |
COUNT, COUNT_DISTINCT, SUM, AVG, MIN, MAX |
|
Governor limits |
None (database-level limits only) |
Strict: 100 queries/transaction, 50,000 records max |
|
Full-text search |
LIKE with wildcards |
Handled by SOSL (separate language) |
|
Date functions |
Extensive built-in date functions |
Date literals (THIS_WEEK, LAST_MONTH, etc.) |
|
NULL handling |
IS NULL / IS NOT NULL |
#ERROR! |
|
Schema |
Tables with foreign keys |
Objects with relationship fields |
Difference 1: SELECT * Does Not Exist in SOQL
In SQL, the wildcard select is one of the first things you learn:
sql
-- SQL: Works fine
SELECT * FROM Account;
In SOQL, this does not work. Full stop.
apex
// SOQL: This will throw an error
SELECT * FROM Account // INVALID
// SOQL: You must explicitly list every field you want
SELECT Id, Name, Industry, AnnualRevenue, OwnerId FROM Account
This isn't an oversight — it's a deliberate design choice tied to Salesforce's multi-tenant architecture and governor limits. Every field you retrieve consumes heap memory and counts toward query performance limits. Forcing explicit field declaration keeps queries efficient and prevents accidental over-retrieval of sensitive or unnecessary data.
Practical implication: Before writing SOQL in Apex, you need to know which fields you actually need. Get into the habit of querying only the fields your code will actually use — both for governor limit efficiency and for security reasons (you shouldn't retrieve sensitive field data your code doesn't need).
Difference 2: SOQL Uses Relationship Traversal, Not JOINs
This is the difference that catches SQL developers most off guard. Traditional SQL uses JOIN syntax to combine data from multiple tables:
sql
-- SQL JOIN: Standard approach
SELECT Account.Name, Contact.FirstName, Contact.LastName
FROM Contact
INNER JOIN Account ON Contact.AccountId = Account.Id
WHERE Account.Industry = 'Technology';
SOQL has no JOIN keyword. Instead, it uses relationship traversal — dot-notation to walk between related objects in a single query.
Parent-to-Child Relationship Queries (Subquery approach)
When you want data from a parent object and related child records:
apex
// SOQL: Get Accounts with their related Contacts
SELECT Id, Name,
(SELECT FirstName, LastName, Email FROM Contacts)
FROM Account
WHERE Industry = 'Technology'
Note: The inner query (SELECT ... FROM Contacts) uses the relationship name — not the object API name. For standard objects, this is often the plural form (Contacts, Opportunities). For custom objects, it's the relationship name defined on the field plus __r.
Child-to-Parent Relationship Queries (Dot-notation)
When you're querying child records and want fields from the parent:
apex
// SOQL: Get Contacts with their parent Account name
SELECT FirstName, LastName, Email, Account.Name, Account.Industry
FROM Contact
WHERE Account.Industry = 'Technology'
Here Account.Name traverses the relationship from Contact to its parent Account using dot-notation. You can traverse up to 5 levels of parent relationships in a single query.
What's Missing Compared to SQL JOINs?
-
No LEFT JOIN equivalent for unrelated objects. If two Salesforce objects have no relationship field connecting them, you cannot query them together in SOQL. SQL can JOIN any two tables that share a common value; SOQL can only traverse defined Salesforce relationships.
-
No FULL OUTER JOIN. SOQL subqueries in parent-to-child direction return null for parents with no children (similar to LEFT JOIN behavior), but there's no equivalent of FULL OUTER JOIN.
-
No cross-object aggregation without relationships. If you need to aggregate data across objects that aren't related in Salesforce, you'd need to make separate SOQL queries and handle the join logic in Apex.
Difference 3: Governor Limits Are a Hard Reality in SOQL
This is perhaps the most operationally significant difference between SOQL and SQL — and it's one that SQL developers almost always underestimate when they first start working in Salesforce.
In a traditional database, query limits are soft — the database might slow down if you query too much, but it rarely throws an exception that crashes your application. In Salesforce, governor limits are hard runtime exceptions that terminate your transaction immediately when breached.
Key SOQL governor limits per synchronous Apex transaction:
|
Limit |
Cap |
|
Total SOQL queries |
100 |
|
Total records retrieved by SOQL |
50,000 |
|
Total records retrieved by a single Database.getQueryLocator |
10,000 (50 million in Batch Apex) |
|
Total heap size |
6 MB |
These limits exist because Salesforce is a multi-tenant platform — every org shares the same infrastructure. If one tenant's runaway query consumed unlimited resources, every other org on the same server would suffer. Governor limits are the architectural solution to that problem.
What this means for SOQL in practice:
You can never write SOQL inside a loop. This is the cardinal rule of Salesforce development, and it's the most common mistake SQL developers make when they transition to Apex:
apex
// WRONG — SOQL inside a loop: will hit 101 SOQL limit error
for (Account acc : accountList) {
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
// Do something
}
// RIGHT — Single SOQL query with IN clause, process in Apex
Set<Id> accountIds = new Map<Id, Account>(accountList).keySet();
List<Contact> contacts = [SELECT Id, AccountId FROM Contact
WHERE AccountId IN :accountIds];
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : contacts) {
if (!contactsByAccount.containsKey(c.AccountId)) {
contactsByAccount.put(c.AccountId, new List<Contact>());
}
contactsByAccount.get(c.AccountId).add(c);
}
The IN clause with a set of Ids replaces what a SQL developer would do with a JOIN — one query retrieves all related records at once, and the filtering is done in Apex memory.
Difference 4: SOQL Date Handling Is Completely Different
SQL databases typically have rich date function libraries:
sql
-- SQL: Date functions
SELECT * FROM Opportunity
WHERE YEAR(CloseDate) = 2026
AND MONTH(CloseDate) = 6;
SELECT DATEDIFF(day, CreatedDate, CloseDate) AS DaysToClose
FROM Opportunity;
SOQL takes a different approach — it uses date literals rather than date functions:
apex
// SOQL date literals
SELECT Id, Name, CloseDate FROM Opportunity
WHERE CloseDate = THIS_YEAR;
SELECT Id, Name, CloseDate FROM Opportunity
WHERE CloseDate > LAST_N_DAYS:30;
SELECT Id, Name, CloseDate FROM Opportunity
WHERE CloseDate = THIS_MONTH;
Common SOQL date literals:
|
Date Literal |
What It Means |
|
TODAY |
Current day |
|
YESTERDAY |
Previous day |
|
THIS_WEEK |
Current week (Sunday to Saturday) |
|
LAST_WEEK |
