This lesson on Advanced SQL Tutorial | Subqueries is hands-on and example-driven. You will learn how to embed nested subqueries across SELECT, FROM, and WHERE clauses in SQL. You will master calculating table-wide aggregates alongside granular rows, deriving queryable inline views, and filtering datasets against secondary tables without writing full joins.
What You'll Be Able To Do
- Construct scalar subqueries inside a SELECT clause to display aggregated benchmarks beside row-level details.
- Evaluate trade-offs between SELECT subqueries, window functions, and GROUP BY aggregations.
- Nest derived table subqueries inside a FROM clause with required aliases for multi-step data transformations.
- Apply WHERE clause subqueries using the IN operator to filter records based on secondary table criteria.
- Distinguish when to use WHERE subqueries versus SQL JOIN operations depending on column output needs.
Detailed Concept Walkthrough
1. Scalar Subqueries in the SELECT Clause
A scalar subquery in the SELECT clause executes an independent aggregate calculation and returns a single value to display alongside individual row data.
- Mechanism: The inner query executes
SELECT AVG(salary) FROM EmployeeSalaryto produce a single scalar value. This scalar result is then appended as an identical constant column across every individual row returned by the outer query. - Comparison with GROUP BY: A standard
GROUP BYcollapses rows to the specified grouping dimensions (e.g.,EmployeeID, Salary), making it impossible to compute an overall table-wide average across all rows simultaneously without losing row-level detail. - Alternative Pattern: Window functions like
AVG(Salary) OVER()achieve the exact same output without requiring a separate nested query block, often with cleaner syntax and better execution plans.
-- Scalar subquery in SELECT vs Window Function equivalent
SELECT
EmployeeID,
Salary,
(SELECT AVG(Salary) FROM EmployeeSalary) AS AllAvgSalary
FROM EmployeeSalary;
-- Equivalent approach using window functions:
-- SELECT EmployeeID, Salary, AVG(Salary) OVER() AS AllAvgSalary FROM EmployeeSalary;
Key Takeaway: SELECT subqueries must return exactly one value per row, serving as a direct alternative to unpartitioned window functions.
2. Derived Tables in the FROM Clause
A subquery in the FROM clause acts as a temporary inline view or derived table that the outer query selects from directly.
- Execution Flow: The database engine processes the inner subquery first to construct an intermediate result set in memory, and the outer query subsequently executes filters, selections, and projections on top of that intermediate dataset.
- Syntax Rule: Subqueries located within the FROM clause must be assigned a table alias (e.g.,
FROM (...) a), allowing outer SELECT statements to explicitly reference inner column names. - Best Practice: Inline FROM subqueries cannot be reused across multiple queries and can become slow and difficult to maintain; Common Table Expressions (CTEs) or temporary tables are generally preferred for multi-step transformations.
-- Querying from a derived inline table
SELECT
a.EmployeeID,
a.AllAvgSalary
FROM (
SELECT
EmployeeID,
Salary,
AVG(Salary) OVER () AS AllAvgSalary
FROM EmployeeSalary
) a;
Key Takeaway: FROM subqueries create queryable intermediate result sets, but CTEs and temp tables offer better reusability and maintainability.
3. Membership Filtering in the WHERE Clause
A WHERE clause subquery filters the outer query's records by evaluating membership against a dynamic list generated by an inner query.
- Mechanism: The inner query selects a list of keys meeting a specific condition from an auxiliary table (e.g.,
EmployeeIDwhereAge > 30), and the outer query'sWHERE EmployeeID IN (...)evaluates each outer row against this dynamic list. - Syntax Rule: When using the
INoperator with a subquery, the inner SELECT statement must return exactly one column; selecting multiple columns or usingSELECT *produces a syntax error. - JOIN vs Subquery Nuance: Use a WHERE subquery when you only need to filter rows based on another table without displaying any columns from that table; use a JOIN if you need to return columns from both tables in the final output.
-- Filter EmployeeSalary based on demographic criteria from another table
SELECT
EmployeeID,
JobTitle,
Salary
FROM EmployeeSalary
WHERE EmployeeID IN (
SELECT EmployeeID
FROM EmployeeDemographics
WHERE Age > 30
);
Key Takeaway: WHERE IN subqueries must project a single column and are ideal for existence filtering when secondary table columns are not needed in the SELECT list.
Topics Covered in Advanced SQL Tutorial | Subqueries
- Subquery Overview and Setup (0:00 - 0:45) — Defines subqueries and introduces the sample EmployeeSalary dataset.
- Subqueries in SELECT Clause (0:45 - 1:40) — Demonstrates calculating and appending overall average salary to row-level employee data.
- Window Function Comparison (1:40 - 2:30) — Compares SELECT subquery output with AVG() OVER() and explains why GROUP BY fails here.
- Subqueries in FROM Clause (2:30 - 3:55) — Builds a derived table subquery in FROM and compares it against CTEs and temp tables.
- Subqueries in WHERE Clause (3:55 - 5:30) — Filters salary records based on age criteria from EmployeeDemographics using WHERE IN.
SQL Cheat Sheet
-
SELECT col, (SELECT AGG(col) FROM tbl) AS alias FROM tbl— Appends an aggregate benchmark to each individual rowSELECT EmployeeID, (SELECT AVG(Salary) FROM EmployeeSalary) AS AvgSal FROM EmployeeSalary; -
SELECT a.col FROM (SELECT col FROM tbl) a— Queries against an inline derived result setSELECT a.EmployeeID FROM (SELECT EmployeeID, Salary FROM EmployeeSalary) a; -
WHERE col IN (SELECT col FROM tbl WHERE condition)— Filters outer rows by matching inner query resultsSELECT EmployeeID FROM EmployeeSalary WHERE EmployeeID IN (SELECT EmployeeID FROM EmployeeDemographics WHERE Age > 30); -
AVG(col) OVER()— Calculates overall aggregate across all rows without groupingSELECT EmployeeID, Salary, AVG(Salary) OVER() AS AllAvgSalary FROM EmployeeSalary;
Comparison Table
| Clause Location | Output Requirement | Primary Use Case |
|---|---|---|
| SELECT Subquery | Single scalar value | Benchmark metric alongside row data |
| FROM Subquery | Tabular dataset with alias | Intermediate transformation for outer query |
| WHERE Subquery (IN) | Single column list | Filtering records by secondary table |
Common Pitfalls
- Mistake: Returning multiple columns in a WHERE IN subquery. Avoid: Select only the single matching key column in the inner query.
- Mistake: Forgetting to alias a derived table inside the FROM clause. Avoid: Always assign an alias identifier immediately after the closing parenthesis.
- Mistake: Using GROUP BY to display row-level values alongside table aggregates. Avoid: Use a scalar subquery or an OVER() window function instead.
- Mistake: Using a subquery in WHERE when you need to display joined columns. Avoid: Switch to an explicit INNER or LEFT JOIN.
FAQs
- Can I use SELECT * inside a WHERE IN subquery? No, the WHERE IN operator expects a single column of values to match against the outer column. Returning multiple columns will throw a database syntax error.
- When should I prefer CTEs or temp tables over FROM subqueries? Prefer CTEs and temp tables when query logic is complex or reused across multiple steps, as FROM subqueries cannot be referenced multiple times and quickly become unreadable.
- How does a SELECT subquery differ from AVG() OVER()? Both return identical overall average results alongside row data, but window functions are native analytical constructs that are typically cleaner and more performant.