SQL4 min read7 sections

SQL for beginners: write your first queries with SELECT, WHERE and ORDER BY

You do not need any experience. This guide starts with what a table is, then writes 10 small queries one at a time, and shows exactly what each one returns.

Solve “Filter Engineering salaries”

Free. Runs in your browser, no install.

Updated Written for: Complete beginners and students who have never written SQL.

What you will learn

  • A table is rows and columns. SELECT chooses columns, WHERE chooses rows, ORDER BY sorts, LIMIT cuts the list short.
  • Text goes in single quotes and numbers do not. Comparison uses one equals sign.
  • COUNT, SUM, AVG, MIN and MAX turn many rows into one answer.
  • SQL reads FROM first, then WHERE, then SELECT. Knowing this order explains most beginner errors.
On this page
  1. What is a table?
  2. 1. SELECT: choose which columns to see
  3. 2. WHERE: choose which rows to see
  4. 3. ORDER BY and LIMIT: sort the answer and keep the top few
  5. 4. COUNT, SUM, AVG, MIN and MAX: many rows, one answer
  6. 5. Common beginner mistakes
  7. What to learn next

SQL is the language you use to ask a database questions. "How many orders came from Pune?" "Which customers spent the most?" You write the question as a short piece of text, the database runs it, and it sends back a table of answers. Almost every data engineering job uses it every day, which is why this is the first skill to learn.

This guide uses one small table. Every query was run in DuckDB, and the output under each query is exactly what it printed, so you can check your own results against it. The same queries work in MySQL, PostgreSQL, Snowflake and BigQuery unless a note says otherwise.

What is a table?

A table is a grid. Each row is one thing (here, one order) and each column is one fact about it (the customer, the city, the amount). Create the table below and fill it with 8 orders. You do not need to understand the CREATE and INSERT lines yet. They just build the data.

SQL
CREATE TABLE orders (
  order_id   INTEGER,
  customer   VARCHAR,
  city       VARCHAR,
  order_date DATE,
  amount     INTEGER,
  status     VARCHAR
);

INSERT INTO orders VALUES
  (1, 'Asha',  'Pune',      '2026-03-01', 1200, 'paid'),
  (2, 'Ravi',  'Mumbai',    '2026-03-01',  800, 'paid'),
  (3, 'Meena', 'Chennai',   '2026-03-02',  450, 'refunded'),
  (4, 'Kiran', 'Pune',      '2026-03-02', 2500, 'paid'),
  (5, 'Asha',  'Pune',      '2026-03-03',  300, 'paid'),
  (6, 'Divya', 'Bengaluru', '2026-03-03', 1500, 'pending'),
  (7, 'Ravi',  'Mumbai',    '2026-03-04',  950, 'paid'),
  (8, 'Meena', 'Chennai',   '2026-03-05', 2200, 'paid');

SELECT: choose which columns to see

SELECT * means "show me every column". The star is a shortcut for "all of them". FROM orders says which table to read.

SQL
SELECT * FROM orders;
order_id | customer | city      | order_date | amount | status
---------+----------+-----------+------------+--------+---------
1        | Asha     | Pune      | 2026-03-01 | 1200   | paid
2        | Ravi     | Mumbai    | 2026-03-01 | 800    | paid
3        | Meena    | Chennai   | 2026-03-02 | 450    | refunded
4        | Kiran    | Pune      | 2026-03-02 | 2500   | paid
5        | Asha     | Pune      | 2026-03-03 | 300    | paid
6        | Divya    | Bengaluru | 2026-03-03 | 1500   | pending
7        | Ravi     | Mumbai    | 2026-03-04 | 950    | paid
8        | Meena    | Chennai   | 2026-03-05 | 2200   | paid
(8 rows)

8 rows and 6 columns, the whole table. Usually you want only some columns. Name them, separated by commas:

SQL
SELECT customer, amount FROM orders;
customer | amount
---------+-------
Asha     | 1200
Ravi     | 800
Meena    | 450
Kiran    | 2500
Asha     | 300
Divya    | 1500
Ravi     | 950
Meena    | 2200
(8 rows)

Now only 2 columns come back. The rows are in no promised order unless you ask for one, which is the next steps.

1FROM orders

Pick the table to read.

2WHERE city = 'Pune'

Keep only the rows that match.

3SELECT customer, amount

Choose the columns and make any aliases.

4ORDER BY amount DESC

Sort what is left.

5LIMIT 3

Keep the first 3 rows.

You write SELECT first, but the database reads it third. That is why WHERE cannot use an alias created in SELECT.

Figure 1. The order the database reads a query, which is not the order you write it. WHERE runs before SELECT, so it can only use columns that exist in the table.

WHERE: choose which rows to see

WHERE keeps only the rows that match a condition. Text goes in single quotes, numbers do not, and a comparison uses one equals sign.

SQL
SELECT order_id, customer, amount
FROM orders
WHERE city = 'Pune';
order_id | customer | amount
---------+----------+-------
1        | Asha     | 1200
4        | Kiran    | 2500
5        | Asha     | 300
(3 rows)

3 orders are from Pune. You can compare numbers with >, <, >=, <= and <> (not equal):

SQL
SELECT order_id, customer, amount
FROM orders
WHERE amount > 1000;
order_id | customer | amount
---------+----------+-------
1        | Asha     | 1200
4        | Kiran    | 2500
6        | Divya    | 1500
8        | Meena    | 2200
(4 rows)

Join conditions with AND (both must be true) and OR (at least one must be true):

SQL
SELECT order_id, customer, city, amount
FROM orders
WHERE city = 'Pune' AND amount > 1000;
order_id | customer | city | amount
---------+----------+------+-------
1        | Asha     | Pune | 1200
4        | Kiran    | Pune | 2500
(2 rows)

Only the orders that are both from Pune and above 1000 survive. IN is a tidy way to say "any of these":

SQL
SELECT order_id, customer, city
FROM orders
WHERE city IN ('Mumbai', 'Chennai');
order_id | customer | city
---------+----------+--------
2        | Ravi     | Mumbai
3        | Meena    | Chennai
7        | Ravi     | Mumbai
8        | Meena    | Chennai
(4 rows)

And LIKE matches patterns in text. A percent sign % stands for "anything":

SQL
SELECT DISTINCT customer
FROM orders
WHERE customer LIKE 'M%';
customer
--------
Meena
(1 row)

Names starting with M. DISTINCT removed the repeat, because Meena has 2 orders and you asked for each name once.

ORDER BY and LIMIT: sort the answer and keep the top few

Without ORDER BY, the order of rows is not promised. Ask for what you want:

SQL
SELECT customer, amount
FROM orders
ORDER BY amount DESC;
customer | amount
---------+-------
Kiran    | 2500
Meena    | 2200
Divya    | 1500
Asha     | 1200
Ravi     | 950
Ravi     | 800
Meena    | 450
Asha     | 300
(8 rows)

DESC means largest first. The default is ASC, smallest first. To keep only the top three, add LIMIT:

SQL
SELECT customer, amount
FROM orders
ORDER BY amount DESC
LIMIT 3;
customer | amount
---------+-------
Kiran    | 2500
Meena    | 2200
Divya    | 1500
(3 rows)

"The three biggest orders" is one of the most common questions people ask a database. You can sort by 2 columns too, and the second one breaks ties in the first:

SQL
SELECT city, customer, amount
FROM orders
ORDER BY city, amount DESC;
city      | customer | amount
----------+----------+-------
Bengaluru | Divya    | 1500
Chennai   | Meena    | 2200
Chennai   | Meena    | 450
Mumbai    | Ravi     | 950
Mumbai    | Ravi     | 800
Pune      | Kiran    | 2500
Pune      | Asha     | 1200
Pune      | Asha     | 300
(8 rows)

Cities come out in alphabetical order, and inside Pune the biggest amount comes first.

COUNT, SUM, AVG, MIN and MAX: many rows, one answer

These aggregate functions look at a group of rows and return one number.

SQL
SELECT COUNT(*)    AS orders_placed,
       SUM(amount) AS total_amount,
       AVG(amount) AS average_amount,
       MIN(amount) AS smallest,
       MAX(amount) AS largest
FROM orders;
orders_placed | total_amount | average_amount | smallest | largest
--------------+--------------+----------------+----------+--------
8             | 9900         | 1237.5         | 300      | 2500
(1 row)

AS gives a column a friendlier name, called an alias. 1 row came back, because 8 rows were folded into five numbers. Combine an aggregate with WHERE to answer a narrower question:

SQL
SELECT COUNT(*) AS paid_orders, SUM(amount) AS paid_total
FROM orders
WHERE status = 'paid';
paid_orders | paid_total
------------+-----------
6           | 7950
(1 row)

6 orders were paid. The refunded and pending orders were filtered out before anything was counted, which is exactly the order shown in Figure 1.

Common beginner mistakes

Read these once. They cause most first-week errors.

  • Using a double equals. SQL compares with =, not ==.
  • Forgetting quotes on text. WHERE city = Pune makes the database look for a column called Pune. Write 'Pune'.
  • Using a column alias in WHERE. WHERE total > 100 fails if total is an alias made in SELECT, because WHERE runs first, before the alias exists.
  • Mixing a column and an aggregate. SELECT customer, SUM(amount) fails without a GROUP BY, which the GROUP BY guide explains.
  • Trusting the row order. If the order matters, say it with ORDER BY.

What to learn next

You can now read a table and ask simple questions of it. The next steps, in order:

  1. GROUP BY and HAVING: one answer per city or per customer, instead of one answer for everything.
  2. Joins: combine 2 tables, such as orders and customers.
  3. NULL: what a missing value does to your queries.

Practise right away in the browser with Filter Engineering salaries and Names starting with A. Both are free, and they use exactly the ideas from this guide.

Keep practising

Take what you just read into the editor.

Next in the beginner pathSQL GROUP BY and HAVING explained for beginners, with real outputBrowse all 36 guides