The SELECT statement is how you read data out of a database, and it is the statement you will write more than any other. Everything from a login check to a monthly sales report is, underneath, a SELECT. Learn its clauses well and most of SQL falls into place.
This tutorial uses the same company schema as the rest of the series. If you have not met it yet, skim what is SQL first — it introduces the employees and departments tables we query below.
Our sample data
Here are the rows we will work with. Keep them in view as you read the queries.
-- employees
emp_id | emp_name | department_id | salary | city
-------+----------+---------------+--------+-----------
101 | Aarti | 1 | 78000 | Hyderabad
102 | Bhaskar | 1 | 65000 | Hyderabad
103 | Chitra | 2 | 52000 | Bengaluru
104 | Devan | 2 | 58000 | Bengaluru
105 | Esha | 3 | 47000 | Hyderabad
106 | Farhan | 4 | 90000 | Pune
The simplest SELECT
At minimum, a SELECT names the columns you want and the table they come from:
SELECT emp_name, salary
FROM employees;
This returns two columns and all six rows. If you want every column, SELECT * is a shortcut — handy while exploring, but you should name columns in real code so you fetch only what you use.
SELECT * FROM employees;
Common mistake: Reaching for
SELECT *in application code. It pulls columns you do not need, breaks when someone adds a column, and hides your true intent. Always list the columns your code actually reads.
Filtering rows with WHERE
Most of the time you do not want every row — you want rows that meet a condition. That is the job of WHERE.
SELECT emp_name, city, salary
FROM employees
WHERE city = 'Hyderabad';
WHERE supports the comparison and logical operators you would expect:
-- salary in a range, in a specific department
SELECT emp_name, salary
FROM employees
WHERE salary BETWEEN 50000 AND 80000
AND department_id = 1;
-- match several cities at once
SELECT emp_name, city
FROM employees
WHERE city IN ('Hyderabad', 'Pune');
-- pattern match: names starting with A or B
SELECT emp_name
FROM employees
WHERE emp_name LIKE 'A%' OR emp_name LIKE 'B%';
BETWEEN, IN and LIKE show up constantly. LIKE with % (any run of characters) and _ (a single character) is your basic text search, and it appears in interviews often.
Pro tip: To test for missing values, use
IS NULLandIS NULL, never= NULL. In SQL,NULLmeans "unknown," and comparing anything to an unknown gives another unknown — sosalary = NULLmatches no rows, even the ones that are actually null.
Sorting results with ORDER BY
Databases do not promise any particular row order unless you ask. ORDER BY fixes the order, ascending by default or descending with DESC.
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC;
You can sort by several columns. The database sorts by the first, breaks ties with the second, and so on:
SELECT emp_name, city, salary
FROM employees
ORDER BY city ASC, salary DESC;
This lists employees grouped by city alphabetically, and within each city, highest salary first.
Limiting how many rows come back
When you only need the top few results — the three highest earners, say — combine ORDER BY with LIMIT:
SELECT emp_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;
This is exactly how "top N" reports are built. Note that LIMIT is MySQL and PostgreSQL syntax; SQL Server uses TOP and Oracle uses FETCH FIRST 3 ROWS ONLY. The differences between engines like these are covered in MySQL vs PostgreSQL.
Removing duplicates with DISTINCT
To see the unique values in a column, add DISTINCT:
SELECT DISTINCT city
FROM employees;
Against our data this returns three rows — Hyderabad, Bengaluru, Pune — even though six employees exist. DISTINCT applies to the entire selected row, so SELECT DISTINCT city, department_id gives unique combinations, not unique cities.
The clause order that explains everything
Beginners are often surprised that this query fails:
-- This does NOT work
SELECT emp_name, salary * 12 AS annual_pay
FROM employees
WHERE annual_pay > 600000;
The database complains that annual_pay does not exist in WHERE. The reason is that SQL clauses do not run in the order you write them. The logical execution order is:
| Step | Clause | What it does |
|---|---|---|
| 1 | FROM | Choose the source table |
| 2 | WHERE | Filter individual rows |
| 3 | GROUP BY | Collapse rows into groups |
| 4 | HAVING | Filter groups |
| 5 | SELECT | Compute output columns and aliases |
| 6 | ORDER BY | Sort the result |
| 7 | LIMIT | Cap the row count |
Because SELECT runs at step 5 but WHERE runs at step 2, the alias annual_pay does not exist yet when WHERE is evaluated. The fix is to repeat the expression or use a subquery:
SELECT emp_name, salary * 12 AS annual_pay
FROM employees
WHERE salary * 12 > 600000;
Interview note: "What is the order of execution of a SQL query?" is one of the most reliable interview questions. Memorize the FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT sequence and be ready to explain why a SELECT alias is not visible in WHERE.
Column aliases and computed columns
You rarely display raw columns exactly as stored. SELECT lets you rename output columns with AS and compute new ones from expressions:
SELECT emp_name AS name,
salary AS monthly_pay,
salary * 12 AS annual_pay,
salary * 0.10 AS pf_contribution
FROM employees;
The AS keyword renames a column purely in the output — it does not change the table. Aliases matter for readability and for the front-end code that consumes your result, which reads columns by name. You can drop AS (salary * 12 annual_pay) but keeping it makes intent obvious.
String and date functions work the same way, letting you shape data at read time rather than in application code:
SELECT UPPER(emp_name) AS name_caps,
LENGTH(emp_name) AS name_length,
CONCAT(emp_name, ' - ', city) AS label
FROM employees;
Doing this transformation in SQL keeps the logic close to the data and reduces the work your Java or JavaScript layer has to do.
Handling NULL safely
NULL means "no value recorded," and it behaves unlike any normal value. Any arithmetic with NULL yields NULL, and any comparison yields "unknown," so rows silently drop out of results. The COALESCE function gives you a fallback:
-- show 'Not set' instead of a blank for missing cities
SELECT emp_name,
COALESCE(city, 'Not set') AS city
FROM employees;
COALESCE returns its first non-null argument, which is the standard way to substitute a default. Combined with IS NULL filtering, it covers almost every missing-data situation you will hit. NULL handling is a favorite interview trap precisely because it breaks the "everything is comparable" intuition beginners bring from other languages.
Putting it together
Real queries stack these clauses. Here is a request in plain English — "the two highest-paid employees based in Hyderabad, showing name and salary" — translated directly:
SELECT emp_name, salary
FROM employees
WHERE city = 'Hyderabad'
ORDER BY salary DESC
LIMIT 2;
FROM picks the table, WHERE keeps only Hyderabad rows, ORDER BY ranks them, and LIMIT keeps the top two. Once you can read a query and mentally trace it through the execution order, you are reading SQL the way professionals do.
Where to go next
A single-table SELECT is powerful, but real databases spread data across many tables. Your employees table, for example, only stores a department_id — to show the department name, you must combine it with the departments table.
That is the next skill: learn how to connect tables in SQL joins explained. After that, GROUP BY in SQL teaches you to summarize data, and the SQL interview questions for freshers let you test everything on the employees schema you now know well.
Frequently Asked Questions
What is the difference between SELECT and SELECT *?
In what order do SQL clauses execute?
How do I remove duplicate rows in a SELECT?
What is the difference between WHERE and HAVING?
How do I limit the number of rows returned?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

