SELECT

On this page 7

SELECT is the SQL statement that retrieves data from one or more tables, views or derived results and returns it as a result set of rows and columns. It is declarative: the query states which data is wanted, under which conditions and in which order, while the database engine’s query optimizer decides how to read indexes, join tables and sort the output.

In SQL and relational databases, SELECT is by far the most frequently executed statement and the foundation for nearly every other query feature, including JOIN, GROUP BY, HAVING, subqueries, CTEs and window functions. A plain SELECT does not modify data, but its correctness and performance determine whether reports are right and whether an application stays fast as tables grow.

Structure of a SELECT statement

A SELECT statement consists of a mandatory select list and a series of optional clauses, each of which narrows, combines or shapes the data. The basic form is SELECT columns FROM table WHERE condition ORDER BY column, and every clause has a precisely defined role.

The table lists the clauses in the order in which they are written and explains what each one does.

ClausePurposeExample
SELECTLists the columns or expressions to returnSELECT name, price * 1.2 AS gross
FROMNames the source tables, views or subqueriesFROM products p
JOIN … ONCombines rows from related tablesJOIN categories c ON c.id = p.category_id
WHEREFilters individual rows before groupingWHERE p.active = TRUE
GROUP BYCollapses rows into groups for aggregationGROUP BY c.name
HAVINGFilters groups after aggregationHAVING COUNT(*) > 10
ORDER BYSorts the final resultORDER BY gross DESC
LIMIT or FETCH FIRSTRestricts the number of returned rowsFETCH FIRST 20 ROWS ONLY
Clauses of a SELECT statement

The keyword DISTINCT after SELECT removes duplicate rows from the result, and column aliases created with AS rename output columns. Without ORDER BY, the order of rows is not guaranteed, even if a query happens to return them sorted during testing.

Logical order of evaluation

A SELECT statement is not evaluated in the order it is written. Logically, the engine first builds the source rows, then filters, groups and projects them, and only at the end sorts and limits the result. This logical order explains many error messages and surprising results.

The following table shows the logical processing order and its practical consequence.

StepClausePractical consequence
1FROM and JOINAll source rows and join results are formed first
2WHERECannot use aggregates or most select-list aliases
3GROUP BYEach output row now represents a group
4HAVINGCan filter on aggregates such as SUM or COUNT
5Window functionsComputed over the grouped and filtered rows
6SELECT listExpressions and aliases are evaluated here
7DISTINCTDuplicates are removed from the projected rows
8ORDER BYCan usually refer to select-list aliases
9LIMIT, OFFSET or FETCHApplied last, to the sorted result
Logical processing order of SELECT

This is a logical model, not the physical execution plan. The optimizer may push filters into joins, use an index to avoid sorting or stop reading early once the limit is reached, as long as the result is the same as the logical order would produce.

Dialect differences across database systems

The core of SELECT is defined by the ISO/IEC 9075 SQL standard, but database systems differ in details, especially in how they limit rows and handle grouping. Portable code relies on the standard forms where the target systems support them.

The table compares common syntax for returning the first ten rows and related behavior.

SystemFirst ten rowsNotable behavior
PostgreSQLLIMIT 10 or FETCH FIRST 10 ROWS ONLYStrict GROUP BY rules; supports DISTINCT ON
MySQLLIMIT 10ONLY_FULL_GROUP_BY enforced by default since 5.7
SQL ServerSELECT TOP 10 or OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLYOFFSET and FETCH require ORDER BY
Oracle DatabaseFETCH FIRST 10 ROWS ONLY since 12cOlder code uses ROWNUM, applied before ORDER BY
SQLiteLIMIT 10Dynamic typing can affect comparisons
Row limiting and related behavior by system

The Oracle ROWNUM case illustrates why the logical order matters: filtering on ROWNUM in the same query level as ORDER BY returns ten arbitrary rows, sorted afterward, rather than the top ten. The standard FETCH FIRST syntax avoids this trap.

Writing efficient SELECT queries

The performance of a SELECT depends mainly on how many rows the engine must read, not on how many it returns. A query that filters on an indexed column with good selectivity can locate a few rows among millions in milliseconds, while the same query without a usable index scans the whole table.

  • Select only the needed columns instead of SELECT *; this reduces input and output, network transfer and enables covering indexes.
  • Keep WHERE conditions sargable: wrapping an indexed column in a function, as in WHERE YEAR(created_at) = 2025, usually prevents index use, while a range condition on the raw column does not.
  • Use keyset pagination (WHERE id > last_seen ORDER BY id LIMIT 50) for deep pages, because a large OFFSET still reads and discards every skipped row.
  • Inspect the execution plan with EXPLAIN or the system’s equivalent before tuning, rather than guessing.
  • Use parameterized queries so that values never become part of the SQL text, which prevents SQL injection and allows plan reuse.

Common mistakes with SELECT

Most SELECT errors produce results that look plausible but are wrong, which makes them more dangerous than syntax errors. The table lists frequent mistakes, their symptoms and the correction.

MistakeSymptomCorrection
Comparing with = NULLNo rows returnedUse IS NULL or IS NOT NULL
NOT IN with a NULL in the listEmpty result despite matching dataUse NOT EXISTS instead
Missing join conditionRow count explodes to a Cartesian productJoin on the correct key columns
COUNT(column) instead of COUNT(*)Counts skip rows where the column is NULLChoose the count deliberately
Relying on implicit orderRow order changes after an upgrade or new indexAlways specify ORDER BY
Filtering aggregates in WHERESyntax errorMove the condition to HAVING
Frequent SELECT mistakes

These mistakes share one root cause: SQL uses three-valued logic with TRUE, FALSE and UNKNOWN, and any comparison with NULL yields UNKNOWN, which WHERE treats as not satisfied.

Put simply

SELECT is how you ask a database a question. You describe what you want to see, and the database finds it for you. It is like asking a librarian for all mystery novels published after 2010, sorted by author. You do not tell the librarian which shelves to walk past.

The answer always comes back as a neat table of rows and columns. Asking the question never changes the books themselves. The main skill is phrasing the question precisely, because a slightly wrong question still gets an answer, just not the right one.

In practice

An analyst at an online retailer needs the five product categories with the highest revenue in the last full quarter, counting only completed orders. The data lives in three tables: orders, order_items and products.

The query joins the tables, filters with WHERE o.status = 'completed' AND o.created_at >= DATE '2026-07-01' AND o.created_at < DATE '2026-10-01', groups by category, sums quantity times unit price, sorts by that sum in descending order and fetches the first five rows. Using a half-open date range instead of wrapping the date column in a function lets the engine use the index on created_at, and it also includes orders placed late on September 30.

On a table of about 12 million orders, the execution plan shows an index range scan over roughly one quarter’s rows instead of a full scan, and the query returns in under a second. The analyst saves it as a view so the monthly report runs the same verified logic every time.