Intermediate: combining and changing data · Subqueries, CTEs and set operations · topic 26 of 62 · 30 min
Scalar subqueries
Explanation
Why a query inside a query?
Some questions have two steps. "Who earns more than the company average?" needs the average first, and then the comparison. You could run two queries and type the first answer into the second, but the number goes stale the moment a salary changes. A subquery lets one query do both steps, always with fresh data.
A subquery (also called an inner query) is a complete SELECT written inside brackets within another statement, the outer query. A scalar subquery is one that returns a single value: one row with one column. "Scalar" just means "a single value", as opposed to a list or a table.
In WHERE: compare with an aggregate
Module 4 showed that WHERE cannot contain AVG(salary) directly, because WHERE works on single rows before any grouping. A subquery solves that: the inner query computes the average over the whole table, and the outer WHERE compares each row with that one number.
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- Meera Iyer | 250000
-- Rahul Sharma | 120000
-- Pooja Gupta | 110000
-- Farhan Ali | 105000SELECT name, price
FROM products
WHERE price = (SELECT MAX(price) FROM products);
-- Running Shoes | 2499In SELECT: a value on every row
A scalar subquery can also be a column. It is computed for the result and repeated on each row, which is handy for comparisons with a total or an average.
SELECT name, price,
ROUND(price - (SELECT AVG(price) FROM products), 2) AS diff
FROM products;
-- Basmati Rice 5kg | 650 | -525.75
-- Running Shoes | 2499 | 1323.25
-- ... (the average price is 1175.75)SELECT name, price,
ROUND(100.0 * price / (SELECT SUM(price) FROM products), 1) AS pct
FROM products;
-- Running Shoes | 2499 | 26.6 <- 26.6% of the summed list pricesWhat if the subquery returns zero or many rows?
| Subquery returns | Result |
|---|---|
| One row, one column | That value: the normal case |
| No rows | NULL. Comparing with NULL is unknown, so a WHERE using it keeps no rows |
| Many rows | PostgreSQL, MySQL and SQL Server raise an error ("more than one row returned by a subquery"); SQLite quietly uses the first row |
| Many columns | An error everywhere: a scalar has one column |
Common mistakes
- Writing WHERE salary > AVG(salary). Aggregates are not allowed in WHERE; wrap the average in a subquery.
- Forgetting the brackets around the subquery, which is a syntax error.
- A "scalar" subquery that returns several rows, such as (SELECT price FROM products WHERE category = 'Grocery'). Use = only with one value; use IN (next topic) for a list.
- Using LIMIT 1 to find "the maximum" and missing ties. WHERE x = (SELECT MAX(x) ...) returns every tied row.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Scalar subqueries · 1/6
Some questions need two steps: find the average, then compare. A subquery does both in one query.
Prefer a video by a teacher? Find free videos on “sql scalar subquery where select above average tutorial” on YouTube ↗
In real life: Flagging unusual transactions
A simple first check in a bank or payment app is to flag payments much larger than usual, for example more than one and a half times the average amount. The average is a scalar subquery, so the rule keeps working as new payments arrive. Real fraud systems use many more signals, but this "compare with an aggregate" pattern is the starting point of many reports.
SELECT order_id, product_id, qty * price AS amount
FROM order_items
WHERE qty * price > 1.5 * (SELECT AVG(qty * price) FROM order_items);Practice (0/4 done)
Which employees earn more than the company average salary? Return name and salary. Use a subquery for the average.
Return the name and price of the most expensive product, using a subquery with MAX (so that ties would all be returned).
For every product, return name, price and diff: the price minus the average price of all products, rounded to 2 decimal places.
How far behind the top scorer is each student? For students who have marks, return name, marks and behind (the highest marks in the class minus the student's marks).
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- IN, EXISTS and NOT EXISTSSubqueries, CTEs and set operations · same module
- Correlated subqueries and derived tablesSubqueries, CTEs and set operations · same module
- Common table expressions (WITH)Subqueries, CTEs and set operations · same module
- Subqueries, CTEs and window functionsBackend Development · SQL in depth
- Counting and totals: aggregate functionsBackend Development · Databases and SQL basics
- UPDATE and DELETE safelyChanging data, transactions and ACID
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Why does WHERE salary > AVG(salary) fail, while WHERE salary > (SELECT AVG(salary) FROM employees) works?
Answer: WHERE works row by row and cannot use aggregates; the subquery computes the average separately as one value
The subquery is a complete query that returns one number.
A scalar subquery returns no rows. What value does it give?
Answer: NULL
An empty scalar subquery gives NULL.
Two products share the highest price. Which query returns both?
Answer: WHERE price = (SELECT MAX(price) FROM products)
Every row equal to the maximum passes the WHERE.
In PostgreSQL, a subquery used with = returns 3 rows. What happens?
Answer: An error: more than one row returned by a subquery
PostgreSQL, MySQL and SQL Server raise an error; SQLite quietly uses the first row.
What does "scalar" mean in "scalar subquery"?
Answer: It returns a single value: one row and one column
A scalar is one value, so it fits anywhere a single value fits.
Inside WHERE salary > (SELECT AVG(salary) FROM employees WHERE dept_id = dept_id), what does dept_id = dept_id compare?Related
Answer: The inner row with itself, so it is always true (except for NULLs)
Unqualified names refer to the nearest table. Use aliases: e2.dept_id = e.dept_id.
What does WHERE price > (SELECT AVG(price) FROM products) do?Related
Answer: Keeps products whose price is above the overall average
The subquery returns one number, the average; the outer WHERE compares each price with it.
The subquery SELECT dept_id FROM employees returns 1, 2, 3, 4 and one NULL. How many rows does WHERE id NOT IN (that subquery) return from departments (ids 1 to 5)?Related
Answer: 0
id <> NULL is unknown, so the NOT IN is never true for any row.
No product is in the Toys category. What does SELECT SUM(price) FROM products WHERE category = 'Toys' return?Related
Answer: NULL
SUM over no rows is NULL (COUNT would be 0). Use COALESCE(SUM(price), 0) to show 0.
From Aggregate functions
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 5 more topics in “Subqueries, CTEs and set operations” before its exam: IN, EXISTS and NOT EXISTS, Correlated subqueries and derived tables, Common table expressions (WITH), Recursive CTEs, UNION, INTERSECT and EXCEPT.
- Module 6 exam: subqueries, CTEs and set operations · 14 questions · 25 min, without help. 6 topics of this module still to finish first.
- Then the next module, Window functions. 62 topics left in the course before the final exam.