Below is a set of practical questions in English designed to test your understanding of SQL JOINs, including all the topics you mentioned: SQL JOIN, AS Alias, WHERE Clause, Multiple Tables, Types of JOINs (INNER, LEFT, RIGHT, FULL OUTER, SELF, CROSS), and JOIN vs. Nested Queries. These questions include a sample database schema for context and require writing SQL queries to solve real-world scenarios. Each question is practical and focuses on applying the concepts.
Sample Database Schema
Use the following tables for all questions:
Customers:
customer_id(INT, Primary Key)first_name(VARCHAR)country(VARCHAR)
Orders:
order_id(INT, Primary Key)customer_id(INT, Foreign Key)order_date(DATE)amount(DECIMAL)
OrderDetails:
order_id(INT, Foreign Key)product_id(INT)quantity(INT)
Products:
product_id(INT, Primary Key)product_name(VARCHAR)price(DECIMAL)
Employees:
employee_id(INT, Primary Key)first_name(VARCHAR)manager_id(INT, Foreign Key to employee_id)
Sample Data:
-- Create Customers Table
CREATE TABLE Customers (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50),
country VARCHAR(50)
);
-- Create Orders Table
CREATE TABLE Orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
amount DECIMAL(10, 2),
FOREIGN KEY (customer_id) REFERENCES Customers(customer_id)
);
-- Create OrderDetails Table
CREATE TABLE OrderDetails (
order_id INT,
product_id INT,
quantity INT,
FOREIGN KEY (order_id) REFERENCES Orders(order_id)
);
-- Create Products Table
CREATE TABLE Products (
product_id INT PRIMARY KEY,
product_name VARCHAR(50),
price DECIMAL(10, 2)
);
-- Create Employees Table
CREATE TABLE Employees (
employee_id INT PRIMARY KEY,
first_name VARCHAR(50),
manager_id INT,
FOREIGN KEY (manager_id) REFERENCES Employees(employee_id)
);
-- Insert Sample Data into Customers
INSERT INTO Customers (customer_id, first_name, country) VALUES
(1, 'Alice', 'USA'),
(2, 'Bob', 'UK'),
(3, 'Charlie', 'Canada'),
(4, 'David', 'USA');
-- Insert Sample Data into Orders
INSERT INTO Orders (order_id, customer_id, order_date, amount) VALUES
(101, 1, '2023-01-15', 150.00),
(102, 1, '2023-02-20', 300.00),
(103, 2, '2023-03-10', 200.00),
(104, 3, '2023-04-05', 100.00);
-- Insert Sample Data into OrderDetails
INSERT INTO OrderDetails (order_id, product_id, quantity) VALUES
(101, 1001, 5),
(101, 1002, 2),
(102, 1003, 3),
(103, 1001, 4);
-- Insert Sample Data into Products
INSERT INTO Products (product_id, product_name, price) VALUES
(1001, 'Laptop', 1000.00),
(1002, 'Mouse', 20.00),
(1003, 'Keyboard', 50.00);
-- Insert Sample Data into Employees
INSERT INTO Employees (employee_id, first_name, manager_id) VALUES
(1, 'Emma', NULL),
(2, 'Liam', 1),
(3, 'Olivia', 1),
(4, 'Noah', 2);
Below are the solutions to all 20 practical questions based on the provided database schema and sample data. Each solution includes the SQL query, a brief explanation, and the expected output based on the sample data provided. The queries are written to be clear, efficient, and compatible with standard SQL databases (e.g., MySQL, PostgreSQL, SQL Server). I’ve also included comparisons for questions involving JOIN vs. subqueries and addressed any special cases (e.g., emulating FULL OUTER JOIN).
Database Schema and Sample Data Recap
The tables (Customers, Orders, OrderDetails, Products, Employees) and sample data were provided in the previous response. For reference, here’s a quick summary of key data:
- Customers: 4 customers (Alice, Bob, Charlie, David).
- Orders: 4 orders (3 for Alice and Bob, 1 for Charlie, none for David).
- OrderDetails: Links orders to products (e.g., order 101 includes Laptop and Mouse).
- Products: 3 products (Laptop, Mouse, Keyboard).
- Employees: 4 employees (Emma is the top manager, others report to Emma or Liam).
Solutions to Practical Questions
1. Basic SQL JOIN
Question: Retrieve the first name of customers and their order IDs for all orders placed using an INNER JOIN.
Query:
SELECT c.first_name, o.order_id
FROM Customers c
INNER JOIN Orders o
ON c.customer_id = o.customer_id;
Explanation:
- INNER JOIN returns only customers with orders.
- Matches
Customers.customer_idwithOrders.customer_id. - Selects customer’s first name and order ID.
Expected Output:
| first_name | order_id |
|---|---|
| Alice | 101 |
| Alice | 102 |
| Bob | 103 |
| Charlie | 104 |
2. SQL JOIN With AS Alias
Question: List customer first name and order date using aliases c and o for Customers and Orders.
Query:
SELECT c.first_name, o.order_date
FROM Customers AS c
INNER JOIN Orders AS o
ON c.customer_id = o.customer_id;
Explanation:
- Uses
ASto alias tables (cforCustomers,oforOrders). - INNER JOIN ensures only customers with orders are included.
- Retrieves
first_nameandorder_date.
Expected Output:
| first_name | order_date |
|---|---|
| Alice | 2023-01-15 |
| Alice | 2023-02-20 |
| Bob | 2023-03-10 |
| Charlie | 2023-04-05 |
3. JOIN With WHERE Clause
Question: Find customers with orders where amount > 150, showing first name, order ID, and amount.
Query:
SELECT c.first_name, o.order_id, o.amount
FROM Customers c
INNER JOIN Orders o
ON c.customer_id = o.customer_id
WHERE o.amount > 150;
Explanation:
- INNER JOIN matches customers to orders.
- WHERE clause filters orders with
amount > 150. - Returns first name, order ID, and amount for qualifying orders.
Expected Output:
| first_name | order_id | amount |
|---|---|---|
| Alice | 102 | 300.00 |
| Bob | 103 | 200.00 |
4. JOIN Multiple Tables
Question: Retrieve customer first name, order date, product name, and quantity by joining Customers, Orders, OrderDetails, and Products.
Query:
SELECT c.first_name, o.order_date, p.product_name, od.quantity
FROM Customers c
JOIN Orders o ON c.customer_id = o.customer_id
JOIN OrderDetails od ON o.order_id = od.order_id
JOIN Products p ON od.product_id = p.product_id;
Explanation:
- Chains JOINs to connect all four tables.
- Matches:
Customers→Orders(viacustomer_id),Orders→OrderDetails(viaorder_id),OrderDetails→Products(viaproduct_id). - Retrieves first name, order date, product name, and quantity.
Expected Output:
| first_name | order_date | product_name | quantity |
|---|---|---|---|
| Alice | 2023-01-15 | Laptop | 5 |
| Alice | 2023-01-15 | Mouse | 2 |
| Alice | 2023-02-20 | Keyboard | 3 |
| Bob | 2023-03-10 | Laptop | 4 |
5. SQL INNER JOIN
Question: List customers with at least one order, showing first name and total number of orders using INNER JOIN and GROUP BY.
Query:
SELECT c.first_name, COUNT(o.order_id) AS order_count
FROM Customers c
INNER JOIN Orders o
ON c.customer_id = o.customer_id
GROUP BY c.first_name;
Explanation:
- INNER JOIN excludes customers without orders.
- GROUP BY groups results by
first_name. - COUNT calculates the number of orders per customer.
Expected Output:
| first_name | order_count |
|---|---|
| Alice | 2 |
| Bob | 1 |
| Charlie | 1 |
6. SQL LEFT JOIN
Question: List all customers and their order IDs, including those without orders, using LEFT JOIN.
Query:
SELECT c.first_name, o.order_id
FROM Customers c
LEFT JOIN Orders o
ON c.customer_id = o.customer_id;
Explanation:
- LEFT JOIN includes all customers, even those without orders.
- Non-matching orders return NULL for
order_id. - Returns first name and order ID.
Expected Output:
| first_name | order_id |
|---|---|
| Alice | 101 |
| Alice | 102 |
| Bob | 103 |
| Charlie | 104 |
| David | NULL |
7. SQL RIGHT JOIN
Question: List all orders and associated customer names, including orders without matching customers, using RIGHT JOIN.
Query:
SELECT c.first_name, o.order_id
FROM Customers c
RIGHT JOIN Orders o
ON c.customer_id = o.customer_id;
Explanation:
- RIGHT JOIN includes all orders, with NULL for non-matching customers.
- In this dataset, all orders have matching customers, so no NULLs appear.
- Returns first name and order ID.
Expected Output:
| first_name | order_id |
|---|---|
| Alice | 101 |
| Alice | 102 |
| Bob | 103 |
| Charlie | 104 |
8. SQL FULL OUTER JOIN
Question: List all customers and orders, including those without matches, using FULL OUTER JOIN. If not supported, describe emulation.
Query:
SELECT c.first_name, o.order_id
FROM Customers c
FULL OUTER JOIN Orders o
ON c.customer_id = o.customer_id;
Explanation:
- FULL OUTER JOIN includes all customers and orders, with NULLs for non-matches.
- In this dataset, David has no orders, so his
order_idis NULL. - If FULL OUTER JOIN is not supported (e.g., MySQL), emulate with:
Emulation Query (for databases like MySQL):
SELECT c.first_name, o.order_id
FROM Customers c
LEFT JOIN Orders o
ON c.customer_id = o.customer_id
UNION
SELECT c.first_name, o.order_id
FROM Customers c
RIGHT JOIN Orders o
ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL;
Expected Output:
| first_name | order_id |
|---|---|
| Alice | 101 |
| Alice | 102 |
| Bob | 103 |
| Charlie | 104 |
| David | NULL |
9. SQL SELF JOIN
Question: Display each employee’s first name and their manager’s first name using a SELF JOIN.
Query:
SELECT e1.first_name AS employee, e2.first_name AS manager
FROM Employees e1
LEFT JOIN Employees e2
ON e1.manager_id = e2.employee_id;
Explanation:
- SELF JOIN joins
Employeeswith itself. - LEFT JOIN ensures employees without managers (e.g., Emma) are included.
- Aliases
e1ande2distinguish the employee and manager instances.
Expected Output:
| employee | manager |
|---|---|
| Emma | NULL |
| Liam | Emma |
| Olivia | Emma |
| Noah | Liam |
10. SQL CROSS JOIN
Question: Generate all combinations of customers and products with price > 500, showing first name and product name.
Query:
SELECT c.first_name, p.product_name
FROM Customers c
CROSS JOIN Products p
WHERE p.price > 500;
Explanation:
- CROSS JOIN pairs every customer with every product.
- WHERE clause filters products with
price > 500(only Laptop). - Returns customer first name and product name.
Expected Output:
| first_name | product_name |
|---|---|
| Alice | Laptop |
| Bob | Laptop |
| Charlie | Laptop |
| David | Laptop |
11. Practical Scenario (Multiple JOIN Types)
Question: List USA customers, their order IDs, and amounts (> 100) using LEFT JOIN.
Query:
SELECT c.first_name, o.order_id, o.amount
FROM Customers c
LEFT JOIN Orders o
ON c.customer_id = o.customer_id
WHERE c.country = 'USA' AND (o.amount > 100 OR o.amount IS NULL);
Explanation:
- LEFT JOIN includes all USA customers, even without orders.
- WHERE filters for
country = 'USA'and orders withamount > 100or NULL (for customers without orders). - Returns first name, order ID, and amount.
Expected Output:
| first_name | order_id | amount |
|---|---|---|
| Alice | 101 | 150.00 |
| Alice | 102 | 300.00 |
| David | NULL | NULL |
12. JOIN vs. Nested Queries
Question: Find customers who ordered 'Laptop' using JOIN and subquery. Compare approaches.
JOIN Query:
SELECT DISTINCT c.first_name
FROM Customers c
JOIN Orders o ON c.customer_id = o.customer_id
JOIN OrderDetails od ON o.order_id = od.order_id
JOIN Products p ON od.product_id = p.product_id
WHERE p.product_name = 'Laptop';
Subquery Query:
SELECT first_name
FROM Customers
WHERE customer_id IN (
SELECT o.customer_id
FROM Orders o
JOIN OrderDetails od ON o.order_id = od.order_id
JOIN Products p ON od.product_id = p.product_id
WHERE p.product_name = 'Laptop'
);
Expected Output (Both Queries): ``` first_name
Alice Bob
**Comparison**:
- **Readability**: JOIN is clearer for multi-table queries, as relationships are explicit. Subquery breaks logic into steps, which may be intuitive for simpler tasks but less readable for complex joins.
- **Performance**: JOIN is typically faster, as the database optimizes the join operation. The subquery may execute the inner query separately, especially if not optimized. For large datasets, JOIN is preferred.
- **Recommendation**: Use JOIN for this scenario due to clarity and performance.
---
#### 13. Advanced JOIN with Aggregation
**Question**: Calculate total order amount per customer, including those without orders, using LEFT JOIN.
**Query**:
```sql
SELECT c.first_name, COALESCE(SUM(o.amount), 0) AS total_amount
FROM Customers c
LEFT JOIN Orders o
ON c.customer_id = o.customer_id
GROUP BY c.first_name;
Explanation:
- LEFT JOIN includes all customers.
- SUM aggregates order amounts; COALESCE converts NULL to 0 for customers without orders.
- GROUP BY groups by
first_name.
Expected Output:
| first_name | total_amount |
|---|---|
| Alice | 450.00 |
| Bob | 200.00 |
| Charlie | 100.00 |
| David | 0.00 |
14. Complex Multiple Table JOIN
Question: Find total quantity of each product ordered by UK customers.
Query:
SELECT p.product_name, SUM(od.quantity) AS total_quantity
FROM Customers c
JOIN Orders o ON c.customer_id = o.customer_id
JOIN OrderDetails od ON o.order_id = od.order_id
JOIN Products p ON od.product_id = p.product_id
WHERE c.country = 'UK'
GROUP BY p.product_name;
Explanation:
- Joins connect
Customers(filtered for UK),Orders,OrderDetails, andProducts. - SUM calculates total quantity per product.
- GROUP BY groups by
product_name.
Expected Output:
| product_name | total_quantity |
|---|---|
| Laptop | 4 |
15. SELF JOIN with WHERE Clause
Question: Find pairs of employees with the same manager, showing their names and manager’s name.
Query:
SELECT e1.first_name AS employee1, e2.first_name AS employee2, m.first_name AS manager
FROM Employees e1
JOIN Employees e2 ON e1.manager_id = e2.manager_id AND e1.employee_id < e2.employee_id
JOIN Employees m ON e1.manager_id = m.employee lea_id;
Explanation:
- SELF JOIN on
Employeesmatches employees with the samemanager_id. e1.employee_id < e2.employee_idprevents duplicate pairs (e.g., Liam-Olivia vs. Olivia-Liam).- JOIN with
Employees(asm) gets manager’s name. - Excludes Emma (no manager) since the JOIN requires a valid
manager_id.
Expected Output:
| employee1 | employee2 | manager |
|---|---|---|
| Liam | Olivia | Emma |
16. CROSS JOIN for Combinations
Question: Pair every customer with products priced under 100, showing first name and product name.
Query:
SELECT c.first_name, p.product_name
FROM Customers c
CROSS JOIN Products p
WHERE p.price < 100;
Explanation:
- CROSS JOIN creates all customer-product pairs.
- WHERE filters for products with
price < 100(Mouse, Keyboard). - Returns first name and product name.
Expected Output:
| first_name | product_name |
|---|---|
| Alice | Mouse |
| Alice | Keyboard |
| Bob | Mouse |
| Bob | Keyboard |
| Charlie | Mouse |
| Charlie | Keyboard |
| David | Mouse |
| David | Keyboard |
17. Emulating FULL OUTER JOIN
Question: Emulate FULL OUTER JOIN to list all customers and orders, including unmatched rows.
Query:
SELECT c.first_name, o.order_id
FROM Customers c
LEFT JOIN Orders o
ON c.customer_id = o.customer_id
UNION
SELECT c.first_name, o.order_id
FROM Customers c
RIGHT JOIN Orders o
ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL;
Explanation:
- LEFT JOIN gets all customers, including those without orders.
- RIGHT JOIN gets all orders, filtering for unmatched orders (though none exist here).
- UNION combines results, removing duplicates.
- Mimics FULL OUTER JOIN behavior.
Expected Output:
| first_name | order_id |
|---|---|
| Alice | 101 |
| Alice | 102 |
| Bob | 103 |
| Charlie | 104 |
| David | NULL |
18. Practical Business Question
Question: Report all orders with customer first name, order date, and total order value (quantity * price).
Query:
SELECT c.first_name, o.order_date, SUM(od.quantity * p.price) AS total_value
FROM Customers c
JOIN Orders o ON c.customer_id = o.customer_id
JOIN OrderDetails od ON o.order_id = od.order_id
JOIN Products p ON od.product_id = p.product_id
GROUP BY c.first_name, o.order_date, o.order_id;
Explanation:
- Joins connect all four tables.
quantity * pricecalculates the value per order detail.- SUM aggregates total value per order.
- GROUP BY includes
order_idto ensure unique orders.
Expected Output:
| first_name | order_date | total_value |
|---|---|---|
| Alice | 2023-01-15 | 5040.00 |
| Alice | 2023-02-20 | 150.00 |
| Bob | 2023-03-10 | 4000.00 |
19. JOIN with Sorting and Limiting
Question: List top 3 customers by total order amount, showing first name and total amount.
Query:
SELECT c.first_name, SUM(o.amount) AS total_amount
FROM Customers c
INNER JOIN Orders o
ON c.customer_id = o.customer_id
GROUP BY c.first_name
ORDER BY total_amount DESC
LIMIT 3;
Explanation:
- INNER JOIN includes only customers with orders.
- SUM calculates total order amount per customer.
- ORDER BY sorts by total amount (descending).
- LIMIT restricts to top 3.
Expected Output:
| first_name | total_amount |
|---|---|
| Alice | 450.00 |
| Bob | 200.00 |
| Charlie | 100.00 |
20. Subquery Alternative to LEFT JOIN
Question: Rewrite Question 6 (list all customers and order IDs) using a subquery. Compare approaches.
Subquery Query:
SELECT c.first_name, (
SELECT o.order_id
FROM Orders o
WHERE o.customer_id = c.customer_id
LIMIT 1
) AS order_id
FROM Customers c;
Explanation:
- Subquery retrieves an
order_idfor each customer. - LIMIT 1 ensures one order per customer (note: this doesn’t list all orders like the LEFT JOIN).
- To match the LEFT JOIN fully, we’d need a correlated subquery with multiple rows, which is complex.
Expected Output (Partial, as it shows one order per customer):
| first_name | order_id |
|---|---|
| Alice | 101 |
| Bob | 103 |
| Charlie | 104 |
| David | NULL |
Comparison:
- Clarity: LEFT JOIN (Question 6) is clearer, as it naturally handles multiple orders per customer. The subquery is less intuitive and requires additional logic for multiple rows.
- Performance: LEFT JOIN is more efficient, as it’s optimized by the database. The correlated subquery executes for each customer, potentially slowing down with large datasets.
- Recommendation: Use LEFT JOIN for this scenario due to simplicity and performance.
Notes
- Correctness: All queries were designed to produce the expected output based on the sample data. You can test them by running the table creation and insert scripts provided earlier.
- Performance: Queries use JOINs where possible for efficiency. Indexes on
customer_id,order_id, andproduct_idwould improve performance in a real database. - Edge Cases: Handled cases like customers without orders (LEFT JOIN), employees without managers (SELF JOIN), and databases without FULL OUTER JOIN support.
- Comparisons: For JOIN vs. subquery questions (12, 20), JOINs are generally preferred for multi-table queries due to readability and optimization.