A Complete SQL Guide: From Basics to Mastery

SQL, or Structured Query Language, is the standard language for interacting with databases. Whether you are a data analyst, a backend developer, or an Android engineer, SQL is a core skill. This article introduces the essential capabilities of SQL, from basic queries to advanced operations, and helps you build a complete SQL knowledge system.
1. Basic SQL Queries
1.1 Basic Query Structure
The foundation of SQL is the SELECT statement. It consists of several key parts:
SELECT column1, column2, ... -- Choose the columns to query
FROM table_name -- Specify the data source
WHERE condition -- Filter rows
GROUP BY grouping_column -- Define grouping keys
HAVING grouped_condition -- Filter grouped results
ORDER BY sort_column -- Sort the result set
LIMIT count; -- Limit the number of returned rows
1.2 Common Query Examples
Query all columns:
SELECT * FROM employees;
Query specific columns and set aliases:
SELECT
first_name AS "First Name",
last_name AS "Last Name"
FROM employees;
Query distinct values:
SELECT DISTINCT department_id FROM employees;
Limit the number of returned rows:
-- MySQL/PostgreSQL
SELECT * FROM products LIMIT 10;
-- SQL Server
SELECT TOP 10 * FROM products;
-- Oracle
SELECT * FROM employees WHERE ROWNUM <= 10;
2. Filtering and Sorting Data
2.1 Conditional Filtering
The WHERE clause supports many conditional operators:
Comparison operators:
SELECT * FROM employees WHERE salary > 50000;
Logical operators:
SELECT * FROM employees
WHERE salary > 50000 AND department_id = 10;
Range queries:
SELECT * FROM employees
WHERE salary BETWEEN 40000 AND 60000;
Pattern matching:
-- Starts with S
SELECT * FROM employees WHERE last_name LIKE 'S%';
-- Contains son
SELECT * FROM employees WHERE last_name LIKE '%son%';
NULL checks:
SELECT * FROM employees WHERE manager_id IS NULL;
2.2 Sorting Results
Sort by one column:
SELECT * FROM employees ORDER BY last_name;
Sort by multiple columns:
SELECT * FROM employees
ORDER BY department_id ASC, salary DESC;
Custom sorting:
SELECT * FROM employees
ORDER BY
CASE WHEN department_id = 10 THEN 0 ELSE 1 END,
last_name;
2.3 Pagination Queries
MySQL/PostgreSQL:
SELECT * FROM employees
ORDER BY employee_id
LIMIT 5 OFFSET 10; -- Skip 10 rows and fetch 5 rows
SQL Server:
SELECT * FROM employees
ORDER BY employee_id
OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY;
Oracle:
SELECT * FROM (
SELECT e.*, ROWNUM rn
FROM employees e
WHERE ROWNUM <= 15
) WHERE rn > 10;
3. Aggregation and Grouped Statistics
3.1 Aggregate Functions
Common aggregate functions:
SELECT
COUNT(*) AS total_employees,
AVG(salary) AS average_salary,
MAX(salary) AS highest_salary,
MIN(salary) AS lowest_salary,
SUM(salary) AS total_salary
FROM employees;
Count non-NULL values:
SELECT COUNT(manager_id) FROM employees;
Count distinct values:
SELECT COUNT(DISTINCT department_id) FROM employees;
3.2 Grouping Data
Basic grouping:
SELECT
department_id,
COUNT(*) AS employee_count,
AVG(salary) AS average_salary
FROM employees
GROUP BY department_id;
Filter after grouping:
SELECT
department_id,
AVG(salary) AS average_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 50000;
Multi-level grouped statistics:
SELECT
department_id,
job_id,
COUNT(*) AS employee_count
FROM employees
GROUP BY department_id, job_id;
3.3 Advanced Grouping
ROLLUP subtotals:
SELECT
department_id,
job_id,
COUNT(*)
FROM employees
GROUP BY ROLLUP(department_id, job_id);
CUBE multidimensional analysis:
SELECT
department_id,
job_id,
COUNT(*)
FROM employees
GROUP BY CUBE(department_id, job_id);
Calculate percentages:
SELECT
department_id,
COUNT(*) as count,
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM employees) AS percentage
FROM employees
GROUP BY department_id;
4. Multi-Table Join Queries
4.1 Join Types
Inner join:
SELECT e.last_name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;
Left join:
SELECT e.last_name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;
Right join:
SELECT e.last_name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;
Full outer join:
SELECT e.last_name, d.department_name
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.department_id;
4.2 Special Joins
Self join to query employees and their managers:
SELECT
e1.last_name AS employee,
e2.last_name AS manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.employee_id;
Multi-table join:
SELECT e.last_name, d.department_name, l.city
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN locations l ON d.location_id = l.location_id;
Non-equi join:
SELECT e.last_name, g.grade_level
FROM employees e
JOIN grade_levels g ON e.salary BETWEEN g.low_salary AND g.high_salary;
5. Using Subqueries
5.1 WHERE Subqueries
Single-value subquery:
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
IN subquery:
SELECT * FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location_id = 1700);
EXISTS subquery:
SELECT * FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e WHERE e.department_id = d.department_id);
5.2 FROM Subqueries
SELECT dept_avg.department_id, dept_avg.avg_salary
FROM (
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
) dept_avg
WHERE dept_avg.avg_salary > 50000;
5.3 Correlated Subqueries
SELECT e.last_name, e.salary, e.department_id
FROM employees e
WHERE e.salary > (
SELECT AVG(salary)
FROM employees
WHERE department_id = e.department_id
);
6. Data Manipulation Language (DML)
6.1 Insert Data
Insert one row:
INSERT INTO employees (
employee_id, first_name, last_name,
email, hire_date, job_id
) VALUES (
1001, 'John', 'Doe',
'jdoe@example.com', '2023-01-15', 'IT_PROG'
);
Batch insert:
INSERT INTO employees VALUES
(1001, 'John', 'Doe', 'jdoe@example.com', '2023-01-15', 'IT_PROG'),
(1002, 'Jane', 'Smith', 'jsmith@example.com', '2023-02-20', 'SA_REP');
Insert query results:
INSERT INTO employee_archive
SELECT * FROM employees WHERE hire_date < '2020-01-01';
6.2 Update Data
Simple update:
UPDATE employees
SET salary = salary * 1.05
WHERE department_id = 10;
Correlated update:
UPDATE employees e
SET e.salary = e.salary * 1.10
WHERE EXISTS (
SELECT 1
FROM departments d
WHERE d.department_id = e.department_id
AND d.location_id = 1700
);
6.3 Delete Data
DELETE FROM employees WHERE employee_id = 100;
6.4 Merge Data (MERGE/UPSERT)
SQL Server/Oracle:
MERGE INTO employees_target t
USING employees_source s ON (t.employee_id = s.employee_id)
WHEN MATCHED THEN
UPDATE SET t.salary = s.salary, t.department_id = s.department_id
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, last_name, salary, department_id)
VALUES (s.employee_id, s.first_name, s.last_name, s.salary, s.department_id);
PostgreSQL:
INSERT INTO employees (employee_id, first_name, last_name, email)
VALUES (100, 'John', 'Doe', 'jdoe@example.com')
ON CONFLICT (employee_id)
DO UPDATE SET
first_name = EXCLUDED.first_name,
last_name = EXCLUDED.last_name;
7. Table Structure and Indexes
7.1 Table Operations
Create a table:
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
hire_date DATE DEFAULT CURRENT_DATE,
salary DECIMAL(10,2) CHECK (salary > 0),
department_id INT REFERENCES departments(department_id)
);
Modify table structure:
ALTER TABLE employees ADD COLUMN phone_number VARCHAR(20);
ALTER TABLE employees ALTER COLUMN phone_number TYPE VARCHAR(30);
ALTER TABLE employees DROP COLUMN phone_number;
7.2 Index Management
Create indexes:
CREATE INDEX idx_emp_last_name ON employees(last_name);
CREATE UNIQUE INDEX idx_emp_email ON employees(email);
CREATE INDEX idx_emp_name_dept ON employees(last_name, department_id);
Function-based index:
CREATE INDEX idx_emp_upper_name ON employees(UPPER(last_name));
Drop an index:
DROP INDEX idx_emp_last_name;
8. Advanced Features
8.1 Views
Create a view:
CREATE VIEW emp_dept_view AS
SELECT e.employee_id, e.last_name, d.department_name
FROM employees e JOIN departments d ON e.department_id = d.department_id;
Materialized view:
CREATE MATERIALIZED VIEW emp_salary_mv AS
SELECT department_id, AVG(salary) AS avg_salary
FROM employees GROUP BY department_id;
8.2 Execution Plan Analysis
EXPLAIN SELECT * FROM employees WHERE last_name = 'Smith';
9. SQL Best Practices
- Write readable SQL: use indentation, line breaks, and comments sensibly.
- Avoid
SELECT *: query only the columns you need. - Use indexes thoughtfully: create indexes for common query predicates.
- Handle
NULLcarefully: comparisons betweenNULLand any value evaluate toNULL. - Prefer batch operations over loops: use batch
INSERTandUPDATEwhenever possible. - Use transactions: wrap related operations in transactions to preserve consistency.
- Optimize regularly: analyze slow queries and improve execution plans.
Conclusion
SQL is a powerful and flexible language. This article covered most common features, from basic queries to advanced operations. Once you master these foundations, you can continue into advanced topics such as window functions, CTEs, and stored procedures. Good SQL is not only syntactically correct; it also accounts for performance and maintainability. Practice is the best teacher, so keep applying these techniques in real projects.