Skip to main content

Course Progress

Loading...

Mastering SELECT Queries

Duration: 100 minutes
Module 3: Session 4

Learning Objectives

  • Master basic SELECT statement structure
  • Use WHERE clauses for filtering data
  • Implement sorting with ORDER BY
  • Limit and paginate results
  • Select distinct values and handle NULL
  • Use operators and wildcards effectively
  • Apply column aliases and expressions
  • Understand query execution order

SELECT Statement Anatomy

The SELECT statement is the most used SQL command. It retrieves data from one or more tables. Let's master it! 🔍

💡
Query Execution Order
MySQL executes clauses in this order (different from writing order):
  1. FROM- Identify tables
  2. WHERE- Filter rows
  3. GROUP BY- Group rows
  4. HAVING- Filter groups
  5. SELECT- Select columns
  6. ORDER BY- Sort results
  7. LIMIT- Limit output

Basic SELECT Statements

graph TB A[SELECT] --> B[All Columns *] A --> C[Specific Columns] A --> D[Expressions] A --> E[Functions] A --> F[DISTINCT] A --> G[Aliases] style A fill:#dcfce7 style B fill:#dbeafe style C fill:#dbeafe style D fill:#fef3c7 style E fill:#fee2e2 style F fill:#e9d5ff style G fill:#e0f2fe

Basic SELECT Examples

-- ========================================
-- SELECTING COLUMNS
-- ========================================

-- Select all columns (avoid in production)
SELECT * FROM users;

-- Select specific columns (recommended)
SELECT username, email, created_at 
FROM users;

-- Select with table prefix (useful for joins)
SELECT 
    users.username,
    users.email,
    users.created_at
FROM users;

-- Using table alias
SELECT 
    u.username,
    u.email,
    u.created_at
FROM users u;

-- ========================================
-- COLUMN ALIASES
-- ========================================

-- Simple alias
SELECT 
    username AS user_name,
    email AS email_address
FROM users;

-- Alias without AS (valid but less clear)
SELECT 
    username user_name,
    email email_address
FROM users;

-- Alias with spaces (use backticks)
SELECT 
    username AS `User Name`,
    email AS `Email Address`,
    created_at AS `Registration Date`
FROM users;

-- ========================================
-- EXPRESSIONS AND CALCULATIONS
-- ========================================

-- Arithmetic expressions
SELECT 
    product_name,
    price,
    price * 1.1 AS price_with_tax,
    price * quantity AS total_value
FROM products;

-- String concatenation
SELECT 
    CONCAT(first_name, ' ', last_name) AS full_name,
    CONCAT(city, ', ', country) AS location,
    UPPER(username) AS username_upper,
    LENGTH(bio) AS bio_length
FROM users;

-- Date calculations
SELECT 
    username,
    created_at,
    DATEDIFF(NOW(), created_at) AS days_since_joined,
    DATE_ADD(created_at, INTERVAL 1 YEAR) AS anniversary_date,
    YEAR(created_at) AS join_year
FROM users;

-- Conditional expressions
SELECT 
    product_name,
    price,
    CASE 
        WHEN price < 50 THEN 'Budget'
        WHEN price < 200 THEN 'Mid-range'
        ELSE 'Premium'
    END AS price_category,
    IF(stock_quantity > 0, 'In Stock', 'Out of Stock') AS availability
FROM products;

-- ========================================
-- DISTINCT VALUES
-- ========================================

-- Get unique values
SELECT DISTINCT status FROM orders;

-- Distinct combinations
SELECT DISTINCT country, city FROM customers;

-- Count distinct values
SELECT 
    COUNT(DISTINCT customer_id) AS unique_customers,
    COUNT(DISTINCT product_id) AS unique_products,
    COUNT(*) AS total_orders
FROM orders;

-- ========================================
-- LIMITING RESULTS
-- ========================================

-- Get first 10 rows
SELECT * FROM products LIMIT 10;

-- Get rows 11-20 (pagination)
SELECT * FROM products LIMIT 10 OFFSET 10;
-- Or alternative syntax
SELECT * FROM products LIMIT 10, 10;

-- Get top 5 expensive products
SELECT product_name, price 
FROM products 
ORDER BY price DESC 
LIMIT 5;

-- ========================================
-- SELECTING FROM MULTIPLE DATABASES
-- ========================================

-- Fully qualified table name
SELECT * FROM database_name.table_name;

-- Cross-database query
SELECT 
    db1.users.username,
    db2.profiles.bio
FROM 
    database1.users db1,
    database2.profiles db2
WHERE db1.user_id = db2.user_id;

-- ========================================
-- LITERAL VALUES AND CONSTANTS
-- ========================================

-- Select literal values
SELECT 
    'Hello World' AS greeting,
    42 AS answer,
    NOW() AS current_time,
    PI() AS pi_value;

-- Combine literals with data
SELECT 
    username,
    'Active' AS status,
    100 AS default_score,
    NOW() AS query_time
FROM users;

-- Using DUAL (dummy table)
SELECT 'Test' FROM DUAL;
SELECT 1 + 1 FROM DUAL;
SELECT NOW() FROM DUAL;

WHERE Clause - Filtering Data

WHERE Clause Examples

-- ========================================
-- COMPARISON OPERATORS
-- ========================================

-- Basic equality
SELECT * FROM users WHERE status = 'active';

-- Not equal (two syntaxes)
SELECT * FROM products WHERE category != 'Electronics';
SELECT * FROM products WHERE category <> 'Electronics';

-- Numeric comparisons
SELECT * FROM products WHERE price > 100;
SELECT * FROM orders WHERE total_amount <= 500;
SELECT * FROM users WHERE age >= 18;

-- Date comparisons
SELECT * FROM orders WHERE order_date > '2024-01-01';
SELECT * FROM events WHERE event_date <= NOW();

-- ========================================
-- LOGICAL OPERATORS
-- ========================================

-- AND operator (all conditions must be true)
SELECT * FROM products 
WHERE price > 50 
  AND stock_quantity > 0 
  AND status = 'active';

-- OR operator (any condition can be true)
SELECT * FROM users 
WHERE role = 'admin' 
   OR role = 'moderator';

-- Combining AND and OR (use parentheses!)
SELECT * FROM products 
WHERE (category = 'Electronics' OR category = 'Computers')
  AND price < 1000
  AND stock_quantity > 0;

-- NOT operator
SELECT * FROM users WHERE NOT status = 'banned';
SELECT * FROM products WHERE NOT (price > 100 AND category = 'Luxury');

-- ========================================
-- IN and NOT IN
-- ========================================

-- IN operator (match any value in list)
SELECT * FROM orders 
WHERE status IN ('pending', 'processing', 'shipped');

-- NOT IN
SELECT * FROM products 
WHERE category NOT IN ('Discontinued', 'Test');

-- IN with subquery
SELECT * FROM users 
WHERE user_id IN (
    SELECT DISTINCT user_id 
    FROM orders 
    WHERE total_amount > 1000
);

-- ========================================
-- BETWEEN
-- ========================================

-- Numeric range
SELECT * FROM products 
WHERE price BETWEEN 50 AND 200;  -- Inclusive

-- Date range
SELECT * FROM orders 
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';

-- NOT BETWEEN
SELECT * FROM users 
WHERE age NOT BETWEEN 13 AND 17;

-- ========================================
-- NULL HANDLING
-- ========================================

-- Check for NULL
SELECT * FROM users WHERE phone IS NULL;
SELECT * FROM products WHERE description IS NOT NULL;

-- NEVER use = NULL (wrong!)
-- SELECT * FROM users WHERE phone = NULL;  -- This won't work!

-- Coalesce NULL values
SELECT * FROM products 
WHERE COALESCE(discount, 0) > 10;

-- ========================================
-- LIKE and Pattern Matching
-- ========================================

-- Starts with
SELECT * FROM users WHERE username LIKE 'john%';

-- Ends with
SELECT * FROM users WHERE email LIKE '%@gmail.com';

-- Contains
SELECT * FROM products WHERE name LIKE '%laptop%';

-- Specific pattern (_ for single character)
SELECT * FROM codes WHERE code LIKE 'ABC___';  -- ABC followed by 3 chars

-- Case-insensitive by default
SELECT * FROM users WHERE name LIKE 'john%';  -- Matches 'John', 'JOHN', etc.

-- Escape special characters
SELECT * FROM products WHERE name LIKE '%\_%' ESCAPE '\';  -- Contains underscore

-- NOT LIKE
SELECT * FROM users WHERE email NOT LIKE '%@test.com';

-- ========================================
-- REGULAR EXPRESSIONS
-- ========================================

-- Basic regex
SELECT * FROM users WHERE username REGEXP '^[a-z]+$';  -- Lowercase only

-- Email validation
SELECT * FROM users 
WHERE email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';

-- Phone number pattern
SELECT * FROM contacts 
WHERE phone REGEXP '^[0-9]{3}-[0-9]{3}-[0-9]{4}$';

-- Word boundaries
SELECT * FROM posts 
WHERE content REGEXP '[[:<:]]MySQL[[:>:]]';  -- Whole word 'MySQL'

-- Case-sensitive regex
SELECT * FROM users 
WHERE BINARY username REGEXP '^[A-Z]';  -- Must start with uppercase

-- ========================================
-- COMPLEX CONDITIONS
-- ========================================

-- Multiple conditions with grouping
SELECT * FROM products 
WHERE 
    (category = 'Electronics' AND price < 500)
    OR (category = 'Books' AND price < 50)
    OR (is_featured = TRUE);

-- Date and status combination
SELECT * FROM orders 
WHERE 
    status IN ('pending', 'processing')
    AND order_date >= DATE_SUB(NOW(), INTERVAL 7 DAY)
    AND total_amount > 100;

-- Complex NULL handling
SELECT * FROM users 
WHERE 
    (email IS NOT NULL AND email != '')
    AND (phone IS NULL OR phone = '')
    AND status = 'active';

-- Using CASE in WHERE
SELECT * FROM products 
WHERE price > 
    CASE category
        WHEN 'Luxury' THEN 1000
        WHEN 'Standard' THEN 100
        ELSE 10
    END;

ORDER BY - Sorting Results

Sorting Data with ORDER BY

-- ========================================
-- BASIC SORTING
-- ========================================

-- Sort ascending (default)
SELECT * FROM users ORDER BY username;
SELECT * FROM users ORDER BY username ASC;

-- Sort descending
SELECT * FROM products ORDER BY price DESC;

-- Multiple columns
SELECT * FROM users 
ORDER BY last_name, first_name;

-- Different directions for each column
SELECT * FROM products 
ORDER BY category ASC, price DESC;

-- ========================================
-- SORTING BY EXPRESSIONS
-- ========================================

-- Sort by calculated field
SELECT 
    product_name,
    price,
    discount,
    price - discount AS final_price
FROM products 
ORDER BY final_price DESC;

-- Sort by function result
SELECT * FROM users 
ORDER BY LENGTH(username) DESC;

-- Sort by CASE expression
SELECT * FROM orders 
ORDER BY 
    CASE status
        WHEN 'urgent' THEN 1
        WHEN 'high' THEN 2
        WHEN 'medium' THEN 3
        WHEN 'low' THEN 4
    END;

-- ========================================
-- SORTING WITH NULL VALUES
-- ========================================

-- NULLs first (MySQL default for ASC)
SELECT * FROM users 
ORDER BY last_login ASC;

-- NULLs last
SELECT * FROM users 
ORDER BY last_login IS NULL, last_login DESC;

-- Custom NULL handling
SELECT * FROM products 
ORDER BY 
    CASE 
        WHEN discount IS NULL THEN 1 
        ELSE 0 
    END,
    discount DESC;

-- ========================================
-- FIELD() FUNCTION FOR CUSTOM ORDER
-- ========================================

-- Specific order for values
SELECT * FROM orders 
ORDER BY FIELD(status, 'pending', 'processing', 'shipped', 'delivered');

-- Custom priority ordering
SELECT * FROM tickets 
ORDER BY 
    FIELD(priority, 'critical', 'high', 'medium', 'low'),
    created_at ASC;

-- ========================================
-- RANDOM ORDERING
-- ========================================

-- Random order (be careful with large tables!)
SELECT * FROM products 
ORDER BY RAND() 
LIMIT 10;

-- Better random selection for large tables
SELECT * FROM products 
WHERE RAND() < 0.01  -- ~1% sample
ORDER BY RAND() 
LIMIT 10;

-- ========================================
-- COMBINING WITH LIMIT
-- ========================================

-- Top 10 highest prices
SELECT product_name, price 
FROM products 
ORDER BY price DESC 
LIMIT 10;

-- Latest 5 orders
SELECT * FROM orders 
ORDER BY order_date DESC 
LIMIT 5;

-- Pagination with ORDER BY
SELECT * FROM users 
ORDER BY user_id 
LIMIT 20 OFFSET 40;  -- Page 3, 20 per page

Advanced SELECT Techniques

🚀
Pro Tips
  • Use column names instead of * for better performance
  • Add indexes on columns used in WHERE and ORDER BY
  • Use LIMIT to test queries before running on large datasets
  • Check query execution plan with EXPLAIN
-- ========================================
-- WORKING WITH DATES
-- ========================================

-- Current date/time
SELECT 
    NOW() AS current_datetime,
    CURDATE() AS current_date,
    CURTIME() AS current_time;

-- Date filtering
SELECT * FROM orders 
WHERE DATE(order_datetime) = CURDATE();  -- Today's orders

SELECT * FROM orders 
WHERE YEAR(order_date) = 2024 
  AND MONTH(order_date) = 12;  -- December 2024

-- Date ranges
SELECT * FROM users 
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY);  -- Last 30 days

SELECT * FROM events 
WHERE event_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 1 WEEK) 
    AND DATE_ADD(NOW(), INTERVAL 1 WEEK);  -- ±1 week

-- ========================================
-- STRING MANIPULATION
-- ========================================

SELECT 
    UPPER(name) AS name_upper,
    LOWER(email) AS email_lower,
    CONCAT(first_name, ' ', last_name) AS full_name,
    SUBSTRING(description, 1, 100) AS short_desc,
    REPLACE(phone, '-', '') AS phone_clean,
    TRIM(address) AS address_trimmed,
    LENGTH(bio) AS bio_length,
    REVERSE(username) AS username_reversed
FROM users;

-- ========================================
-- CONDITIONAL LOGIC
-- ========================================

-- IF statement
SELECT 
    product_name,
    IF(stock > 0, 'Available', 'Out of Stock') AS status
FROM products;

-- CASE statement
SELECT 
    order_id,
    total_amount,
    CASE 
        WHEN total_amount < 100 THEN 'Small'
        WHEN total_amount < 500 THEN 'Medium'
        WHEN total_amount < 1000 THEN 'Large'
        ELSE 'Extra Large'
    END AS order_size
FROM orders;

-- IFNULL and COALESCE
SELECT 
    username,
    IFNULL(phone, 'No phone') AS phone,
    COALESCE(email, phone, 'No contact') AS primary_contact
FROM users;

-- ========================================
-- QUERY OPTIMIZATION
-- ========================================

-- Use EXPLAIN to analyze query
EXPLAIN SELECT * FROM orders WHERE customer_id = 123;

-- Use index hints
SELECT * FROM users USE INDEX (idx_email)
WHERE email = 'user@example.com';

-- Avoid functions on indexed columns
-- Bad:
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
-- Good:
SELECT * FROM orders 
WHERE order_date >= '2024-01-01' 
  AND order_date < '2025-01-01';

-- ========================================
-- UNION - Combining Results
-- ========================================

-- UNION (removes duplicates)
SELECT username, email FROM customers
UNION
SELECT username, email FROM vendors;

-- UNION ALL (keeps duplicates, faster)
SELECT product_name, 'In Stock' AS status FROM products WHERE stock > 0
UNION ALL
SELECT product_name, 'Out of Stock' FROM products WHERE stock = 0;

-- ========================================
-- EXISTS vs IN
-- ========================================

-- EXISTS (usually faster for large datasets)
SELECT * FROM customers c
WHERE EXISTS (
    SELECT 1 FROM orders o 
    WHERE o.customer_id = c.customer_id
);

-- IN (simpler syntax)
SELECT * FROM customers 
WHERE customer_id IN (
    SELECT DISTINCT customer_id FROM orders
);

Practice Exercise

💻
E-Commerce Query Challenge
Write SELECT queries to answer these questions:
  1. Find all active products under $50
  2. List customers who joined in the last 30 days
  3. Show top 5 best-selling products
  4. Find orders with total over $500
  5. List products with "laptop" in the name
  6. Show customers with no orders
  7. Find products low in stock (less than 10)
  8. Calculate age of each customer
💡 Click for Solutions
-- 1. Find all active products under $50
SELECT 
    product_id,
    product_name,
    price,
    stock_quantity
FROM products
WHERE status = 'active' 
  AND price < 50
ORDER BY price ASC;

-- 2. List customers who joined in the last 30 days
SELECT 
    customer_id,
    first_name,
    last_name,
    email,
    created_at
FROM customers
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY created_at DESC;

-- 3. Show top 5 best-selling products
SELECT 
    p.product_id,
    p.product_name,
    COUNT(oi.order_id) AS times_ordered,
    SUM(oi.quantity) AS total_quantity_sold,
    SUM(oi.quantity * oi.unit_price) AS total_revenue
FROM products p
INNER JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id
ORDER BY total_quantity_sold DESC
LIMIT 5;

-- 4. Find orders with total over $500
SELECT 
    order_id,
    customer_id,
    order_date,
    total_amount,
    status
FROM orders
WHERE total_amount > 500
ORDER BY total_amount DESC;

-- 5. List products with "laptop" in the name
SELECT 
    product_id,
    product_name,
    category,
    price,
    stock_quantity
FROM products
WHERE LOWER(product_name) LIKE '%laptop%'
   OR LOWER(description) LIKE '%laptop%'
ORDER BY price DESC;

-- 6. Show customers with no orders
SELECT 
    c.customer_id,
    c.first_name,
    c.last_name,
    c.email,
    c.created_at
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL
ORDER BY c.created_at DESC;

-- Alternative using NOT EXISTS
SELECT 
    customer_id,
    first_name,
    last_name,
    email
FROM customers c
WHERE NOT EXISTS (
    SELECT 1 
    FROM orders o 
    WHERE o.customer_id = c.customer_id
);

-- 7. Find products low in stock (less than 10)
SELECT 
    product_id,
    product_name,
    category,
    stock_quantity,
    CASE 
        WHEN stock_quantity = 0 THEN 'Out of Stock'
        WHEN stock_quantity < 5 THEN 'Critical'
        ELSE 'Low Stock'
    END AS stock_status
FROM products
WHERE stock_quantity < 10
  AND status = 'active'
ORDER BY stock_quantity ASC;

-- 8. Calculate age of each customer
SELECT 
    customer_id,
    first_name,
    last_name,
    birth_date,
    TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age,
    CASE 
        WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 18 THEN 'Minor'
        WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 30 THEN 'Young Adult'
        WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 50 THEN 'Adult'
        WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) < 65 THEN 'Middle Age'
        ELSE 'Senior'
    END AS age_group
FROM customers
WHERE birth_date IS NOT NULL
ORDER BY age DESC;

Additional Resources