Learn SQL in 30 Minutes: Everything You Need to Query a Database

SQL is the language of databases. Every company stores its data in databases, and SQL is how you get that data out. If you work with data in any capacity, whether you are an analyst, a marketer, a product manager, or a developer, SQL is one of the most valuable skills you can learn. The good news is that the core of SQL is small and learnable in a single sitting.

This guide teaches you the essential SQL commands using two sample tables that we will reference throughout. Each section includes the SQL statement, what it does, and what the result looks like. By the end, you will be able to query, filter, sort, aggregate, join, and modify data in any relational database.

Our Sample Tables

We will work with two tables throughout this guide. Imagine these exist in a company's HR database.

employees

id name department_id salary hire_date
1 Alice Johnson 1 85000 2020-03-15
2 Bob Smith 2 72000 2019-07-01
3 Carol Williams 1 92000 2018-01-20
4 David Brown 3 65000 2022-11-10
5 Eve Davis 2 78000 2021-06-01
6 Frank Miller 3 61000 2023-02-14
7 Grace Wilson 1 98000 2017-09-05
8 Henry Taylor NULL 55000 2024-01-15

departments

id name location
1 Engineering New York
2 Marketing Chicago
3 Sales Los Angeles
4 HR New York

SELECT: Getting Data Out

SELECT is the most fundamental SQL command. It retrieves data from a table.

-- Select specific columns
SELECT name, salary
FROM employees;

This returns the name and salary for every employee. The result is a table with two columns and eight rows.

-- Select all columns
SELECT *
FROM employees;

The asterisk means "all columns." This returns every column in the employees table. It is useful for quick exploration but in production code you should name the specific columns you need.

-- Select with an alias
SELECT name, salary AS annual_salary
FROM employees;

The AS keyword renames a column in the output. The actual table is unchanged. This is useful for readability, especially when you perform calculations.

WHERE: Filtering Rows

WHERE limits which rows are returned based on a condition.

-- Employees earning more than 80,000
SELECT name, salary
FROM employees
WHERE salary > 80000;

This returns Alice (85,000), Carol (92,000), and Grace (98,000). Only rows where the condition is true are included.

-- Exact match
SELECT name, hire_date
FROM employees
WHERE name = 'Bob Smith';

String values are enclosed in single quotes. This returns only Bob's row.

-- Dates
SELECT name, hire_date
FROM employees
WHERE hire_date >= '2022-01-01';

This returns David, Frank, and Henry, the employees hired in 2022 or later.

AND, OR: Combining Conditions

You can combine multiple conditions with AND and OR.

-- AND: both conditions must be true
SELECT name, salary, department_id
FROM employees
WHERE department_id = 1
  AND salary > 90000;

This returns Carol (92,000) and Grace (98,000), employees in department 1 who earn more than 90,000.

-- OR: either condition can be true
SELECT name, department_id
FROM employees
WHERE department_id = 2
   OR department_id = 3;

This returns Bob, David, Eve, and Frank, employees in either Marketing or Sales.

-- IN: shorthand for multiple OR conditions
SELECT name, department_id
FROM employees
WHERE department_id IN (2, 3);

The IN keyword is a cleaner way to check for multiple values. This produces the same result as the OR example above.

-- BETWEEN: range of values
SELECT name, salary
FROM employees
WHERE salary BETWEEN 70000 AND 90000;

BETWEEN is inclusive on both ends. This returns Bob (72,000), Alice (85,000), and Eve (78,000).

ORDER BY: Sorting Results

ORDER BY sorts your results by one or more columns.

-- Sort by salary, highest first
SELECT name, salary
FROM employees
ORDER BY salary DESC;

DESC means descending (highest to lowest). The default is ASC (ascending, lowest to highest). Grace (98,000) appears first, Henry (55,000) last.

-- Sort by multiple columns
SELECT name, department_id, salary
FROM employees
ORDER BY department_id ASC, salary DESC;

This sorts by department first, and within each department, by salary from highest to lowest. Department 1 shows Grace, Carol, then Alice.

LIMIT: Restricting the Number of Rows

LIMIT controls how many rows are returned. The syntax varies slightly across database systems, but the concept is universal.

-- Get the top 3 highest-paid employees
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;

This returns Grace (98,000), Carol (92,000), and Alice (85,000). LIMIT is almost always used with ORDER BY. Without sorting first, the rows you get are essentially arbitrary.

Note: In SQL Server, use TOP 3 instead of LIMIT 3. In Oracle, use FETCH FIRST 3 ROWS ONLY.

COUNT, SUM, AVG: Aggregate Functions

Aggregate functions perform calculations across multiple rows and return a single value.

-- Count all employees
SELECT COUNT(*) AS total_employees
FROM employees;

Returns 8. COUNT(*) counts all rows, including those with NULL values.

-- Average salary
SELECT AVG(salary) AS average_salary
FROM employees;

Returns 75,750 (the average of all eight salaries).

-- Total payroll
SELECT SUM(salary) AS total_payroll
FROM employees;

Returns 606,000 (the sum of all salaries).

-- Multiple aggregates at once
SELECT
    COUNT(*) AS total_employees,
    AVG(salary) AS avg_salary,
    MIN(salary) AS lowest_salary,
    MAX(salary) AS highest_salary
FROM employees;

This returns a single row with four values: 8, 75750, 55000, and 98000. You can use multiple aggregate functions in a single query.

GROUP BY: Aggregating by Category

GROUP BY splits your data into groups and applies aggregate functions to each group separately.

-- Average salary by department
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id;

This returns one row per department. Department 1 averages 91,667, department 2 averages 75,000, and department 3 averages 63,000.

-- Number of employees per department
SELECT department_id, COUNT(*) AS employee_count
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id;

Department 1 has 3 employees, department 2 has 2, and department 3 has 2.

The key rule: every column in your SELECT that is not inside an aggregate function must appear in the GROUP BY clause.

HAVING: Filtering Groups

HAVING is like WHERE, but it filters groups after aggregation. WHERE filters individual rows before grouping. HAVING filters the grouped results.

-- Departments with average salary above 70,000
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
HAVING AVG(salary) > 70000;

This returns departments 1 and 2 only. Department 3 is excluded because its average salary (63,000) does not meet the threshold.

The distinction matters: WHERE happens before GROUP BY, HAVING happens after. You cannot use aggregate functions in WHERE, only in HAVING.

JOINs: Combining Tables

JOINs are what make relational databases powerful. They combine rows from two or more tables based on a related column.

INNER JOIN

An INNER JOIN returns only rows that have matching values in both tables.

SELECT
    e.name AS employee_name,
    d.name AS department_name,
    e.salary
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;

This returns 7 rows. Henry is excluded because his department_id is NULL, so there is no match in the departments table. The e and d are aliases for the table names, which makes the query shorter and more readable.

LEFT JOIN

A LEFT JOIN returns all rows from the left table and matching rows from the right table. If there is no match, the right side columns are NULL.

SELECT
    e.name AS employee_name,
    d.name AS department_name,
    e.salary
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;

This returns all 8 employees. Henry's row is included with NULL for the department name. Left joins are useful when you want all records from one table regardless of whether they have a match in the other.

-- Find employees without a department
SELECT e.name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
WHERE d.id IS NULL;

This returns only Henry. This pattern, a LEFT JOIN followed by a WHERE checking for NULL, is a common way to find unmatched records.

Joining with Aggregates

JOINs and aggregates work together naturally.

-- Employee count and average salary by department name
SELECT
    d.name AS department,
    COUNT(e.id) AS employee_count,
    AVG(e.salary) AS avg_salary
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.name;

This returns all four departments. HR shows 0 employees and NULL for average salary because no employees are assigned to it. The LEFT JOIN from departments ensures that even departments with no employees appear in the results.

INSERT: Adding Data

INSERT adds new rows to a table.

-- Insert a single row
INSERT INTO employees (name, department_id, salary, hire_date)
VALUES ('Ivy Chen', 4, 70000, '2026-01-15');

-- Insert multiple rows
INSERT INTO employees (name, department_id, salary, hire_date)
VALUES
    ('Jack Brown', 1, 88000, '2025-06-01'),
    ('Kate Lee', 2, 73000, '2025-09-15');

You list the columns you are populating and provide the values in the same order. The id column is omitted because it auto-increments in most databases.

UPDATE: Modifying Data

UPDATE changes existing data in a table.

-- Give Alice a raise
UPDATE employees
SET salary = 90000
WHERE name = 'Alice Johnson';

-- Update multiple columns
UPDATE employees
SET salary = 68000, department_id = 2
WHERE name = 'David Brown';

Always include a WHERE clause with UPDATE. Without it, you will update every row in the table. This is one of the most common and most painful mistakes in SQL.

DELETE: Removing Data

DELETE removes rows from a table.

-- Delete a specific employee
DELETE FROM employees
WHERE name = 'Frank Miller';

-- Delete employees hired before 2019
DELETE FROM employees
WHERE hire_date < '2019-01-01';

Like UPDATE, always include a WHERE clause. A DELETE without WHERE removes every row in the table. There is no undo button.

CREATE TABLE: Building a Table

CREATE TABLE defines a new table with its columns and data types.

CREATE TABLE projects (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    budget DECIMAL(12, 2),
    start_date DATE,
    department_id INT,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

The most common data types are INT for integers, VARCHAR for variable-length text, DECIMAL for precise numbers (like money), DATE for dates, and BOOLEAN for true/false values. Exact syntax varies by database system, but the concepts are the same.

NULL: The Absence of Data

NULL represents missing or unknown data. It is not zero, it is not an empty string, it is the absence of a value. NULL requires special handling.

-- Find rows where a value is NULL
SELECT name
FROM employees
WHERE department_id IS NULL;

-- Find rows where a value is NOT NULL
SELECT name
FROM employees
WHERE department_id IS NOT NULL;

You cannot use = NULL or != NULL. You must use IS NULL and IS NOT NULL. This is a common source of bugs for SQL beginners.

The Query Execution Order

SQL does not execute in the order you write it. Understanding the actual execution order helps you write correct queries and debug problems.

  1. FROM and JOIN: Identify the tables and combine them
  2. WHERE: Filter individual rows
  3. GROUP BY: Group the remaining rows
  4. HAVING: Filter the groups
  5. SELECT: Choose which columns to return
  6. ORDER BY: Sort the results
  7. LIMIT: Restrict the number of rows returned

This is why you cannot use a column alias defined in SELECT inside a WHERE clause: WHERE executes before SELECT. It is also why HAVING can use aggregate functions but WHERE cannot: HAVING executes after GROUP BY.

Practical Tips

Always use WHERE with UPDATE and DELETE. Run a SELECT with your WHERE clause first to verify which rows will be affected. Then change it to UPDATE or DELETE.

Start with SELECT *. When exploring a new table, start with SELECT * FROM table_name LIMIT 10 to see what the data looks like before writing a complex query.

Use aliases. Table and column aliases make queries more readable, especially with joins. FROM employees e is easier to read than repeating employees throughout the query.

Format your SQL. Put each clause on its own line. Capitalize SQL keywords. Indent consistently. Readable SQL is easier to debug and maintain.

Learn your specific database. The SQL in this guide works across most databases (PostgreSQL, MySQL, SQLite, SQL Server), but each has its own extensions and quirks. Learn the specifics of whichever database you use at work.

SQL is a skill that rewards practice. Find a dataset, load it into a database, and start asking questions. Every question is a query waiting to be written.

Our free Python for Business Beginners textbook teaches you how to combine Python with SQL for powerful data analysis workflows.