Master SELECT queries to retrieve exactly the data you need from your MySQL database.
SELECT Fundamentals
SQL
-- Select all columns
SELECT * FROM users;
-- Select specific columns
SELECT name, email, age FROM users;
-- Column aliases
SELECT
name AS full_name,
CONCAT(first_name, ' ', last_name) AS display_name,
price * quantity AS total
FROM orders;
-- Distinct values
SELECT DISTINCT city FROM users;
SELECT DISTINCT COUNT(DISTINCT user_id) AS unique_users FROM orders;
-- Limit and offset
SELECT * FROM users LIMIT 10; -- First 10 rows
SELECT * FROM users LIMIT 10 OFFSET 20; -- Skip 20, get next 10
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20; -- Safer with ORDER BYSorting
SQL
-- Single column
SELECT * FROM users ORDER BY name ASC; -- A-Z
SELECT * FROM users ORDER BY created_at DESC; -- Newest first
-- Multiple columns
SELECT * FROM users ORDER BY city ASC, age DESC;
-- Sorts by city, then within each city by age
-- Custom sort order
SELECT * FROM products ORDER BY FIELD(status, 'active', 'pending', 'archived');
-- Sort by expression
SELECT * FROM products ORDER BY (price * stock) DESC;Filtering with WHERE
SQL
-- Numeric conditions
SELECT * FROM products WHERE price > 50;
SELECT * FROM products WHERE price BETWEEN 10 AND 50;
SELECT * FROM products WHERE stock IN (0, 1, 5);
-- Text conditions
SELECT * FROM users WHERE name LIKE 'John%';
SELECT * FROM users WHERE email LIKE '%@gmail.com';
SELECT * FROM users WHERE name REGEXP '^[A-Z]'; -- Starts with uppercase
-- Date conditions
SELECT * FROM orders WHERE created_at >= '2024-01-01';
SELECT * FROM orders WHERE YEAR(created_at) = 2024;
SELECT * FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31';
-- NULL conditions
SELECT * FROM users WHERE phone IS NOT NULL;
SELECT * FROM users WHERE deleted_at IS NULL;Aggregate Functions
SQL
-- Count
SELECT COUNT(*) FROM users;
SELECT COUNT(DISTINCT city) FROM users;
-- Sum, Average, Min, Max
SELECT
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order,
MIN(amount) AS smallest_order,
MAX(amount) AS largest_order
FROM orders;
-- Group by
SELECT
city,
COUNT(*) AS user_count,
AVG(age) AS avg_age
FROM users
GROUP BY city
ORDER BY user_count DESC;
-- Having (filters groups, not rows)
SELECT
city,
COUNT(*) AS user_count
FROM users
GROUP BY city
HAVING COUNT(*) > 10
ORDER BY user_count DESC;
-- Complete query order
SELECT department, AVG(salary) AS avg_salary
FROM employees
WHERE hire_date > '2020-01-01'
GROUP BY department
HAVING AVG(salary) > 75000
ORDER BY avg_salary DESC
LIMIT 5;JOIN
SQL
-- INNER JOIN
SELECT u.name, o.product, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- LEFT JOIN (all users, even without orders)
SELECT u.name, COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;
-- RIGHT JOIN
SELECT o.product, u.name
FROM orders o
RIGHT JOIN users u ON o.user_id = u.id;
-- Multiple JOINs
SELECT
u.name,
o.product,
c.category_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id
JOIN categories c ON p.category_id = c.id;
-- Self JOIN
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;Subqueries
SQL
-- WHERE subquery
SELECT * FROM users
WHERE id IN (
SELECT user_id FROM orders WHERE amount > 100
);
-- FROM subquery (derived table)
SELECT avg_spending, COUNT(*) AS user_count
FROM (
SELECT user_id, AVG(amount) AS avg_spending
FROM orders
GROUP BY user_id
) AS user_averages
GROUP BY ROUND(avg_spending, -1);
-- EXISTS
SELECT u.name
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- Correlated subquery
SELECT name, age,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count
FROM users u;💡 Tip: Use JOIN instead of subqueries when possible — MySQL optimizes JOINs better. But use subqueries for readability when the query is complex.
Next: JOINs Deep Dive