I Inqua
Sign in Get started
Fundamentals

SOQL vs SQL: the differences that trip everyone up

Updated June 9, 2026 · 7 min read · by the Inqua team

SOQL looks enough like SQL that experienced database people assume they already know it — right up until their first query throws an error. SOQL (Salesforce Object Query Language) is a query language over the Salesforce object model and its API, not over tables, and that changes more than the name. Here are the differences that actually trip people up, with side-by-side examples.

1. There is no SELECT *

Every field must be listed explicitly. For quick exploration, newer API versions accept FIELDS(ALL), FIELDS(STANDARD), or FIELDS(CUSTOM), but FIELDS(ALL) requires a LIMIT of 200 or fewer — it's a browsing tool, not a production pattern.

-- SQL: SELECT * FROM accounts
SELECT Id, Name, Industry, AnnualRevenue FROM Account

2. You query exactly one object

The FROM clause names a single object — there's no listing two tables and joining them in the WHERE clause. Related data is reached through relationship paths defined by lookup and master-detail fields.

3. There is no JOIN keyword

Instead of joins, SOQL traverses relationships: dot notation walks up to a parent, and a nested subquery walks down to children.

-- SQL: SELECT c.last_name, a.name FROM contacts c JOIN accounts a ON ...
SELECT LastName, Account.Name, Account.Owner.Name FROM Contact

This is a big enough topic that it has its own guide: JOINs in SOQL: how relationship queries work.

4. = null instead of IS NULL

SQL's IS NULL / IS NOT NULL don't exist. SOQL compares against null directly:

SELECT Id, Name FROM Contact WHERE Email = null
SELECT Id, Name FROM Contact WHERE Email != null

5. No expressions in the SELECT list

No CASE, no string concatenation, no arithmetic between columns. The select list is fields, relationship paths, and aggregate functions — formatting and derived values happen in your code (or in formula fields on the object). A handful of server-side functions exist, such as toLabel() for picklist labels, FORMAT() for localized output, and convertCurrency() for multi-currency orgs.

6. Date filtering uses literals, not functions

There's no GETDATE() or DATEADD(). SOQL has named date literals like TODAY, THIS_FISCAL_QUARTER, and LAST_N_DAYS:30 — unquoted keywords evaluated at query time. See the full SOQL date literals cheat sheet.

-- SQL: WHERE created_at >= DATEADD(day, -30, GETDATE())
WHERE CreatedDate = LAST_N_DAYS:30

7. Aggregates exist, but with house rules

COUNT, SUM, AVG, MIN, MAX, GROUP BY, and HAVING all work — but aliases are written without AS, unaliased aggregates come back named expr0, expr1, and so on, and in Apex an aggregate query is limited to 2,000 result rows. Details and examples are in SOQL aggregate queries.

8. SOQL only reads

There is no INSERT, UPDATE, or DELETE statement. Writes go through a separate path entirely (DML in Apex, or create/update/delete calls in the APIs). A SOQL query can never modify data — which is also why a read-only tool can safely expose it.

9. Results are governed and capped

Salesforce is multi-tenant, so queries run under governor limits: an Apex transaction can retrieve at most 50,000 rows, OFFSET tops out at 2,000, and large extracts belong in the Bulk API rather than paginated SOQL. If a query "works in dev but dies in production", a limit is usually why.

10. Text matching is case-insensitive

Comparisons and LIKE on standard text fields ignore case — WHERE Name LIKE '%acme%' matches "ACME Corp". (Fields explicitly configured as case-sensitive unique are the exception.) Wildcards and the rest of the filter toolbox are covered in the SOQL WHERE clause guide.

Coming from SQL and just need answers? Inqua lets you ask Salesforce questions in plain English and shows the generated SOQL with every answer — the fastest way to map your SQL instincts onto SOQL.

Quick translation table

You'd write in SQLYou write in SOQL
SELECT * FROM accountsSELECT Id, Name, Industry FROM Account
JOIN accounts a ON c.account_id = a.idSELECT LastName, Account.Name FROM Contact
WHERE email IS NULLWHERE Email = null
WHERE created_at >= DATEADD(day,-30,GETDATE())WHERE CreatedDate = LAST_N_DAYS:30
SELECT COUNT(*) FROM casesSELECT COUNT() FROM Case
GROUP BY stage HAVING SUM(amount) > 100000GROUP BY StageName HAVING SUM(Amount) > 100000

Or skip the syntax entirely

Inqua turns plain-English questions into live SOQL against your real org — and shows the exact query behind every answer, so you learn the syntax as you go. Read-only, OAuth-only, free during early access.

The demo runs on sample data — no Salesforce org required.