Complete Guide

SOQL — Salesforce Object Query Language
Complete Guide

From your first SELECT statement to advanced aggregate queries, relationship traversal, and writing governor-limit-safe SOQL inside Apex. Everything you need to query Salesforce data like a pro.

~18 min read 25+ code examples Beginner to Advanced
Advertisement

1 What is SOQL?

SOQL stands for Salesforce Object Query Language. It is the query language used to retrieve records from the Salesforce database, both from standard objects (like Account, Contact, Opportunity) and custom objects you create in your org. SOQL is modelled closely after SQL and will feel familiar if you have worked with relational databases, but it has important differences that reflect Salesforce's multi-tenant, cloud architecture.

SOQL vs SQL — Key Differences

FeatureSQLSOQL
Read dataSELECTSELECT (same)
Modify dataINSERT, UPDATE, DELETENot supported — use DML
Wildcard SELECTSELECT *Must name every field
JoinsJOIN keywordRelationship traversal (dot notation / subquery)
Date filteringDate literals vary by DBBuilt-in: TODAY, THIS_MONTH, LAST_N_DAYS:n
Execution limitNone (database level)100 queries per Apex transaction
Row limitVaries50,000 rows returned per query; 10,000 via getQueryLocator

When to Use SOQL

Use SOQL whenever you need to retrieve records from Salesforce in Apex, Flows (via Get Records element), or the Developer Console. SOQL is the only way to query the Salesforce database from code. To write or delete records you use Data Manipulation Language (DML) statements — insert, update, delete, upsert — which are separate from SOQL entirely.

Developer Console tip: You can execute SOQL queries interactively in the Salesforce Developer Console via Query Editor, or in VS Code with the Salesforce CLI command sf data query --query "SELECT Id FROM Account LIMIT 5".

2 Basic Syntax — SELECT Fields FROM Object

Every SOQL query follows this fundamental pattern:

PatternSOQL
SELECT Field1, Field2, Field3
FROM   ObjectApiName
WHERE  Condition
ORDER BY FieldName ASC|DESC
LIMIT  200

Let's look at real, practical examples starting from the simplest query and building up.

Example 1 — Retrieve All Accounts (with a safety LIMIT)

Fetch accountsSOQL
SELECT Id, Name, Industry, AnnualRevenue, Phone
FROM   Account
LIMIT  200

Example 2 — Query a Custom Object

Custom objects always end in __c. Custom fields also use the __c suffix.

Custom objectSOQL
SELECT Id, Name, Project_Status__c, Budget__c, Deadline__c
FROM   Project__c
WHERE  Project_Status__c = 'Active'
ORDER BY Deadline__c ASC
LIMIT  50

Example 3 — Querying Contacts with Multiple Fields

Contacts querySOQL
SELECT Id, FirstName, LastName, Email, Title, Department,
       AccountId, Account.Name, LeadSource
FROM   Contact
WHERE  Email != null
ORDER BY LastName ASC, FirstName ASC
LIMIT  500

Important: SOQL does not support SELECT *. You must always explicitly list the fields you want. Querying only the fields you need reduces heap size and makes your code more efficient.

3 WHERE Clause — Operators & Date Literals

The WHERE clause filters which records are returned. SOQL supports a rich set of comparison and logical operators.

Comparison Operators

OperatorDescriptionExample
=Equal toStage = 'Closed Won'
!=Not equal toStatus != 'Inactive'
<Less thanAmount < 10000
>Greater thanAmount > 50000
<=Less than or equalCloseDate <= TODAY
>=Greater than or equalCreatedDate >= LAST_MONTH
LIKEPattern match (% wildcard)Name LIKE '%acme%'
INMatches any value in setStage IN ('Prospecting','Qualification')
NOT INDoes not match any valueLeadSource NOT IN ('Web','Email')
INCLUDESMulti-select picklist includesLanguages__c INCLUDES ('English')
EXCLUDESMulti-select picklist excludesLanguages__c EXCLUDES ('French')

Logical Operators — AND, OR, NOT

Logical operatorsSOQL
-- AND: both conditions must be true
SELECT Id, Name, Amount
FROM   Opportunity
WHERE  StageName = 'Closed Won'
  AND  Amount > 100000
  AND  CloseDate = THIS_YEAR

-- OR: at least one condition must be true
SELECT Id, Name, Industry
FROM   Account
WHERE  Industry = 'Technology'
  OR   Industry = 'Finance'

-- NOT: negates a condition
SELECT Id, LastName, Email
FROM   Contact
WHERE  NOT Email = null

LIKE Operator and Wildcards

The LIKE operator performs case-insensitive pattern matching. The % wildcard matches any sequence of characters; _ matches any single character.

LIKE examplesSOQL
-- Starts with 'Acme'
SELECT Id, Name FROM Account WHERE Name LIKE 'Acme%'

-- Contains 'cloud' anywhere in the name
SELECT Id, Name FROM Account WHERE Name LIKE '%cloud%'

-- Email domain matches (ends with @salesforce.com)
SELECT Id, Email FROM Contact
WHERE  Email LIKE '%@salesforce.com'

Date Literals

Salesforce provides powerful built-in date literals that automatically adjust relative to the current date and the running user's timezone. No date formatting or conversion is required.

Date LiteralMatches Records Where…
TODAYDate = today
YESTERDAYDate = yesterday
TOMORROWDate = tomorrow
THIS_WEEKDate falls in the current week (Sun–Sat)
LAST_WEEKDate falls in last week
THIS_MONTHDate falls in the current calendar month
LAST_MONTHDate falls in the previous calendar month
THIS_YEARDate falls in the current year
LAST_N_DAYS:nDate is within the last n days (including today)
NEXT_N_DAYS:nDate is within the next n days
LAST_N_MONTHS:nDate is within the last n calendar months
NEXT_N_WEEKS:nDate is within the next n weeks
Date literal examplesSOQL
-- Opportunities closing in the next 30 days
SELECT Id, Name, StageName, CloseDate, Amount
FROM   Opportunity
WHERE  CloseDate = NEXT_N_DAYS:30
  AND  IsClosed = false

-- Cases created in the last 7 days that are still open
SELECT Id, CaseNumber, Subject, Status, CreatedDate
FROM   Case
WHERE  CreatedDate >= LAST_N_DAYS:7
  AND  Status != 'Closed'

-- Leads created this month
SELECT Id, FirstName, LastName, Company, LeadSource
FROM   Lead
WHERE  CreatedDate = THIS_MONTH
ORDER BY CreatedDate DESC

Tip: Use LAST_N_DAYS:n with a colon and no spaces around it. The value n must be a positive integer and cannot be a variable — use Apex binding for dynamic ranges.

Advertisement

4 Relationships — Parent-to-Child & Child-to-Parent

Salesforce objects are connected through lookup and master-detail relationships. SOQL lets you traverse these relationships without needing SQL JOINs — using either dot notation (child to parent) or subqueries (parent to child).

Child-to-Parent — Dot Notation

When you are querying a child object and want to access fields on its parent, use dot notation to walk up the relationship. The relationship name is the field without the "Id" suffix (e.g., AccountId becomes Account).

Child-to-Parent (dot notation)SOQL
-- Query Contacts and include their parent Account's name and industry
SELECT Id, FirstName, LastName, Email,
       Account.Name,          -- parent Account Name
       Account.Industry,      -- parent Account Industry
       Account.BillingCity
FROM   Contact
WHERE  Account.Industry = 'Technology'

-- Query Opportunities and walk up to the Account's Owner
SELECT Id, Name, StageName, Amount,
       Account.Name,
       Account.Owner.Name,    -- two hops up the relationship
       Account.Owner.Email
FROM   Opportunity
WHERE  StageName = 'Closed Won'
  AND  CloseDate = THIS_YEAR

-- Custom relationship: Case with custom lookup to Product__c
SELECT Id, Subject, Related_Product__r.Name, Related_Product__r.Price__c
FROM   Case
WHERE  Related_Product__r.IsActive__c = true

Custom relationship fields: For custom lookup fields, the relationship name uses __r instead of __c. So a field named Related_Product__c becomes Related_Product__r when traversing the relationship.

Parent-to-Child — Subquery

To retrieve a parent record along with its related child records, use a subquery in the SELECT clause. The subquery uses the child relationship name (usually the plural of the child object). You can find this name in the object's relationship section in Setup.

Parent-to-Child (subquery)SOQL
-- Accounts with their related Contacts
SELECT Id, Name, Industry,
  (SELECT Id, FirstName, LastName, Email
   FROM   Contacts)           -- 'Contacts' is the child relationship name
FROM   Account
WHERE  Industry = 'Technology'
LIMIT  50

-- Accounts with their Opportunities that closed this year
SELECT Id, Name,
  (SELECT Id, Name, StageName, Amount, CloseDate
   FROM   Opportunities
   WHERE  StageName = 'Closed Won'
   AND    CloseDate = THIS_YEAR
   ORDER BY Amount DESC)
FROM   Account
WHERE  Name LIKE '%Acme%'

-- Custom parent-to-child: Project__c with its Tasks__r
SELECT Id, Name, Project_Status__c,
  (SELECT Id, Name, Due_Date__c, Assigned_To__r.Name
   FROM   Tasks__r          -- __r for custom relationships
   WHERE  Is_Complete__c = false)
FROM   Project__c

Accessing Subquery Results in Apex

Apex — iterate subquery resultsApex
List<Account> accounts = [
    SELECT Id, Name,
        (SELECT Id, LastName, Email FROM Contacts)
    FROM Account
    WHERE Industry = 'Technology'
    LIMIT 20
];

for (Account acc : accounts) {
    // getSObjects() returns the child list — may be null
    List<Contact> contacts = acc.getSObjects('Contacts');
    if (contacts != null) {
        for (Contact c : contacts) {
            System.debug(acc.Name + ' — ' + c.LastName);
        }
    }
}

5 Aggregate Functions — COUNT, SUM, AVG, MIN, MAX

SOQL supports five aggregate functions that let you perform calculations on groups of records. When aggregate functions are used, SOQL returns AggregateResult objects rather than SObject records.

Available Aggregate Functions

FunctionDescriptionApplies To
COUNT()Count of all rows (including nulls)Any field or *
COUNT(field)Count of non-null values for a fieldAny field
COUNT_DISTINCT(field)Count of distinct non-null valuesAny field
SUM(field)Sum of numeric field valuesNumber, Currency, Percent
AVG(field)Average of numeric field valuesNumber, Currency, Percent
MIN(field)Minimum valueNumber, Date, DateTime, Currency
MAX(field)Maximum valueNumber, Date, DateTime, Currency

GROUP BY — Grouping Results

Use GROUP BY with aggregate functions to compute statistics for each unique value of a field. All non-aggregate fields in the SELECT must appear in the GROUP BY clause.

GROUP BY examplesSOQL
-- Count of Opportunities by Stage
SELECT StageName, COUNT(Id) opptyCount
FROM   Opportunity
GROUP BY StageName

-- Total and average opportunity amount by Account
SELECT AccountId, Account.Name,
       SUM(Amount) totalRevenue,
       AVG(Amount) avgDeal,
       COUNT(Id)   dealCount,
       MAX(Amount) largestDeal
FROM   Opportunity
WHERE  StageName = 'Closed Won'
  AND  CloseDate = THIS_YEAR
GROUP BY AccountId, Account.Name
ORDER BY SUM(Amount) DESC
LIMIT  20

-- Cases per priority this month
SELECT Priority, COUNT(Id) caseCount,
       MIN(CreatedDate) earliestCase
FROM   Case
WHERE  CreatedDate = THIS_MONTH
GROUP BY Priority

HAVING — Filtering Grouped Results

The HAVING clause filters groups after aggregation, similar to WHERE but applied to aggregate values. You cannot use WHERE to filter on an aggregate function result.

HAVING examplesSOQL
-- Accounts with more than 10 open opportunities
SELECT AccountId, COUNT(Id) openDeals
FROM   Opportunity
WHERE  IsClosed = false
GROUP BY AccountId
HAVING COUNT(Id) > 10

-- Product categories with total sales above $500,000
SELECT Category__c, SUM(Amount__c) totalSales
FROM   Order_Line__c
GROUP BY Category__c
HAVING SUM(Amount__c) > 500000
ORDER BY SUM(Amount__c) DESC

Reading AggregateResult in Apex

AggregateResult in ApexApex
List<AggregateResult> results = [
    SELECT StageName, COUNT(Id) cnt, SUM(Amount) total
    FROM Opportunity
    WHERE CloseDate = THIS_YEAR
    GROUP BY StageName
];

for (AggregateResult ar : results) {
    String stage  = (String)  ar.get('StageName');
    Integer count = (Integer) ar.get('cnt');
    Decimal total = (Decimal) ar.get('total');
    System.debug(stage + ': ' + count + ' deals — $' + total);
}

Aliasing: You can give aggregate functions an alias (e.g., COUNT(Id) cnt) and then retrieve the value with ar.get('cnt'). Without an alias, use ar.get('expr0'), ar.get('expr1'), etc.

6 ORDER BY, LIMIT, and OFFSET

These three clauses control the ordering and pagination of your query results.

ORDER BY

Sort results by one or more fields. Use ASC (ascending, default) or DESC (descending). Handle null values with NULLS FIRST or NULLS LAST.

ORDER BYSOQL
-- Sort by close date descending, then by amount descending
SELECT Id, Name, CloseDate, Amount
FROM   Opportunity
ORDER BY CloseDate DESC, Amount DESC
LIMIT  100

-- Put records where Email is null at the end
SELECT Id, LastName, Email
FROM   Contact
ORDER BY Email ASC NULLS LAST

LIMIT

LIMIT restricts the maximum number of rows returned. Always include a LIMIT in ad-hoc queries and set a sensible ceiling in production code to avoid hitting the 50,000 row governor limit.

OFFSET — Pagination

OFFSET skips a specified number of records at the start of the result set. Combined with LIMIT, it enables page-by-page data retrieval. The maximum OFFSET value is 2,000.

Pagination with LIMIT + OFFSETSOQL
-- Page 1: records 1–20
SELECT Id, Name, Industry FROM Account
ORDER BY Name ASC
LIMIT  20 OFFSET 0

-- Page 2: records 21–40
SELECT Id, Name, Industry FROM Account
ORDER BY Name ASC
LIMIT  20 OFFSET 20

-- Page 3: records 41–60
SELECT Id, Name, Industry FROM Account
ORDER BY Name ASC
LIMIT  20 OFFSET 40

OFFSET limitation: OFFSET is capped at 2,000. For querying large data sets beyond that limit, use cursor-based pagination with Database.getQueryLocator() inside a Batch Apex class, which handles up to 50 million records.

7 Semi-joins and Anti-joins

SOQL supports using a subquery inside an IN or NOT IN clause to filter records based on the existence (or absence) of related records. These are called semi-joins and anti-joins respectively.

Semi-join — IN with Subquery

A semi-join returns records where a field value matches the result of an inner subquery. The inner query must return a single Id field.

Semi-join examplesSOQL
-- Contacts whose Account has at least one Closed Won Opportunity
SELECT Id, FirstName, LastName, Email
FROM   Contact
WHERE  AccountId IN (
    SELECT AccountId
    FROM   Opportunity
    WHERE  StageName = 'Closed Won'
)

-- Users who are members of a specific Permission Set
SELECT Id, Name, Email
FROM   User
WHERE  Id IN (
    SELECT AssigneeId
    FROM   PermissionSetAssignment
    WHERE  PermissionSet.Name = 'Salesforce_API_Access'
)

Anti-join — NOT IN with Subquery

An anti-join returns records that do not have related records matching the inner query — the inverse of a semi-join.

Anti-join examplesSOQL
-- Accounts that have NO Opportunities at all
SELECT Id, Name, Industry, CreatedDate
FROM   Account
WHERE  Id NOT IN (
    SELECT AccountId
    FROM   Opportunity
    WHERE  AccountId != null
)

-- Contacts who have no open Cases
SELECT Id, Name, Email
FROM   Contact
WHERE  Id NOT IN (
    SELECT ContactId
    FROM   Case
    WHERE  Status != 'Closed'
    AND    ContactId != null
)

Performance note: Semi-joins and anti-joins that return very large inner sets can be slow. Where possible, filter the inner query tightly or use an alternative approach with Apex collections.

Advertisement

8 SOQL in Apex — Binding Variables & For Loops

SOQL is embedded directly inside Apex code using inline query syntax surrounded by square brackets. This inline SOQL is validated at compile time, giving you early error detection.

Inline SOQL and Variable Binding

You can bind Apex variables directly into a SOQL query using the colon (:) prefix. Bound variables are automatically escaped, which prevents SOQL injection attacks — never concatenate user-supplied strings into queries.

Binding variablesApex
// Single variable binding
String targetIndustry = 'Technology';
List<Account> techAccounts = [
    SELECT Id, Name, Phone
    FROM   Account
    WHERE  Industry = :targetIndustry
    ORDER BY Name
    LIMIT  200
];

// List/Set binding — great for IN clauses
Set<Id> accountIds = new Set<Id>{'001xx000001A2bCAAE', '001xx000001B3cDAAE'};
List<Account> specificAccounts = [
    SELECT Id, Name, Industry, AnnualRevenue
    FROM   Account
    WHERE  Id IN :accountIds
];

// Integer/Decimal binding
Decimal minimumAmount = 50000;
Date   cutoffDate    = Date.today().addDays(-30);
List<Opportunity> bigRecentDeals = [
    SELECT Id, Name, Amount, CloseDate, StageName
    FROM   Opportunity
    WHERE  Amount >= :minimumAmount
    AND    CloseDate >= :cutoffDate
    ORDER BY Amount DESC
];

SOQL For Loops — Memory-Efficient Processing

A SOQL for loop retrieves records in chunks of 200 and processes them without loading all records into heap memory at once. This is the preferred pattern when processing large result sets in Apex, as it respects heap size limits.

SOQL for loop patternsApex
// Pattern 1: Iterate one record at a time (200 fetched per batch internally)
for (Contact c : [
    SELECT Id, Email, DoNotContact__c
    FROM   Contact
    WHERE  HasOptedOutOfEmail = false
]) {
    // process each contact
    c.Last_Contacted__c = Date.today();
}

// Pattern 2: Chunked list — process 200 records at a time explicitly
List<Account> toUpdate = new List<Account>();
for (List<Account> chunk : [
    SELECT Id, Name, Rating
    FROM   Account
    WHERE  Industry = 'Technology'
]) {
    for (Account acc : chunk) {
        acc.Rating = 'Hot';
        toUpdate.add(acc);
    }
    if (toUpdate.size() >= 200) {
        update toUpdate;
        toUpdate.clear();
    }
}
if (!toUpdate.isEmpty()) { update toUpdate; }

Database.query() — Dynamic SOQL

When you need to build a query dynamically at runtime (e.g., user-selected filters), use Database.query(). Be very careful to sanitise any user-supplied input using String.escapeSingleQuotes() to prevent SOQL injection.

Dynamic SOQLApex
String objectName = 'Account';
String filterField = 'Industry';
// ALWAYS escape user-supplied strings
String filterValue = String.escapeSingleQuotes(userInput);

String soql = 'SELECT Id, Name FROM '
            + objectName
            + ' WHERE ' + filterField
            + ' = \'' + filterValue + '\' LIMIT 200';

List<SObject> results = Database.query(soql);

9 Best Practices

Writing efficient, safe SOQL is essential for building applications that perform well within Salesforce's governor limits. Follow these practices consistently.

1. Never Put SOQL Inside a Loop

This is the single most common governor limit violation. Each SOQL call inside a loop counts against the 100 SOQL queries per transaction limit. Instead, collect all the IDs or criteria first, then run one query and map results.

Anti-pattern — SOQL in loop

Running a SOQL query inside a for loop over records burns through governor limits rapidly and will throw a LimitException with large data volumes.

Correct — Query once, map results

Collect all needed IDs into a Set before the loop, run one SOQL query with WHERE Id IN :idSet, build a Map, and access it inside the loop in O(1) time.

Bad vs Good patternApex
// BAD: SOQL inside a loop — hits governor limits fast
for (Opportunity opp : opportunities) {
    Account acc = [SELECT Id, Name FROM Account WHERE Id = :opp.AccountId]; // WRONG
}

// GOOD: One query outside the loop, Map lookup inside
Set<Id> accountIds = new Set<Id>();
for (Opportunity opp : opportunities) {
    accountIds.add(opp.AccountId);
}
Map<Id, Account> accountMap = new Map<Id, Account>([
    SELECT Id, Name, Industry
    FROM   Account
    WHERE  Id IN :accountIds
]);
for (Opportunity opp : opportunities) {
    Account acc = accountMap.get(opp.AccountId); // safe O(1) lookup
}

2. Use WITH SECURITY_ENFORCED

Adding WITH SECURITY_ENFORCED to a query ensures that the running user's field-level security (FLS) and object permissions are respected. If the user does not have access to a queried field, a System.QueryException is thrown. This is especially important in Visualforce pages and LWC components that run in user context.

WITH SECURITY_ENFORCEDSOQL
SELECT Id, Name, Salary__c
FROM   Employee__c
WITH SECURITY_ENFORCED
WHERE  Department__c = 'Engineering'
LIMIT  100

3. Always Use LIMIT

Always add a LIMIT to queries in production code. Without it, a query could return tens of thousands of rows, consuming heap memory and time. Use Database.getQueryLocator() in Batch Apex for processing millions of records.

4. Selective Queries — Use Indexed Fields in WHERE

Salesforce automatically indexes Id, Name, OwnerId, CreatedDate, SystemModstamp, and foreign key fields (lookups and master-detail). Filtering on indexed fields produces selective queries that run much faster. Non-selective queries on large objects can time out.

5. Query Only the Fields You Need

Avoid retrieving unnecessary fields. Every extra field increases heap size consumption. On objects with very wide field definitions, querying all fields can significantly impact performance and push you toward heap limits.

6. Use Apex Binding Instead of String Concatenation

Always use the colon binding syntax (WHERE Name = :myVar) in static SOQL. For dynamic SOQL, always call String.escapeSingleQuotes() on any user-supplied or external input before injecting it into a query string.

10 Common Mistakes to Avoid

Mistake 1 — Querying in a Loop (SOQL in Loops)

Already covered in best practices, but worth repeating: putting a SOQL query inside any type of loop (for, while, do-while) will throw a System.LimitException: Too many SOQL queries: 101 error once the loop exceeds 100 iterations in a single transaction.

Mistake 2 — Forgetting to Handle Empty Results

When querying for a single record using a query that might return zero rows, do not assume a result exists. Wrap single-record inline queries in a try-catch or use a List and check its size.

Safe single-record queryApex
// RISKY: throws QueryException if no record found
Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Acme' LIMIT 1];

// SAFE: use a List, check size first
List<Account> accs = [SELECT Id, Name FROM Account WHERE Name = 'Acme' LIMIT 1];
if (!accs.isEmpty()) {
    Account acc = accs[0];
    // safe to use acc here
}

Mistake 3 — Using SELECT * (Wildcard)

SOQL does not support SELECT *. You will get a compile-time error. You must explicitly list every field. If you need all fields dynamically, use Schema.SObjectType.Account.fields.getMap().keySet() to build the field list at runtime.

Mistake 4 — Not Filtering Subquery Results for Null

When using anti-joins (NOT IN with a subquery), if the inner subquery field can be null, a null value in the inner result set will cause the outer query to return zero records. Always add a WHERE fieldName != null filter inside the inner subquery.

Mistake 5 — Ignoring Governor Limits in Async Context

Batch Apex, Queueable, and Scheduled Apex each have their own governor limit context. Batch Apex execute() allows 200 SOQL queries per chunk, but each execute() call is a separate transaction. Always design your queries with these limits in mind, and use Limits.getQueries() during development to monitor consumption.

Mistake 6 — Non-Selective Queries on Large Objects

Querying a large object (over 1 million records) without a selective WHERE clause — for example, filtering on a non-indexed custom text field — can time out or produce a "System.QueryException: Non-selective query against large object type" error. Always filter on indexed fields, use CreatedDate ranges, or add a custom index via Salesforce support.

Advertisement

Frequently Asked Questions

What is SOQL in Salesforce?

SOQL (Salesforce Object Query Language) is a SELECT-only query language used to retrieve records from the Salesforce database. Unlike SQL, it cannot perform INSERT, UPDATE, DELETE, or JOIN operations directly. It is purpose-built to query Salesforce objects (standard and custom) and navigate object relationships using dot notation and subqueries. SOQL is used inside Apex code, Flows, REST API requests, and the Developer Console.

How do I write a basic SOQL query?

A basic SOQL query follows this pattern: SELECT Field1, Field2 FROM ObjectApiName WHERE Condition ORDER BY Field LIMIT n. For example: SELECT Id, Name, Industry FROM Account WHERE Industry = 'Technology' ORDER BY Name LIMIT 50. You must always specify at least one field — SOQL does not support SELECT *. The WHERE, ORDER BY, and LIMIT clauses are all optional but recommended for production code.

What is the difference between SOQL and SQL?

SOQL is modelled after SQL but has key differences: (1) SOQL is read-only — no INSERT, UPDATE, or DELETE (use DML statements for those); (2) SOQL does not support wildcards like SELECT *; (3) SOQL uses relationship traversal (dot notation and subqueries) instead of JOIN; (4) SOQL has Salesforce-specific date literals like TODAY, LAST_N_DAYS:7, and THIS_MONTH; (5) SOQL runs inside Apex and enforces governor limits including a maximum of 100 SOQL queries per transaction and 50,000 rows returned per query.

How do I avoid SOQL in loops in Apex?

Move all SOQL queries outside any loop. Collect the IDs or values you need while iterating first, then run a single bulkified query after the loop. Store results in a Map<Id, SObject> for O(1) access. For example, instead of querying inside a for loop over Opportunity records, first collect all AccountIds into a Set, then run new Map<Id, Account>([SELECT Id, Name FROM Account WHERE Id IN :accountIds]) and access accountMap.get(opp.AccountId) inside the loop.