dev.rean.me
database

SQL SELECT Statement — All Forms Explained with Example

Learn all forms of SQL SELECT statement from basic to advanced. We explain SELECT, WHERE, JOIN, GROUP BY, HAVING, Subquery, CASE, UNION, Window Function with simple example. Good for MySQL, PostgreSQL, SQL Server.

2026-08-26 · 11 min read

Share:

SQL SELECT — All Forms from Basic to Advanced

Hello friend! Today we learn all form of SELECT statement in SQL.

SELECT is the most important SQL command. You use it every day when work with database. We go from easy to hard. Learn step by step!

Note: Example below work with MySQL, PostgreSQL, and SQL Server. Small difference in syntax will have note for each database.

If you want to know how SQL read your query step by step, read this first: → SQL Operation Logic Order Explained

select statement explained sql guide by stackengineeringhub

🎯 Practice with Real Database

All example in this article use tbStudents, tbPayments, tbEnrollments table. You can practice with real database from these article:

🎓 Student Database (MySQL)

🖥️ Student Database (SQL Server)

📝 Student Exam Database

📚 Library Database

1. SELECT — Pick Column You Want

The most basic form. Tell SQL what column to show.

-- Show all column (* = everything)
SELECT * FROM tbStudents;

-- Show only specific column
SELECT student_name, email FROM tbStudents;

-- Give column a friendly name (alias)
SELECT student_name AS name, email AS contact FROM tbStudents;

2. FROM — Tell SQL Which Table

FROM tell SQL where to find the data. Always write after SELECT.

-- Get data from one table
SELECT student_id, student_name
FROM tbStudents;

3. WHERE — Filter Row You Want

WHERE filter row. Only row that match condition will show.

-- Only student that is active
SELECT * FROM tbStudents
WHERE is_active = 1;

-- Only student with age more than 18
SELECT * FROM tbStudents
WHERE age > 18;

-- Only student name equal Rean
SELECT * FROM tbStudents
WHERE student_name = 'Rean';

4. AND / OR / NOT — Combine Condition

Use AND, OR, NOT to combine many condition together.

-- AND = both condition must true
SELECT * FROM tbStudents
WHERE age > 18 AND is_active = 1;

-- OR = any one condition true is ok
SELECT * FROM tbStudents
WHERE department_id = 1 OR department_id = 2;

-- NOT = opposite of condition
SELECT * FROM tbStudents
WHERE NOT is_active = 0;

5. IN — Match List of Value

IN check if value match any in a list. Short way instead of many OR.

-- Old way (many OR)
SELECT * FROM tbStudents
WHERE department_id = 1 OR department_id = 2 OR department_id = 3;

-- Better way with IN
SELECT * FROM tbStudents
WHERE department_id IN (1, 2, 3);

-- NOT IN = exclude from list
SELECT * FROM tbStudents
WHERE department_id NOT IN (4, 5);

6. BETWEEN — Range of Value

BETWEEN check value between two number (include both end).

-- Student with age 18 to 25
SELECT * FROM tbStudents
WHERE age BETWEEN 18 AND 25;

-- Payment between two date
SELECT * FROM tbPayments
WHERE payment_date BETWEEN '2025-01-01' AND '2025-12-31';

-- NOT BETWEEN = outside range
SELECT * FROM tbStudents
WHERE age NOT BETWEEN 18 AND 25;

7. LIKE — Search Text Pattern

LIKE search text with pattern. Use % for any character, _ for one character.

-- Name start with "R"
SELECT * FROM tbStudents
WHERE student_name LIKE 'R%';

-- Name end with "n"
SELECT * FROM tbStudents
WHERE student_name LIKE '%n';

-- Name contain "ea" anywhere
SELECT * FROM tbStudents
WHERE student_name LIKE '%ea%';

-- Name is exactly 4 character
SELECT * FROM tbStudents
WHERE student_name LIKE '____';

8. IS NULL — Check Empty Value

IS NULL find row that have no value (empty). Cannot use = NULL, must use IS NULL.

-- Student that have no email yet
SELECT * FROM tbStudents
WHERE email IS NULL;

-- Student that already have email
SELECT * FROM tbStudents
WHERE email IS NOT NULL;

⚠️ Tip: Never write WHERE email = NULL — this always return empty result! Always use IS NULL.

9. ORDER BY — Sort Result

ORDER BY sort your result. ASC = A to Z (default). DESC = Z to A.

-- Sort by name A to Z
SELECT * FROM tbStudents
ORDER BY student_name ASC;

-- Sort by age Z to A (big to small)
SELECT * FROM tbStudents
ORDER BY age DESC;

-- Sort by many column (first by department, then by name)
SELECT * FROM tbStudents
ORDER BY department_id ASC, student_name ASC;

10. LIMIT / TOP — Cut Result

Cut the number of row to show. Good for top 10 or pagination.

-- MySQL / PostgreSQL: use LIMIT
SELECT * FROM tbStudents
ORDER BY student_name
LIMIT 10;

-- With OFFSET for pagination (skip first 20, show next 10)
SELECT * FROM tbStudents
ORDER BY student_name
LIMIT 10 OFFSET 20;

-- SQL Server: use TOP
SELECT TOP 10 * FROM tbStudents
ORDER BY student_name;

-- SQL Server pagination with OFFSET FETCH
SELECT * FROM tbStudents
ORDER BY student_name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

11. DISTINCT — Remove Duplicate

DISTINCT remove duplicate row. Show only unique value.

-- Show unique department (no repeat)
SELECT DISTINCT department_id FROM tbStudents;

-- Show unique combination of two column
SELECT DISTINCT department_id, is_active FROM tbStudents;

12. Aggregate Functions — Count, Sum, Average

Aggregate function calculate from many row and return one result.

-- COUNT: count how many row
SELECT COUNT(*) AS total_students FROM tbStudents;
SELECT COUNT(email) AS has_email FROM tbStudents;  -- count only not null

-- SUM: total of number
SELECT SUM(amount) AS total_payment FROM tbPayments;

-- AVG: average value
SELECT AVG(age) AS average_age FROM tbStudents;

-- MIN: smallest value
SELECT MIN(age) AS youngest FROM tbStudents;

-- MAX: biggest value
SELECT MAX(age) AS oldest FROM tbStudents;

13. GROUP BY — Group Data Together

GROUP BY group row that have same value. Always use with aggregate function.

-- Count student in each department
SELECT department_id, COUNT(*) AS total_students
FROM tbStudents
GROUP BY department_id;

-- Total payment by each student
SELECT student_id, SUM(amount) AS total_paid
FROM tbPayments
GROUP BY student_id;

-- Average age by department
SELECT department_id, AVG(age) AS avg_age
FROM tbStudents
GROUP BY department_id;

14. HAVING — Filter Group

HAVING filter group after GROUP BY. Cannot use WHERE to filter group, use HAVING instead.

-- Department that have more than 10 student
SELECT department_id, COUNT(*) AS total_students
FROM tbStudents
GROUP BY department_id
HAVING COUNT(*) > 10;

-- Student that pay more than 1000 total
SELECT student_id, SUM(amount) AS total_paid
FROM tbPayments
GROUP BY student_id
HAVING SUM(amount) > 1000;

Remember: WHERE filter row → HAVING filter group.

15. JOIN — Combine Two Table

JOIN combine data from two or more table together. Most common is INNER JOIN.

-- INNER JOIN: show only row that match in both table
SELECT s.student_name, d.department_name
FROM tbStudents s
INNER JOIN tbDepartments d ON s.department_id = d.department_id;

-- LEFT JOIN: show all row from left table, even no match in right
SELECT s.student_name, d.department_name
FROM tbStudents s
LEFT JOIN tbDepartments d ON s.department_id = d.department_id;

-- RIGHT JOIN: show all row from right table, even no match in left
SELECT s.student_name, d.department_name
FROM tbStudents s
RIGHT JOIN tbDepartments d ON s.department_id = d.department_id;

16. Multiple JOIN — Join More than Two Table

You can join many table in one query.

-- Join 3 table: student + enrollment + course
SELECT s.student_name, c.course_name, e.enrolled_date
FROM tbStudents s
INNER JOIN tbEnrollments e ON s.student_id = e.student_id
INNER JOIN tbCourses c ON e.course_id = c.course_id
WHERE e.is_active = 1;

-- Join 4 table: add department
SELECT s.student_name, d.department_name, c.course_name, e.enrolled_date
FROM tbStudents s
INNER JOIN tbDepartments d ON s.department_id = d.department_id
INNER JOIN tbEnrollments e ON s.student_id = e.student_id
INNER JOIN tbCourses c ON e.course_id = c.course_id;

17. Subquery — Query Inside Query

Subquery is SELECT inside another SELECT. Put it inside ().

-- Find student that enroll in course_id = 1 (subquery in WHERE)
SELECT student_name FROM tbStudents
WHERE student_id IN (
  SELECT student_id FROM tbEnrollments WHERE course_id = 1
);

-- Subquery in SELECT (calculate something per row)
SELECT student_name,
  (SELECT COUNT(*) FROM tbEnrollments e WHERE e.student_id = s.student_id) AS total_courses
FROM tbStudents s;

-- Subquery in FROM (make virtual table)
SELECT dept_summary.department_id, dept_summary.total
FROM (
  SELECT department_id, COUNT(*) AS total
  FROM tbStudents
  GROUP BY department_id
) AS dept_summary
WHERE dept_summary.total > 5;

18. CASE — If-Else in SQL

CASE is like if-else. Return different value base on condition.

-- Simple CASE: label age group
SELECT student_name, age,
  CASE
    WHEN age < 18 THEN 'Under 18'
    WHEN age BETWEEN 18 AND 22 THEN 'Young Adult'
    WHEN age > 22 THEN 'Adult'
    ELSE 'Unknown'
  END AS age_group
FROM tbStudents;

-- CASE in ORDER BY (custom sort)
SELECT student_name, department_id
FROM tbStudents
ORDER BY
  CASE department_id
    WHEN 1 THEN 'A'
    WHEN 2 THEN 'B'
    ELSE 'C'
  END;

19. UNION — Combine Result from Two Query

UNION combine result from two SELECT. Remove duplicate. UNION ALL keep duplicate.

-- Combine name from two table (remove duplicate)
SELECT student_name AS person_name FROM tbStudents
UNION
SELECT teacher_name AS person_name FROM tbTeachers;

-- UNION ALL: keep all row even duplicate
SELECT student_name AS person_name FROM tbStudents
UNION ALL
SELECT teacher_name AS person_name FROM tbTeachers;

Rule: Both SELECT must have same number of column and same column type.

20. Window Functions — Advance Calculation

Window function calculate across row without GROUP BY. More powerful than aggregate.

-- ROW_NUMBER: give each row a number
SELECT student_name, age,
  ROW_NUMBER() OVER (ORDER BY age DESC) AS rank_by_age
FROM tbStudents;

-- RANK: rank with gap (if tie, next number skip)
SELECT student_name, age,
  RANK() OVER (ORDER BY age DESC) AS rank
FROM tbStudents;

-- DENSE_RANK: rank without gap (if tie, no skip)
SELECT student_name, age,
  DENSE_RANK() OVER (ORDER BY age DESC) AS dense_rank
FROM tbStudents;

-- SUM OVER: running total
SELECT student_name, amount,
  SUM(amount) OVER (ORDER BY payment_date) AS running_total
FROM tbPayments;

-- PARTITION BY: rank inside each group
SELECT student_name, department_id,
  RANK() OVER (PARTITION BY department_id ORDER BY age DESC) AS rank_in_dept
FROM tbStudents;

MySQL Tip: Window functions available from MySQL 8.0+. If you use old MySQL, it not work.

SQL Server Tip: SQL Server support all window function. Very powerful!

Summary — All SELECT Form

#KeywordWhat it do
1SELECTPick column to show
2FROMChoose table
3WHEREFilter row
4AND / OR / NOTCombine condition
5INMatch list of value
6BETWEENRange of value
7LIKESearch text pattern
8IS NULLCheck empty value
9ORDER BYSort result
10LIMIT / TOPCut number of row
11DISTINCTRemove duplicate
12AggregateCOUNT, SUM, AVG, MIN, MAX
13GROUP BYGroup same value
14HAVINGFilter group
15JOINCombine two table
16Multiple JOINJoin many table
17SubqueryQuery inside query
18CASEIf-else in SQL
19UNIONCombine two query result
20Window FunctionROW_NUMBER, RANK, SUM OVER

That all friend! Practice each one, you will become SQL master. Happy coding! 🎉

📚 Read More in Database

Want learn more? Check other article in database category:

← Back to database
Share: