Skip to main content
CodeOath
← All posts

SQL90 min total · 18 parts

SQL Fundamentals: Joins, NULL, Aggregate Functions, and Subqueries

Part 11 of 18 · ~2 min

Subqueries: Scalar, Column, and Table

Slide three is top earners, and building it means nesting one SELECT statement inside another — a subquery — in three genuinely different shapes. Where the SQL parser lets you drop a subquery in depends entirely on how much the inner query hands back: a single number can go almost anywhere, a whole column of them only where a list makes sense, and a full result set only somewhere that's willing to treat it as a table.

-- Shape 1, a single number: drops in anywhere SQL expects one value
SELECT name, salary FROM Employees
WHERE salary > (SELECT AVG(salary) FROM Employees);
-- Bob (60000), Carol (70000) — everyone clearing the company-wide average; Dave's NULL never entered that average

-- Shape 2, a column of values: pairs naturally with IN / ANY / ALL
SELECT dept FROM Departments
WHERE dept IN (SELECT dept FROM Employees);
-- Sales, IT, HR — every department with at least one person in it; Finance doesn't make the list

-- Shape 3, an entire result set: stand it up as a table and query it
SELECT sub.dept, sub.avg_salary
FROM (
  SELECT dept, AVG(salary) AS avg_salary
  FROM Employees
  GROUP BY dept
) AS sub
WHERE sub.avg_salary >= 55000;

Shape three — a subquery dropped straight into FROM, often called a derived table — comes with one non-negotiable rule attached: it needs a name (sub, in the query above). This isn't a matter of taste. Once that subquery is sitting inside FROM, the outer query has to be able to say sub.dept and sub.avg_salary, and there's no way to write .dept after a bare, unnamed parenthesized query — the alias is the only handle the rest of the statement has to grab onto.

One thing worth flagging before you go looking for it: standard SQL also defines ANY and ALL as quantifiers you can drop in front of a column subquery — salary > ALL (...), salary = ANY (...), that family. Postgres, SQL Server, and MySQL all support them. SQLite does not — paste salary > ALL (SELECT ...) into the code lab and you'll get a flat syntax error, not a wrong answer. The workaround is the same wherever you'd reach for one: = ANY (...) is just IN (...) under a different name, and > ALL (...) is the same question as > (SELECT MAX(...) ...) — compare against the aggregate directly instead of quantifying the comparison.