Let’s create sample tables that we’ll use throughout this guide.
-- Create Employees Table
CREATE TABLE Employees (
EmployeeID INT PRIMARY KEY AUTO_INCREMENT,
FirstName VARCHAR(50),
LastName VARCHAR(50),
Email VARCHAR(100),
Department VARCHAR(50),
Salary DECIMAL(10, 2),
HireDate DATE,
ManagerID INT,
City VARCHAR(50),
Country VARCHAR(50)
);
-- Insert Sample Data
INSERT INTO Employees (FirstName, LastName, Email, Department, Salary, HireDate, ManagerID, City, Country) VALUES
('John', 'Smith', 'john.smith@company.com', 'IT', 75000, '2020-01-15', NULL, 'New York', 'USA'),
('Sarah', 'Johnson', 'sarah.j@company.com', 'HR', 65000, '2019-03-20', 1, 'Los Angeles', 'USA'),
('Mike', 'Davis', 'mike.d@company.com', 'IT', 80000, '2021-06-10', 1, 'New York', 'USA'),
('Emily', 'Wilson', 'emily.w@company.com', 'Sales', 70000, '2020-09-05', NULL, 'Chicago', 'USA'),
('David', 'Brown', 'david.b@company.com', 'Sales', 72000, '2018-11-12', 4, 'Chicago', 'USA'),
('Lisa', 'Anderson', 'lisa.a@company.com', 'IT', 78000, '2020-02-28', 1, 'San Francisco', 'USA'),
('James', 'Taylor', 'james.t@company.com', 'HR', 62000, '2021-08-15', 2, 'Boston', 'USA'),
('Anna', 'Martinez', 'anna.m@company.com', 'Marketing', 68000, '2019-05-22', NULL, 'Miami', 'USA'),
('Robert', 'Garcia', 'robert.g@company.com', 'IT', 85000, '2017-07-30', 1, 'Seattle', 'USA'),
('Jennifer', 'Lee', 'jennifer.l@company.com', 'Sales', 71000, '2020-10-18', 4, 'Denver', 'USA');
-- Create Products Table
CREATE TABLE Products (
ProductID INT PRIMARY KEY AUTO_INCREMENT,
ProductName VARCHAR(100),
Category VARCHAR(50),
Price DECIMAL(10, 2),
Stock INT,
SupplierID INT
);
INSERT INTO Products (ProductName, Category, Price, Stock, SupplierID) VALUES
('Laptop Pro', 'Electronics', 1200.00, 50, 1),
('Wireless Mouse', 'Electronics', 25.00, 200, 1),
('Office Desk', 'Furniture', 350.00, 30, 2),
('Ergonomic Chair', 'Furniture', 280.00, 45, 2),
('Monitor 24"', 'Electronics', 180.00, 75, 1),
('Keyboard Mechanical', 'Electronics', 120.00, 100, 1),
('Filing Cabinet', 'Furniture', 150.00, 25, 2),
('Desk Lamp', 'Furniture', 45.00, 150, 3),
('Webcam HD', 'Electronics', 80.00, 60, 1),
('Notebook Set', 'Stationery', 15.00, 300, 3);
-- Create Orders Table
CREATE TABLE Orders (
OrderID INT PRIMARY KEY AUTO_INCREMENT,
CustomerName VARCHAR(100),
ProductID INT,
Quantity INT,
OrderDate DATE,
ShippingCity VARCHAR(50),
TotalAmount DECIMAL(10, 2),
Status VARCHAR(20),
FOREIGN KEY (ProductID) REFERENCES Products(ProductID)
);
INSERT INTO Orders (CustomerName, ProductID, Quantity, OrderDate, ShippingCity, TotalAmount, Status) VALUES
('Alice Cooper', 1, 2, '2024-01-15', 'New York', 2400.00, 'Delivered'),
('Bob Martin', 2, 5, '2024-01-16', 'Los Angeles', 125.00, 'Delivered'),
('Charlie Brown', 3, 1, '2024-01-17', 'Chicago', 350.00, 'Shipped'),
('Diana Prince', 1, 1, '2024-01-18', 'Houston', 1200.00, 'Delivered'),
('Eve Adams', 5, 2, '2024-01-19', 'Phoenix', 360.00, 'Processing'),
('Frank Castle', 4, 3, '2024-01-20', 'Philadelphia', 840.00, 'Delivered'),
('Grace Lee', 2, 10, '2024-01-21', 'San Antonio', 250.00, 'Shipped'),
('Henry Ford', 6, 2, '2024-01-22', 'San Diego', 240.00, 'Delivered'),
('Iris West', 7, 1, '2024-01-23', 'Dallas', 150.00, 'Processing'),
('Jack Ryan', 8, 5, '2024-01-24', 'Austin', 225.00, 'Delivered'),
('Alice Cooper', 5, 3, '2024-01-25', 'New York', 540.00, 'Shipped'),
('Bob Martin', 9, 1, '2024-01-26', 'Los Angeles', 80.00, 'Processing');
SELECT is the most fundamental SQL command. It retrieves data from a database table.
SELECT column1, column2, ...
FROM table_name;
“Show me specific information from this table”
SELECT * FROM Employees;
What does * mean?
* = “ALL columns”Result: (All 10 rows with all columns) | EmployeeID | FirstName | LastName | Email | Department | Salary | HireDate | ManagerID | City | Country | |————|———–|———-|——-|————|——–|———-|———–|——|———| | 1 | John | Smith | john.smith@company.com | IT | 75000 | 2020-01-15 | NULL | New York | USA | | 2 | Sarah | Johnson | sarah.j@company.com | HR | 65000 | 2019-03-20 | 1 | Los Angeles | USA | | … | … | … | … | … | … | … | … | … | … |
When to use:
SELECT FirstName, LastName, Department
FROM Employees;
Result: | FirstName | LastName | Department | |———–|———-|————| | John | Smith | IT | | Sarah | Johnson | HR | | Mike | Davis | IT | | Emily | Wilson | Sales | | … | … | … |
Why select specific columns?
SELECT
FirstName AS 'First Name',
LastName AS 'Last Name',
Salary AS 'Annual Salary',
Department AS 'Dept'
FROM Employees;
Result: | First Name | Last Name | Annual Salary | Dept | |————|———–|—————|——| | John | Smith | 75000 | IT | | Sarah | Johnson | 65000 | HR | | … | … | … | … |
When to use:
SELECT
FirstName,
LastName,
Salary,
Salary * 12 AS AnnualSalary,
Salary * 0.10 AS MonthlyBonus
FROM Employees;
Result: | FirstName | LastName | Salary | AnnualSalary | MonthlyBonus | |———–|———-|——–|————–|————–| | John | Smith | 75000 | 900000 | 7500 | | Sarah | Johnson | 65000 | 780000 | 6500 | | … | … | … | … | … |
You can use:
+, -, *, /, %CONCAT(), UPPER(), ROUND(), etc.SELECT
CONCAT(FirstName, ' ', LastName) AS FullName,
CONCAT(City, ', ', Country) AS Location,
CONCAT('$', Salary) AS FormattedSalary
FROM Employees;
Result: | FullName | Location | FormattedSalary | |———-|———-|—————–| | John Smith | New York, USA | $75000 | | Sarah Johnson | Los Angeles, USA | $65000 | | … | … | … |
WHERE filters rows based on conditions. Only rows that meet the condition are returned.
SELECT columns
FROM table
WHERE condition;
“Show me data WHERE some condition is TRUE”
SELECT *
FROM Employees
WHERE Department = 'IT';
Result: Only IT department employees | EmployeeID | FirstName | LastName | Department | Salary | |————|———–|———-|————|——–| | 1 | John | Smith | IT | 75000 | | 3 | Mike | Davis | IT | 80000 | | 6 | Lisa | Anderson | IT | 78000 | | 9 | Robert | Garcia | IT | 85000 |
Important:
'IT'Salary = 75000-- Salary greater than 70000
SELECT FirstName, LastName, Salary
FROM Employees
WHERE Salary > 70000;
Result: | FirstName | LastName | Salary | |———–|———-|——–| | John | Smith | 75000 | | Mike | Davis | 80000 | | David | Brown | 72000 | | Lisa | Anderson | 78000 | | Robert | Garcia | 85000 | | Jennifer | Lee | 71000 |
-- Salary less than or equal to 70000
SELECT FirstName, LastName, Salary
FROM Employees
WHERE Salary <= 70000;
Result: | FirstName | LastName | Salary | |———–|———-|——–| | Sarah | Johnson | 65000 | | Emily | Wilson | 70000 | | James | Taylor | 62000 | | Anna | Martinez | 68000 |
-- Employees hired after 2020
SELECT FirstName, LastName, HireDate
FROM Employees
WHERE HireDate > '2020-01-01';
Result: | FirstName | LastName | HireDate | |———–|———-|———-| | Mike | Davis | 2021-06-10 | | Lisa | Anderson | 2020-02-28 | | James | Taylor | 2021-08-15 | | Jennifer | Lee | 2020-10-18 |
Date Format: 'YYYY-MM-DD' (e.g., ‘2020-01-01’)
-- All employees NOT in IT department
SELECT FirstName, LastName, Department
FROM Employees
WHERE Department != 'IT';
-- OR: WHERE Department <> 'IT'; (alternative syntax)
Result: | FirstName | LastName | Department | |———–|———-|————| | Sarah | Johnson | HR | | Emily | Wilson | Sales | | David | Brown | Sales | | James | Taylor | HR | | Anna | Martinez | Marketing | | Jennifer | Lee | Sales |
| Operator | Meaning | Example |
|---|---|---|
= |
Equal to | WHERE Salary = 75000 |
!= or <> |
Not equal to | WHERE Department != 'IT' |
> |
Greater than | WHERE Salary > 70000 |
< |
Less than | WHERE Salary < 70000 |
>= |
Greater than or equal | WHERE Salary >= 70000 |
<= |
Less than or equal | WHERE Salary <= 70000 |
SELECT FirstName, LastName, Department, Salary
FROM Employees
WHERE Department = 'IT'
AND Salary > 75000;
Logic:
Result: | FirstName | LastName | Department | Salary | |———–|———-|————|——–| | Mike | Davis | IT | 80000 | | Lisa | Anderson | IT | 78000 | | Robert | Garcia | IT | 85000 |
SELECT FirstName, LastName, Department
FROM Employees
WHERE Department = 'IT'
OR Department = 'Sales';
Logic:
Result: | FirstName | LastName | Department | |———–|———-|————| | John | Smith | IT | | Mike | Davis | IT | | Emily | Wilson | Sales | | David | Brown | Sales | | Lisa | Anderson | IT | | Robert | Garcia | IT | | Jennifer | Lee | Sales |
SELECT FirstName, LastName, Department
FROM Employees
WHERE NOT Department = 'IT';
-- Same as: WHERE Department != 'IT'
Result: All employees NOT in IT
SELECT FirstName, LastName, Department, Salary
FROM Employees
WHERE (Department = 'IT' OR Department = 'Sales')
AND Salary > 70000;
Logic:
(Department = 'IT' OR Department = 'Sales')Salary > 70000Result: | FirstName | LastName | Department | Salary | |———–|———-|————|——–| | John | Smith | IT | 75000 | | Mike | Davis | IT | 80000 | | David | Brown | Sales | 72000 | | Lisa | Anderson | IT | 78000 | | Robert | Garcia | IT | 85000 | | Jennifer | Lee | Sales | 71000 |
WHERE column BETWEEN value1 AND value2
Inclusive: Includes both value1 and value2
-- Salaries between 65000 and 75000 (inclusive)
SELECT FirstName, LastName, Salary
FROM Employees
WHERE Salary BETWEEN 65000 AND 75000;
Result: | FirstName | LastName | Salary | |———–|———-|——–| | John | Smith | 75000 | | Sarah | Johnson | 65000 | | Emily | Wilson | 70000 | | David | Brown | 72000 | | Anna | Martinez | 68000 | | Jennifer | Lee | 71000 |
Same as:
WHERE Salary >= 65000 AND Salary <= 75000
Date Range:
SELECT FirstName, LastName, HireDate
FROM Employees
WHERE HireDate BETWEEN '2019-01-01' AND '2020-12-31';
Checks if a value matches ANY value in a list. Shortcut for multiple OR conditions.
SELECT FirstName, LastName, Department
FROM Employees
WHERE Department IN ('IT', 'Sales', 'HR');
Same as:
WHERE Department = 'IT'
OR Department = 'Sales'
OR Department = 'HR'
Result: | FirstName | LastName | Department | |———–|———-|————| | John | Smith | IT | | Sarah | Johnson | HR | | Mike | Davis | IT | | Emily | Wilson | Sales | | David | Brown | Sales | | Lisa | Anderson | IT | | James | Taylor | HR | | Robert | Garcia | IT | | Jennifer | Lee | Sales |
NOT IN:
SELECT FirstName, LastName, Department
FROM Employees
WHERE Department NOT IN ('IT', 'Sales');
Result: Only HR and Marketing employees
Used for pattern matching with wildcards.
% = Zero or more characters_ = Exactly one character-- Find names starting with 'J'
SELECT FirstName, LastName
FROM Employees
WHERE FirstName LIKE 'J%';
Pattern: J% = J followed by anything
Result:
| FirstName | LastName |
|———–|———-|
| John | Smith |
| James | Taylor |
| Jennifer | Lee |
-- Find emails ending with 'company.com'
SELECT FirstName, Email
FROM Employees
WHERE Email LIKE '%@company.com';
Pattern: %@company.com = anything followed by @company.com
Result: All employees (all emails end with @company.com)
-- Find names containing 'son'
SELECT FirstName, LastName
FROM Employees
WHERE LastName LIKE '%son%';
Pattern: %son% = anything + “son” + anything
Result:
| FirstName | LastName |
|———–|———-|
| Sarah | Johnson |
| Lisa | Anderson |
-- Find names with 'a' as second letter
SELECT FirstName
FROM Employees
WHERE FirstName LIKE '_a%';
Pattern:
_ = any one charactera = the letter ‘a’% = anything afterResult: | FirstName | |———–| | Sarah | | James | | David |
-- Find first names exactly 4 characters long
SELECT FirstName
FROM Employees
WHERE FirstName LIKE '____'; -- Four underscores
Pattern: ____ = exactly 4 characters
Result: | FirstName | |———–| | John | | Mike | | Lisa | | Anna |
-- Case-insensitive (most databases)
WHERE FirstName LIKE 'john%'; -- Matches 'John', 'JOHN', 'john'
-- Case-sensitive (if needed - MySQL)
WHERE FirstName LIKE BINARY 'john%'; -- Matches only 'john'
NULL means “no value” or “unknown” - it’s NOT the same as empty string ‘’ or 0.
-- Find employees without a manager (top-level employees)
SELECT FirstName, LastName, ManagerID
FROM Employees
WHERE ManagerID IS NULL;
Result: | FirstName | LastName | ManagerID | |———–|———-|———–| | John | Smith | NULL | | Emily | Wilson | NULL | | Anna | Martinez | NULL |
Important:
WHERE ManagerID = NULL (WRONG - doesn’t work!)WHERE ManagerID IS NULL (CORRECT)-- Find employees WITH a manager
SELECT FirstName, LastName, ManagerID
FROM Employees
WHERE ManagerID IS NOT NULL;
Result: All employees who have a manager (ManagerID is not NULL)
Sorts the result set by one or more columns.
SELECT columns
FROM table
ORDER BY column1 [ASC|DESC], column2 [ASC|DESC];
-- Sort by salary (lowest to highest)
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary;
-- OR: ORDER BY Salary ASC;
Result: | FirstName | LastName | Salary | |———–|———-|——–| | James | Taylor | 62000 | | Sarah | Johnson | 65000 | | Anna | Martinez | 68000 | | Emily | Wilson | 70000 | | Jennifer | Lee | 71000 | | David | Brown | 72000 | | John | Smith | 75000 | | Lisa | Anderson | 78000 | | Mike | Davis | 80000 | | Robert | Garcia | 85000 |
-- Sort by salary (highest to lowest)
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary DESC;
Result: | FirstName | LastName | Salary | |———–|———-|——–| | Robert | Garcia | 85000 | | Mike | Davis | 80000 | | Lisa | Anderson | 78000 | | John | Smith | 75000 | | … | … | … |
-- Sort by Department, then by Salary within each department
SELECT FirstName, LastName, Department, Salary
FROM Employees
ORDER BY Department ASC, Salary DESC;
Logic:
Result: | FirstName | LastName | Department | Salary | |———–|———-|————|——–| | Sarah | Johnson | HR | 65000 | | James | Taylor | HR | 62000 | | Robert | Garcia | IT | 85000 | | Mike | Davis | IT | 80000 | | Lisa | Anderson | IT | 78000 | | John | Smith | IT | 75000 | | Anna | Martinez | Marketing | 68000 | | David | Brown | Sales | 72000 | | Jennifer | Lee | Sales | 71000 | | Emily | Wilson | Sales | 70000 |
-- Sort by the 3rd column in SELECT list (Department)
SELECT FirstName, LastName, Department, Salary
FROM Employees
ORDER BY 3, 4 DESC;
-- Same as: ORDER BY Department, Salary DESC;
Note: While this works, it’s not recommended (hard to read and maintain).
SELECT
FirstName,
LastName,
Salary,
Salary * 12 AS AnnualSalary
FROM Employees
ORDER BY AnnualSalary DESC;
Result: Sorted by annual salary (highest to lowest)
-- Alphabetical by last name
SELECT FirstName, LastName
FROM Employees
ORDER BY LastName;
Result: | FirstName | LastName | |———–|———-| | Lisa | Anderson | | David | Brown | | Mike | Davis | | Robert | Garcia | | Sarah | Johnson | | Jennifer | Lee | | Anna | Martinez | | John | Smith | | James | Taylor | | Emily | Wilson |
SELECT columns
FROM table
ORDER BY column
LIMIT number;
-- Get top 5 highest paid employees
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary DESC
LIMIT 5;
Result: | FirstName | LastName | Salary | |———–|———-|——–| | Robert | Garcia | 85000 | | Mike | Davis | 80000 | | Lisa | Anderson | 78000 | | John | Smith | 75000 | | David | Brown | 72000 |
-- Get 3 lowest paid employees
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary ASC
LIMIT 3;
Result: | FirstName | LastName | Salary | |———–|———-|——–| | James | Taylor | 62000 | | Sarah | Johnson | 65000 | | Anna | Martinez | 68000 |
SELECT columns
FROM table
ORDER BY column
LIMIT number OFFSET skip_count;
-- Page 1: First 3 employees
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary DESC
LIMIT 3 OFFSET 0;
Result (Page 1): | FirstName | LastName | Salary | |———–|———-|——–| | Robert | Garcia | 85000 | | Mike | Davis | 80000 | | Lisa | Anderson | 78000 |
-- Page 2: Next 3 employees (skip first 3)
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary DESC
LIMIT 3 OFFSET 3;
Result (Page 2): | FirstName | LastName | Salary | |———–|———-|——–| | John | Smith | 75000 | | David | Brown | 72000 | | Jennifer | Lee | 71000 |
-- Page 3: Next 3 employees (skip first 6)
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary DESC
LIMIT 3 OFFSET 6;
Pagination Formula:
Page 1: LIMIT 3 OFFSET 0 (skip 0)
Page 2: LIMIT 3 OFFSET 3 (skip 3)
Page 3: LIMIT 3 OFFSET 6 (skip 6)
Page N: LIMIT 3 OFFSET (N-1)*3
-- LIMIT offset, count
SELECT FirstName, LastName, Salary
FROM Employees
ORDER BY Salary DESC
LIMIT 3, 3; -- Skip 3, show 3
Returns only unique (different) values, removing duplicates.
SELECT DISTINCT column1, column2
FROM table;
-- Get all unique departments
SELECT DISTINCT Department
FROM Employees;
Without DISTINCT:
IT, HR, IT, Sales, Sales, IT, HR, Marketing, IT, Sales
With DISTINCT: | Department | |————| | IT | | HR | | Sales | | Marketing |
-- Get unique combinations of Department and City
SELECT DISTINCT Department, City
FROM Employees;
Result: Unique pairs of (Department, City) | Department | City | |————|——| | IT | New York | | HR | Los Angeles | | IT | San Francisco | | Sales | Chicago | | … | … |
Important: DISTINCT applies to the entire row, not individual columns!
-- How many different departments exist?
SELECT COUNT(DISTINCT Department) AS UniqueDepartments
FROM Employees;
Result: | UniqueDepartments | |——————-| | 4 |
SELECT DISTINCT Department
FROM Employees
ORDER BY Department;
Result: Unique departments sorted alphabetically | Department | |————| | HR | | IT | | Marketing | | Sales |
Functions that perform calculations on a set of values and return a single value.
COUNT() - Count rowsSUM() - Add up valuesAVG() - Calculate averageMIN() - Find minimum valueMAX() - Find maximum value-- How many employees total?
SELECT COUNT(*) AS TotalEmployees
FROM Employees;
Result: | TotalEmployees | |—————-| | 10 |
-- How many employees have a manager?
SELECT COUNT(ManagerID) AS EmployeesWithManager
FROM Employees;
Result: | EmployeesWithManager | |———————-| | 7 |
Difference:
COUNT(*) counts ALL rows (including NULLs)COUNT(column) counts only non-NULL values in that column-- How many different departments?
SELECT COUNT(DISTINCT Department) AS UniqueDepartments
FROM Employees;
Result: | UniqueDepartments | |——————-| | 4 |
-- Total salary expenditure
SELECT SUM(Salary) AS TotalSalaries
FROM Employees;
Result: | TotalSalaries | |—————| | 726000 |
Calculation: 75000 + 65000 + 80000 + 70000 + 72000 + 78000 + 62000 + 68000 + 85000 + 71000 = 726000
-- Total IT department salaries
SELECT SUM(Salary) AS ITSalaries
FROM Employees
WHERE Department = 'IT';
Result: | ITSalaries | |————| | 318000 |
-- Average salary across all employees
SELECT AVG(Salary) AS AverageSalary
FROM Employees;
Result: | AverageSalary | |—————| | 72600 |
Calculation: 726000 / 10 = 72600
SELECT ROUND(AVG(Salary), 2) AS AverageSalary
FROM Employees;
ROUND(value, decimal_places) - Rounds to specified decimal places
-- Average salary in IT department
SELECT AVG(Salary) AS ITAverageSalary
FROM Employees
WHERE Department = 'IT';
Result: | ITAverageSalary | |—————–| | 79500 |
-- Lowest salary
SELECT MIN(Salary) AS LowestSalary
FROM Employees;
Result: | LowestSalary | |————–| | 62000 |
-- Who has the lowest salary?
SELECT FirstName, LastName, Salary
FROM Employees
WHERE Salary = (SELECT MIN(Salary) FROM Employees);
Result: | FirstName | LastName | Salary | |———–|———-|——–| | James | Taylor | 62000 |
-- Highest salary
SELECT MAX(Salary) AS HighestSalary
FROM Employees;
Result: | HighestSalary | |—————| | 85000 |
SELECT
MIN(HireDate) AS FirstHireDate,
MAX(HireDate) AS LatestHireDate
FROM Employees;
Result: | FirstHireDate | LatestHireDate | |—————|—————-| | 2017-07-30 | 2021-08-15 |
SELECT
COUNT(*) AS TotalEmployees,
AVG(Salary) AS AverageSalary,
MIN(Salary) AS LowestSalary,
MAX(Salary) AS HighestSalary,
SUM(Salary) AS TotalPayroll
FROM Employees;
Result: | TotalEmployees | AverageSalary | LowestSalary | HighestSalary | TotalPayroll | |—————-|—————|————–|—————|————–| | 10 | 72600 | 62000 | 85000 | 726000 |
Groups rows that have the same values in specified columns, allowing you to perform aggregate functions on each group.
SELECT column1, AGGREGATE_FUNCTION(column2)
FROM table
GROUP BY column1;
“Split data into groups and calculate statistics for each group”
-- Count employees in each department
SELECT
Department,
COUNT(*) AS EmployeeCount
FROM Employees
GROUP BY Department;
What happens:
Result: | Department | EmployeeCount | |————|—————| | HR | 2 | | IT | 4 | | Marketing | 1 | | Sales | 3 |
-- Department statistics
SELECT
Department,
COUNT(*) AS EmployeeCount,
AVG(Salary) AS AverageSalary,
MIN(Salary) AS MinSalary,
MAX(Salary) AS MaxSalary,
SUM(Salary) AS TotalSalary
FROM Employees
GROUP BY Department;
Result: | Department | EmployeeCount | AverageSalary | MinSalary | MaxSalary | TotalSalary | |————|—————|—————|———–|———–|————-| | HR | 2 | 63500 | 62000 | 65000 | 127000 | | IT | 4 | 79500 | 75000 | 85000 | 318000 | | Marketing | 1 | 68000 | 68000 | 68000 | 68000 | | Sales | 3 | 71000 | 70000 | 72000 | 213000 |
-- Employee count by Department and City
SELECT
Department,
City,
COUNT(*) AS EmployeeCount
FROM Employees
GROUP BY Department, City
ORDER BY Department, City;
Result: | Department | City | EmployeeCount | |————|——|—————| | HR | Boston | 1 | | HR | Los Angeles | 1 | | IT | New York | 2 | | IT | San Francisco | 1 | | IT | Seattle | 1 | | Marketing | Miami | 1 | | Sales | Chicago | 2 | | Sales | Denver | 1 |
What happens:
-- Average salary by department for employees hired after 2019
SELECT
Department,
AVG(Salary) AS AverageSalary
FROM Employees
WHERE HireDate > '2019-01-01'
GROUP BY Department;
Execution Order:
Result: | Department | AverageSalary | |————|—————| | HR | 63500 | | IT | 77666.67 | | Sales | 71000 |
-- Salary ranges (group by salary bracket)
SELECT
CASE
WHEN Salary < 65000 THEN 'Low'
WHEN Salary BETWEEN 65000 AND 75000 THEN 'Medium'
ELSE 'High'
END AS SalaryBracket,
COUNT(*) AS EmployeeCount,
AVG(Salary) AS AverageSalary
FROM Employees
GROUP BY SalaryBracket;
Result: | SalaryBracket | EmployeeCount | AverageSalary | |—————|—————|—————| | Low | 1 | 62000 | | Medium | 5 | 69600 | | High | 4 | 79750 |
-- ❌ WRONG - FirstName not in GROUP BY or aggregated
SELECT FirstName, Department, AVG(Salary)
FROM Employees
GROUP BY Department;
-- ✅ CORRECT - Only grouped column and aggregate
SELECT Department, AVG(Salary)
FROM Employees
GROUP BY Department;
-- ✅ CORRECT - All non-aggregated columns in GROUP BY
SELECT Department, City, AVG(Salary)
FROM Employees
GROUP BY Department, City;
HAVING filters groups (after GROUP BY), while WHERE filters rows (before GROUP BY).
SELECT column1, AGGREGATE_FUNCTION(column2)
FROM table
WHERE condition -- Filters rows
GROUP BY column1
HAVING condition; -- Filters groups
-- Departments with more than 2 employees
SELECT
Department,
COUNT(*) AS EmployeeCount
FROM Employees
GROUP BY Department
HAVING COUNT(*) > 2;
What happens:
Result: | Department | EmployeeCount | |————|—————| | IT | 4 | | Sales | 3 |
Without HAVING: Would show all 4 departments (HR: 2, IT: 4, Marketing: 1, Sales: 3)
-- Departments with average salary > 70000
SELECT
Department,
AVG(Salary) AS AverageSalary,
COUNT(*) AS EmployeeCount
FROM Employees
GROUP BY Department
HAVING AVG(Salary) > 70000;
Result: | Department | AverageSalary | EmployeeCount | |————|—————|—————| | IT | 79500 | 4 | | Sales | 71000 | 3 |
-- Departments with >2 employees AND average salary >70000
SELECT
Department,
COUNT(*) AS EmployeeCount,
AVG(Salary) AS AverageSalary
FROM Employees
GROUP BY Department
HAVING COUNT(*) > 2
AND AVG(Salary) > 70000;
Result: | Department | EmployeeCount | AverageSalary | |————|—————|—————| | IT | 4 | 79500 | | Sales | 3 | 71000 |
-- Cities with >1 IT employee hired after 2019
SELECT
City,
COUNT(*) AS ITEmployees
FROM Employees
WHERE Department = 'IT' -- Filter rows first
AND HireDate > '2019-01-01'
GROUP BY City
HAVING COUNT(*) > 1; -- Filter groups after
Execution Order:
Result: | City | ITEmployees | |——|————-| | New York | 2 |
| Aspect | WHERE | HAVING |
|---|---|---|
| Filters | Individual rows | Groups |
| When | Before GROUP BY | After GROUP BY |
| Works with | Column values | Aggregate functions |
| Cannot use | Aggregate functions | - |
-- WHERE: Employees with salary > 70000 (individual rows)
SELECT Department, FirstName, Salary
FROM Employees
WHERE Salary > 70000;
Result: 6 individual employees
-- HAVING: Departments with average salary > 70000 (groups)
SELECT Department, AVG(Salary) AS AvgSalary
FROM Employees
GROUP BY Department
HAVING AVG(Salary) > 70000;
Result: 2 departments (IT and Sales)
SELECT
Department,
COUNT(*) AS EmployeeCount,
AVG(Salary) AS AvgSalary
FROM Employees
WHERE HireDate > '2019-01-01' -- Filter: Only recent hires
GROUP BY Department
HAVING COUNT(*) >= 2; -- Filter: Only depts with 2+ recent hires
Execution:
-- ❌ WRONG - Cannot use aggregate in WHERE
SELECT Department, AVG(Salary)
FROM Employees
WHERE AVG(Salary) > 70000 -- ERROR!
GROUP BY Department;
-- ✅ CORRECT - Use HAVING for aggregates
SELECT Department, AVG(Salary)
FROM Employees
GROUP BY Department
HAVING AVG(Salary) > 70000;
A query nested inside another query. Also called “inner query” or “nested query”.
-- Find employees earning more than average salary
SELECT FirstName, LastName, Salary
FROM Employees
WHERE Salary > (SELECT AVG(Salary) FROM Employees);
What happens:
SELECT AVG(Salary) FROM Employees returns 72600WHERE Salary > 72600Result: | FirstName | LastName | Salary | |———–|———-|——–| | John | Smith | 75000 | | Mike | Davis | 80000 | | Lisa | Anderson | 78000 | | Robert | Garcia | 85000 |
-- Find employees in departments that have >2 people
SELECT FirstName, LastName, Department
FROM Employees
WHERE Department IN (
SELECT Department
FROM Employees
GROUP BY Department
HAVING COUNT(*) > 2
);
What happens:
Result: | FirstName | LastName | Department | |———–|———-|————| | John | Smith | IT | | Mike | Davis | IT | | Emily | Wilson | Sales | | David | Brown | Sales | | Lisa | Anderson | IT | | Robert | Garcia | IT | | Jennifer | Lee | Sales |
-- Get department statistics and filter
SELECT *
FROM (
SELECT
Department,
AVG(Salary) AS AvgSalary,
COUNT(*) AS EmpCount
FROM Employees
GROUP BY Department
) AS DeptStats
WHERE AvgSalary > 70000;
What happens:
Result: | Department | AvgSalary | EmpCount | |————|———–|———-| | IT | 79500 | 4 | | Sales | 71000 | 3 |
A correlated subquery references columns from the outer query.
-- Find employees who earn more than their department average
SELECT e1.FirstName, e1.LastName, e1.Department, e1.Salary
FROM Employees e1
WHERE e1.Salary > (
SELECT AVG(e2.Salary)
FROM Employees e2
WHERE e2.Department = e1.Department -- References outer query
);
What happens:
Result: | FirstName | LastName | Department | Salary | |———–|———-|————|——–| | Sarah | Johnson | HR | 65000 | | Mike | Davis | IT | 80000 | | David | Brown | Sales | 72000 | | Lisa | Anderson | IT | 78000 | | Robert | Garcia | IT | 85000 |
-- Find departments that have at least one employee in New York
SELECT DISTINCT Department
FROM Employees e1
WHERE EXISTS (
SELECT 1
FROM Employees e2
WHERE e2.Department = e1.Department
AND e2.City = 'New York'
);
Result: | Department | |————| | IT |
-- Find departments with NO employees in New York
SELECT DISTINCT Department
FROM Employees e1
WHERE NOT EXISTS (
SELECT 1
FROM Employees e2
WHERE e2.Department = e1.Department
AND e2.City = 'New York'
);
Result: | Department | |————| | HR | | Sales | | Marketing |
-- Find employee(s) with highest salary
SELECT FirstName, LastName, Salary
FROM Employees
WHERE Salary >= ALL (SELECT Salary FROM Employees);
What happens:
>= ALL means greater than or equal to ALL values in subqueryResult: | FirstName | LastName | Salary | |———–|———-|——–| | Robert | Garcia | 85000 |
-- Find employees earning more than ANY IT employee
SELECT FirstName, LastName, Department, Salary
FROM Employees
WHERE Salary > ANY (
SELECT Salary
FROM Employees
WHERE Department = 'IT'
)
AND Department != 'IT';
What happens:
> ANY means greater than at least one valueCASE is like an if-then-else statement in SQL. It returns different values based on conditions.
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE result3
END
SELECT
FirstName,
LastName,
Salary,
CASE
WHEN Salary >= 80000 THEN 'High'
WHEN Salary >= 70000 THEN 'Medium'
ELSE 'Low'
END AS SalaryLevel
FROM Employees;
Result: | FirstName | LastName | Salary | SalaryLevel | |———–|———-|——–|————-| | John | Smith | 75000 | Medium | | Sarah | Johnson | 65000 | Low | | Mike | Davis | 80000 | High | | Emily | Wilson | 70000 | Medium | | David | Brown | 72000 | Medium | | Lisa | Anderson | 78000 | Medium | | James | Taylor | 62000 | Low | | Anna | Martinez | 68000 | Low | | Robert | Garcia | 85000 | High | | Jennifer | Lee | 71000 | Medium |
SELECT
FirstName,
LastName,
Department,
Salary,
CASE
WHEN Department = 'IT' AND Salary > 75000 THEN 'Senior IT'
WHEN Department = 'IT' THEN 'Junior IT'
WHEN Department = 'Sales' AND Salary > 70000 THEN 'Senior Sales'
WHEN Department = 'Sales' THEN 'Junior Sales'
ELSE Department
END AS Position
FROM Employees;
Result: | FirstName | LastName | Department | Salary | Position | |———–|———-|————|——–|———-| | John | Smith | IT | 75000 | Junior IT | | Sarah | Johnson | HR | 65000 | HR | | Mike | Davis | IT | 80000 | Senior IT | | … | … | … | … | … |
SELECT
Department,
COUNT(*) AS TotalEmployees,
SUM(CASE WHEN Salary > 70000 THEN 1 ELSE 0 END) AS HighEarners,
SUM(CASE WHEN Salary <= 70000 THEN 1 ELSE 0 END) AS LowEarners
FROM Employees
GROUP BY Department;
What happens:
Result: | Department | TotalEmployees | HighEarners | LowEarners | |————|—————-|————-|————| | HR | 2 | 0 | 2 | | IT | 4 | 4 | 0 | | Marketing | 1 | 0 | 1 | | Sales | 3 | 2 | 1 |
-- Custom sorting: IT first, then Sales, then others
SELECT FirstName, LastName, Department
FROM Employees
ORDER BY
CASE Department
WHEN 'IT' THEN 1
WHEN 'Sales' THEN 2
ELSE 3
END,
LastName;
Result: IT employees first, then Sales, then others, sorted by last name within each group
-- Flexible filtering based on condition
SELECT FirstName, LastName, Department, Salary
FROM Employees
WHERE
CASE
WHEN Department = 'IT' THEN Salary > 75000
WHEN Department = 'Sales' THEN Salary > 70000
ELSE Salary > 65000
END;
What happens:
SELECT
FirstName,
LastName,
Department,
CASE Department
WHEN 'IT' THEN 'Technology'
WHEN 'HR' THEN 'Human Resources'
WHEN 'Sales' THEN 'Sales & Marketing'
ELSE 'Other'
END AS DepartmentFullName
FROM Employees;
Result: | FirstName | LastName | Department | DepartmentFullName | |———–|———-|————|——————–| | John | Smith | IT | Technology | | Sarah | Johnson | HR | Human Resources | | … | … | … | … |
SELECT
CONCAT(FirstName, ' ', LastName) AS FullName,
CONCAT(City, ', ', Country) AS Location
FROM Employees;
Result: | FullName | Location | |———-|———-| | John Smith | New York, USA | | Sarah Johnson | Los Angeles, USA | | … | … |
SELECT
FirstName,
UPPER(FirstName) AS Uppercase,
LOWER(FirstName) AS Lowercase
FROM Employees
LIMIT 3;
Result: | FirstName | Uppercase | Lowercase | |———–|———–|———–| | John | JOHN | john | | Sarah | SARAH | sarah | | Mike | MIKE | mike |
SELECT
FirstName,
LENGTH(FirstName) AS NameLength
FROM Employees;
Result: | FirstName | NameLength | |———–|————| | John | 4 | | Sarah | 5 | | Mike | 4 | | … | … |
-- Extract first 3 characters
SELECT
Email,
SUBSTRING(Email, 1, 3) AS FirstThree,
SUBSTRING(Email, 1, POSITION('@' IN Email) - 1) AS Username
FROM Employees
LIMIT 3;
SUBSTRING(string, start_position, length)
Result: | Email | FirstThree | Username | |——-|————|———-| | john.smith@company.com | joh | john.smith | | sarah.j@company.com | sar | sarah.j | | … | … | … |
SELECT
TRIM(' Hello ') AS Trimmed, -- 'Hello'
LTRIM(' Hello ') AS LeftTrimmed, -- 'Hello '
RTRIM(' Hello ') AS RightTrimmed; -- ' Hello'
SELECT
Email,
REPLACE(Email, '@company.com', '@newcompany.com') AS NewEmail
FROM Employees
LIMIT 3;
Result: | Email | NewEmail | |——-|———-| | john.smith@company.com | john.smith@newcompany.com | | sarah.j@company.com | sarah.j@newcompany.com | | … | … |
SELECT
FirstName,
LEFT(FirstName, 3) AS First3,
RIGHT(FirstName, 3) AS Last3
FROM Employees
LIMIT 3;
Result: | FirstName | First3 | Last3 | |———–|——–|——-| | John | Joh | ohn | | Sarah | Sar | rah | | Mike | Mik | ike |
SELECT
Email,
POSITION('@' IN Email) AS AtPosition
FROM Employees
LIMIT 3;
Result: | Email | AtPosition | |——-|————| | john.smith@company.com | 11 | | sarah.j@company.com | 8 | | … | … |
SELECT
NOW() AS CurrentDateTime, -- 2024-01-30 14:35:22
CURDATE() AS CurrentDate, -- 2024-01-30
CURTIME() AS CurrentTime; -- 14:35:22
SELECT
HireDate,
YEAR(HireDate) AS HireYear,
MONTH(HireDate) AS HireMonth,
DAY(HireDate) AS HireDay,
DAYNAME(HireDate) AS HireDayName
FROM Employees
LIMIT 3;
Result: | HireDate | HireYear | HireMonth | HireDay | HireDayName | |———-|———-|———–|———|————-| | 2020-01-15 | 2020 | 1 | 15 | Wednesday | | 2019-03-20 | 2019 | 3 | 20 | Wednesday | | … | … | … | … | … |
SELECT
FirstName,
LastName,
HireDate,
DATEDIFF(CURDATE(), HireDate) AS DaysEmployed,
ROUND(DATEDIFF(CURDATE(), HireDate) / 365, 1) AS YearsEmployed
FROM Employees;
Result: | FirstName | LastName | HireDate | DaysEmployed | YearsEmployed | |———–|———-|———-|————–|—————| | John | Smith | 2020-01-15 | 1476 | 4.0 | | Sarah | Johnson | 2019-03-20 | 1777 | 4.9 | | … | … | … | … | … |
SELECT
FirstName,
HireDate,
DATE_ADD(HireDate, INTERVAL 1 YEAR) AS OneYearAnniversary,
DATE_ADD(HireDate, INTERVAL 90 DAY) AS After90Days,
DATE_SUB(CURDATE(), INTERVAL 1 YEAR) AS OneYearAgo
FROM Employees
LIMIT 3;
SELECT
FirstName,
HireDate,
TIMESTAMPDIFF(YEAR, HireDate, CURDATE()) AS YearsEmployed,
TIMESTAMPDIFF(MONTH, HireDate, CURDATE()) AS MonthsEmployed,
TIMESTAMPDIFF(DAY, HireDate, CURDATE()) AS DaysEmployed
FROM Employees
LIMIT 3;
-- Employees hired in 2020
SELECT FirstName, LastName, HireDate
FROM Employees
WHERE YEAR(HireDate) = 2020;
-- Employees hired in last year
SELECT FirstName, LastName, HireDate
FROM Employees
WHERE HireDate >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR);
-- Employees hired in Q1 (Jan-Mar)
SELECT FirstName, LastName, HireDate
FROM Employees
WHERE MONTH(HireDate) BETWEEN 1 AND 3;
-- Employees without a manager (top level)
SELECT FirstName, LastName, ManagerID
FROM Employees
WHERE ManagerID IS NULL;
Result: | FirstName | LastName | ManagerID | |———–|———-|———–| | John | Smith | NULL | | Emily | Wilson | NULL | | Anna | Martinez | NULL |
SELECT
FirstName,
LastName,
ManagerID,
COALESCE(ManagerID, 0) AS ManagerIDOrZero,
COALESCE(ManagerID, 'No Manager') AS ManagerIDOrText
FROM Employees
LIMIT 5;
Result: | FirstName | LastName | ManagerID | ManagerIDOrZero | ManagerIDOrText | |———–|———-|———–|—————–|—————–| | John | Smith | NULL | 0 | No Manager | | Sarah | Johnson | 1 | 1 | 1 | | … | … | … | … | … |
-- IFNULL(value, replacement) - MySQL specific
SELECT
FirstName,
IFNULL(ManagerID, 0) AS ManagerIDOrZero
FROM Employees
LIMIT 3;
-- NULLIF(value1, value2) - Returns NULL if equal
SELECT
NULLIF(5, 5), -- Returns NULL
NULLIF(5, 10); -- Returns 5
-- NULL in math operations returns NULL
SELECT
5 + NULL AS Result1, -- NULL
10 * NULL AS Result2, -- NULL
COALESCE(5 + NULL, 0) AS Result3; -- 0
Important:
Combines results from two or more SELECT statements. Removes duplicates.
-- Employees in IT or with salary > 75000
SELECT FirstName, LastName, 'IT Department' AS Reason
FROM Employees
WHERE Department = 'IT'
UNION
SELECT FirstName, LastName, 'High Salary' AS Reason
FROM Employees
WHERE Salary > 75000;
Rules:
-- Same as above but keeps duplicates
SELECT FirstName, LastName
FROM Employees
WHERE Department = 'IT'
UNION ALL
SELECT FirstName, LastName
FROM Employees
WHERE Salary > 75000;
Difference:
-- Employees in IT AND with salary > 75000
SELECT FirstName, LastName
FROM Employees
WHERE Department = 'IT'
INTERSECT
SELECT FirstName, LastName
FROM Employees
WHERE Salary > 75000;
Note: Not all databases support INTERSECT (alternative: use JOIN or WHERE with AND)
-- Employees in IT but NOT with high salary
SELECT FirstName, LastName
FROM Employees
WHERE Department = 'IT'
EXCEPT
SELECT FirstName, LastName
FROM Employees
WHERE Salary > 80000;
Note: MySQL doesn’t support EXCEPT (use LEFT JOIN with NULL check)
-- Highest paid employee in each department
SELECT e1.*
FROM Employees e1
WHERE e1.Salary = (
SELECT MAX(e2.Salary)
FROM Employees e2
WHERE e2.Department = e1.Department
);
SELECT
FirstName,
LastName,
Salary,
(SELECT SUM(e2.Salary)
FROM Employees e2
WHERE e2.EmployeeID <= e1.EmployeeID) AS RunningTotal
FROM Employees e1
ORDER BY EmployeeID;
-- Rank employees by salary within department
SELECT
FirstName,
LastName,
Department,
Salary,
(SELECT COUNT(*) + 1
FROM Employees e2
WHERE e2.Department = e1.Department
AND e2.Salary > e1.Salary) AS SalaryRank
FROM Employees e1
ORDER BY Department, SalaryRank;
-- Find missing OrderIDs
SELECT o1.OrderID + 1 AS MissingID
FROM Orders o1
LEFT JOIN Orders o2 ON o1.OrderID + 1 = o2.OrderID
WHERE o2.OrderID IS NULL
AND o1.OrderID < (SELECT MAX(OrderID) FROM Orders);
-- Count employees by department and salary range
SELECT
Department,
SUM(CASE WHEN Salary < 70000 THEN 1 ELSE 0 END) AS Low,
SUM(CASE WHEN Salary BETWEEN 70000 AND 80000 THEN 1 ELSE 0 END) AS Medium,
SUM(CASE WHEN Salary > 80000 THEN 1 ELSE 0 END) AS High
FROM Employees
GROUP BY Department;
1. FROM - Get tables
2. JOIN - Combine tables
3. WHERE - Filter rows
4. GROUP BY - Group rows
5. HAVING - Filter groups
6. SELECT - Choose columns
7. DISTINCT - Remove duplicates
8. ORDER BY - Sort results
9. LIMIT - Limit rows
SELECT Department, AVG(Salary) AS AvgSalary
FROM Employees
WHERE HireDate > '2019-01-01'
GROUP BY Department
HAVING AVG(Salary) > 70000
ORDER BY AvgSalary DESC
LIMIT 3;
Execution Steps:
SELECT
e.Department,
COUNT(*) AS TotalEmployees,
ROUND(AVG(e.Salary), 2) AS AvgSalary,
MIN(e.Salary) AS MinSalary,
MAX(e.Salary) AS MaxSalary,
SUM(CASE WHEN e.HireDate >= DATE_SUB(CURDATE(), INTERVAL 2 YEAR) THEN 1 ELSE 0 END) AS RecentHires,
CONCAT('$', FORMAT(SUM(e.Salary), 0)) AS TotalPayroll
FROM Employees e
WHERE e.Salary > 60000
GROUP BY e.Department
HAVING COUNT(*) >= 2
ORDER BY AvgSalary DESC;
SELECT
p.Category,
COUNT(DISTINCT o.OrderID) AS TotalOrders,
SUM(o.Quantity) AS TotalQuantitySold,
ROUND(AVG(o.TotalAmount), 2) AS AvgOrderValue,
SUM(o.TotalAmount) AS TotalRevenue,
CASE
WHEN SUM(o.TotalAmount) > 2000 THEN 'High Performer'
WHEN SUM(o.TotalAmount) > 1000 THEN 'Medium Performer'
ELSE 'Low Performer'
END AS PerformanceCategory
FROM Products p
INNER JOIN Orders o ON p.ProductID = o.ProductID
WHERE o.Status = 'Delivered'
GROUP BY p.Category
ORDER BY TotalRevenue DESC;
SELECT
o.CustomerName,
COUNT(o.OrderID) AS TotalOrders,
SUM(o.TotalAmount) AS TotalSpent,
AVG(o.TotalAmount) AS AvgOrderValue,
MIN(o.OrderDate) AS FirstOrder,
MAX(o.OrderDate) AS LastOrder,
DATEDIFF(MAX(o.OrderDate), MIN(o.OrderDate)) AS DaysBetweenFirstLast,
CASE
WHEN COUNT(o.OrderID) >= 3 THEN 'Loyal Customer'
WHEN COUNT(o.OrderID) = 2 THEN 'Repeat Customer'
ELSE 'One-Time Customer'
END AS CustomerType
FROM Orders o
GROUP BY o.CustomerName
HAVING COUNT(o.OrderID) >= 2
ORDER BY TotalSpent DESC;
SELECT
p.ProductName,
p.Category,
p.Stock,
COALESCE(SUM(o.Quantity), 0) AS TotalSold,
p.Stock - COALESCE(SUM(o.Quantity), 0) AS RemainingStock,
CASE
WHEN p.Stock - COALESCE(SUM(o.Quantity), 0) < 10 THEN 'Reorder Needed'
WHEN p.Stock - COALESCE(SUM(o.Quantity), 0) < 50 THEN 'Low Stock'
ELSE 'Adequate Stock'
END AS StockStatus,
ROUND((COALESCE(SUM(o.Quantity), 0) * 100.0 / p.Stock), 2) AS SoldPercentage
FROM Products p
LEFT JOIN Orders o ON p.ProductID = o.ProductID AND o.Status = 'Delivered'
GROUP BY p.ProductID, p.ProductName, p.Category, p.Stock
ORDER BY SoldPercentage DESC;
SELECT
DATE_FORMAT(OrderDate, '%Y-%m') AS OrderMonth,
COUNT(OrderID) AS OrderCount,
SUM(TotalAmount) AS MonthlyRevenue,
AVG(TotalAmount) AS AvgOrderValue,
COUNT(DISTINCT CustomerName) AS UniqueCustomers,
ROUND(SUM(TotalAmount) / COUNT(DISTINCT CustomerName), 2) AS RevenuePerCustomer
FROM Orders
WHERE OrderDate >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
GROUP BY DATE_FORMAT(OrderDate, '%Y-%m')
ORDER BY OrderMonth DESC;
-- Step 1: Basic SELECT
SELECT * FROM Employees;
-- Step 2: Add specific columns
SELECT FirstName, LastName, Department FROM Employees;
-- Step 3: Add filtering
SELECT FirstName, LastName, Department FROM Employees WHERE Department = 'IT';
-- Step 4: Add grouping
SELECT Department, COUNT(*) FROM Employees GROUP BY Department;
-- Step 5: Add more aggregates
SELECT Department, COUNT(*), AVG(Salary) FROM Employees GROUP BY Department;
-- Get department statistics for high-salary departments
SELECT
Department,
COUNT(*) AS EmpCount, -- Number of employees
AVG(Salary) AS AvgSalary -- Average salary
FROM Employees
WHERE Salary > 60000 -- Only well-paid employees
GROUP BY Department
HAVING AVG(Salary) > 70000; -- Only high-avg departments
GROUP BY ... HAVING COUNT(*) > 1LEFT JOIN ... WHERE right_table.id IS NULLORDER BY ... LIMIT N(COUNT(*) * 100.0 / total)WHERE date BETWEEN ... AND ...WHERE column LIKE '%pattern%'WHERE (condition1 OR condition2) AND condition3WHERE column IN (SELECT ...)Happy querying! 🎯