SOQL is the Salesforce Object Query Language, and it is the only way to read records out of the database from Apex. Every report, every list view, every piece of code that fetches an Account or a Contact is using SOQL under the hood. The shape of the language stays the same whether you are pulling one record or a million.
A SOQL query has three pieces, and the simplest useful version looks like this:
List<Account> results = [SELECT Id, Name FROM Account WHERE Rating = 'Hot'];
Read it left to right:
SELECT Id, Name says which fields you want back. Id is almost always the first one, because it is the unique handle for the record. The rest is whatever you actually need to use.FROM Account names the sObject the query runs against. The sObject has to be one that exists in the org, and you usually need at least read permission on it.WHERE Rating = 'Hot' is the filter. Only records whose Rating field equals the string Hot are returned. Skip the WHERE and the query returns every record in the table, which is rarely what you want.The query lives inside square brackets, and the result is a List<Sobject>. Each element is one record. A SOQL query always returns a list, even if there is only one match or no match at all.
The string 'Hot' is a literal, and you will often want to filter on a value that comes from somewhere else, like a method parameter. SOQL lets you bind Apex variables directly into the query with a colon:
public static Integer countAccountsWithRating(String rating) {
Integer result = [SELECT COUNT() FROM Account WHERE Rating = :rating];
return result;
}
The colon turns rating from a literal into a placeholder. Apex evaluates the variable, looks up its value, and substitutes it into the query as a safely quoted value. The same pattern works for any primitive, and it is the standard way to write a parameterized query.
COUNT() is a special form: it returns the number of matching rows as a single Integer, so the result is not a list. That makes it the right tool for "how many" questions and the easiest SOQL to assert on in a test.
You might be tempted to load every Account into a list and count them in Apex. That works on a tiny org, but real orgs have millions of records. SOQL runs the filter in the database and only ships matching rows back over the network, so the same code that does 30 rows on a scratch org still does 30 rows on a production org with three million. The lesson to take away: ask the database for what you want, do not ask it for everything and then filter in code.
The most common slip is forgetting the WHERE clause. The query still compiles, and the user gets every Account in the org instead of the Hot ones. When a number looks "too big" in a test result, the missing WHERE is the first thing to check.
A second slip is putting a single-quoted literal on the left side of the comparison. SOQL accepts WHERE Rating = 'Hot' and WHERE 'Hot' = Rating, but only because the right-hand side happens to be a string. Putting a method call on the right side without a colon is a compile error: WHERE Rating = rating is interpreted as comparing a string to an unquoted identifier, and the parser rejects it. The colon is what binds the variable.
A third slip is treating an empty result as an error. A query that matches nothing returns an empty list (or 0 for a COUNT()), not a thrown exception. The code has to handle "no rows" the same as "any other list".
Almost every Apex feature that talks to the database starts with a SOQL query. Batch classes use SOQL to walk large data sets. Triggers use SOQL to find related records. REST endpoints use SOQL to build their response. Learning the SELECT-FROM-WHERE pattern well is the foundation for everything that comes after in this topic.