# SQL SELECT Statement: Syntax, Clauses and Examples

The SQL SELECT statement is the command you use to read data from a database. You name the columns you want, point to a table with FROM, and add clauses to filter, sort, group or limit the rows that come back. This guide walks through the syntax of SQL SELECT commands and shows each clause with runnable examples.

## Quick Answer

- SELECT chooses which columns to return, and FROM names the source table.
- WHERE filters rows before grouping, and it must evaluate to a Boolean result [1].
- ORDER BY sorts the output. Without it, rows come back in whatever order the system finds fastest to produce [2].
- GROUP BY collapses rows into groups so aggregate functions like SUM and COUNT can summarize each group [3].
- LIMIT (or FETCH FIRST) returns only a subset of the result rows [2].

## Before You Start

You need a database connection and SELECT privilege on every column you query [2]. In SQL Server, that privilege can come from a higher scope such as SELECT permission on the schema, or from membership in the db_datareader or db_owner fixed database roles [4].

You also need a table with data in it. The examples below use a small sales table with columns for region, product, units and revenue. If you want to follow along, create it with the setup code in the Worked Example section.

One habit worth building early: always list columns explicitly instead of using `SELECT *`. Explicit column lists make your output stable when the table structure changes, and MySQL's own guidance for application code says to always use explicit column-name selections [5].

## Step by Step

1. **Pick your columns.** The select list decides what appears in the output. `SELECT region, product FROM sales;` returns two columns. You can rename a column with an alias, as in `SELECT actor_id AS ID FROM sakila.actor;` [3]. See [SQL Alias: Syntax and Examples for Tables and Columns](/blog/data-analysis/sql-alias) for the full syntax.

2. **Name the table.** The FROM clause tells the database where the rows live. `FROM sales` reads from the sales table. You can join several tables here when the data is spread across them.

3. **Filter rows with WHERE.** WHERE keeps only rows where the condition is true. `WHERE first_name LIKE 'Cate'` returns matching rows from the actor table [3]. The condition must return a Boolean result [1]. For set membership, see [SQL IN Operator: Syntax and Examples](/blog/data-analysis/sql-in-operator-syntax-examples).

4. **Group rows with GROUP BY.** When you want one row per category, add GROUP BY and an aggregate function. `SELECT rating AS label, count(rating) AS value FROM sakila.film GROUP BY rating;` returns a count for each rating [3].

5. **Sort with ORDER BY.** Add ORDER BY to control the sequence. `ORDER BY SUM(revenue) DESC` puts the largest totals first. If you skip ORDER BY, the row order is not guaranteed [2].

6. **Limit the output with LIMIT.** LIMIT returns only a subset of rows [2]. This is useful for previewing a large table or building a top-N report.

7. **Combine the clauses in order.** A typical statement reads SELECT, FROM, WHERE, GROUP BY, ORDER BY, LIMIT. Each clause narrows or reshapes what the previous one produced.

A useful mental model is that WHERE filters individual rows, while GROUP BY and its aggregates summarize the rows that survive the filter. Sorting and limiting happen last, on the summarized result.

## Worked Example

The sales table below holds six rows across three regions and two products.

| region | product | units | revenue |
|---|---|---|---|
| North | Widget | 120 | 2400 |
| North | Gadget | 80 | 3200 |
| South | Widget | 150 | 3000 |
| South | Gadget | 60 | 1800 |
| East | Widget | 90 | 1800 |
| East | Gadget | 110 | 4400 |

The goal is total revenue per region, sorted from highest to lowest.

```sql
SELECT region, SUM(revenue) AS total_revenue
FROM sales
GROUP BY region
ORDER BY SUM(revenue) DESC;
```

Here is what each part does:

1. `SELECT region, SUM(revenue) AS total_revenue` chooses the grouping column and aggregates revenue per region.
2. `FROM sales` specifies the source table.
3. `GROUP BY region` collapses rows into one row per region.
4. `ORDER BY SUM(revenue) DESC` sorts regions by total revenue, highest first.

The result has one row per region:

| region | total_revenue |
|---|---|
| East | 6200 |
| North | 5600 |
| South | 4800 |

The result was checked with an equivalent SQLite query.
Notice that the aggregate appears twice: once in the select list with an alias, and once inside ORDER BY. Some databases let you sort by the alias instead, but writing the full expression works everywhere.

## Other Ways to Do It

**Sort by alias or position.** Many databases accept `ORDER BY total_revenue DESC` or `ORDER BY 2 DESC` in place of the full aggregate expression. The positional form is shorter but harder to read once the query grows.

**Filter groups with HAVING.** WHERE runs before grouping, so it cannot test an aggregate. To keep only regions above a revenue threshold, use HAVING after GROUP BY. The pattern is `GROUP BY region HAVING SUM(revenue) > 5000`.

**Remove duplicates with DISTINCT.** `SELECT DISTINCT` eliminates duplicate rows from the result, and `SELECT ALL` is the default that keeps them [2]. Use DISTINCT when you want the list of distinct values rather than counts.

**Break a query into steps.** A [Common Table Expression SQL: Syntax and Examples](/blog/data-analysis/common-table-expression-sql) lets you name an intermediate result and reuse it, which keeps long queries readable.

**Filter with a subquery.** When the filter depends on another query's output, a subquery in the WHERE clause does the job. See [Subqueries in SQL: Types, Syntax and Examples](/blog/data-analysis/subqueries-in-sql-types-examples) for the patterns.

**Conditional logic in the select list.** A CASE expression lets you label or bucket values inside the output. See [Using CASE in SQL: Syntax and Examples](/blog/data-analysis/using-case-in-sql).

**Combine result sets.** The UNION, EXCEPT and INTERSECT operators combine or compare the results of two queries into one result set [4].

## Troubleshooting

**The query returns no rows.** Check the WHERE condition first. A string comparison that expects an exact match will fail on trailing spaces or different casing in some databases. Loosen it with LIKE and a wildcard pattern such as `'%han%'` [3].

**You get an error about a column not being in GROUP BY.** Every column in the select list must either appear in GROUP BY or be wrapped in an aggregate function. Add the column to GROUP BY or remove it from the select list.

**The row order changes between runs.** You did not use ORDER BY. Without it, the database returns rows in whatever order is fastest to produce [2]. Add an explicit sort.

**LIMIT returns fewer rows than expected.** LIMIT caps the output after filtering and sorting. If WHERE removed most rows, the limit applies to what remains.

**A permission error appears.** You need SELECT privilege on each column used in the command [2]. In SQL Server, check whether your role grants that access [4].

**The aggregate looks wrong.** Confirm that WHERE is not accidentally filtering rows you meant to keep. A filter on the wrong column silently changes every total.

## Common Mistakes

- **Using WHERE to filter an aggregate.** `WHERE SUM(revenue) > 5000` fails because WHERE runs before grouping. Use HAVING after GROUP BY instead.
- **Forgetting ORDER BY and assuming sorted output.** Row order is not guaranteed without an explicit sort [2]. Add ORDER BY whenever the sequence matters.
- **Selecting columns that are not grouped or aggregated.** This causes an error in strict databases and misleading values in lenient ones. Every non-aggregated column belongs in GROUP BY.
- **Using SELECT * in application code.** Column lists change and break downstream code. MySQL's guidance is to always use explicit column-name selections [5].
- **Confusing LIMIT with filtering.** LIMIT trims the result set, it does not select which rows qualify. Use WHERE for that decision.
- **Writing a WHERE condition that returns a non-Boolean value.** The WHERE clause must contain an expression that returns a Boolean result [1]. Comparisons and logical operators belong there.

## Limitations

SELECT reads data, it does not change it. If you need to modify rows, you use INSERT, UPDATE or DELETE. SELECT also only sees what your permissions allow, so a query that works for one user may fail for another with narrower access [2][4].

Row order without ORDER BY is unpredictable, and that unpredictability can hide bugs during testing. A query that appears to return sorted data on a small table may return a different order once the table grows or the query plan changes [2]. Treat any unstated ordering as a coincidence, not a feature.

## Frequently Asked Questions

### What is the difference between WHERE and HAVING?

WHERE filters individual rows before grouping, and it must return a Boolean result [1]. HAVING filters groups after aggregation, so it can test aggregate functions like SUM or COUNT. If your condition mentions an aggregate, it belongs in HAVING.

### Does SELECT return rows in a specific order?

No. If ORDER BY is not given, rows are returned in whatever order the system finds fastest to produce [2]. Add ORDER BY with one or more columns to get a predictable sequence.

### What does SELECT DISTINCT do?

SELECT DISTINCT eliminates duplicate rows from the result, while SELECT ALL, the default, returns all candidate rows including duplicates [2]. Use DISTINCT when you want the unique values in a column or combination of columns.

### Can I use SELECT without a FROM clause?

In several databases you can, for example to evaluate an expression or call a function. The behavior varies by product, so check your database's documentation before relying on it in portable code.

### How do I return only the top rows?

Use LIMIT, or FETCH FIRST, to return a subset of the result rows [2]. Combine it with ORDER BY so the subset is the one you actually want, such as the highest revenue regions. For more complete query patterns, see [SQL Query Examples: 10 Practical SELECT Statements](/blog/data-analysis/sql-query-examples).

## References

1. [SELECT (DMX) - SQL Server | Microsoft Learn](https://learn.microsoft.com/en-us/sql/dmx/select-dmx?view=sql-server-ver17)
2. [PostgreSQL: Documentation: 18: SELECT](https://www.postgresql.org/docs/current/sql-select.html)
3. [MySQL :: MySQL Shell for VS Code :: 7.2 Retrieve Data with SELECT Statements](https://dev.mysql.com/doc/mysql-shell-gui/en/mysql-shell-vscode-basic-select-statement.html)
4. [SELECT (Transact-SQL) - SQL Server | Microsoft Learn](https://learn.microsoft.com/en-us/sql/t-sql/queries/select-transact-sql?view=sql-server-ver17)
5. [MySQL :: MySQL 9.7 Reference Manual :: 22.3.4.2 Select Tables](https://dev.mysql.com/doc/refman/9.7/en/mysql-shell-tutorial-javascript-table-select.html)

## Further Reading

- [Codd EF (1970). A relational model of data for large shared data banks. Communications of the ACM](https://doi.org/10.1145/362384.362685)
- [SQLite: SQL As Understood By SQLite](https://www.sqlite.org/lang.html)
- [Wilson G, Bryan J, Cranston K et al. (2017). Good enough practices in scientific computing. PLOS Computational Biology](https://doi.org/10.1371/journal.pcbi.1005510)

## Related Articles

- [SQL Query Examples: 10 Practical SELECT Statements](/blog/data-analysis/sql-query-examples)
- [SQL IN Operator: Syntax and Examples](/blog/data-analysis/sql-in-operator-syntax-examples)
- [Using CASE in SQL: Syntax and Examples](/blog/data-analysis/using-case-in-sql)
- [SQL RIGHT Function: Syntax, Examples and Use Cases](/blog/data-analysis/sql-right-function-syntax-examples)
- [SQL CONCAT Function: Syntax and Examples](/blog/data-analysis/sql-concat-function)