Kodokon kodokon.com

Subqueries: WHERE, FROM, IN and EXISTS

Nest one query inside another to compare against an aggregate, filter on a set or test for existence.

9 min · 3 questions

Open this lesson in Kodokon

A subquery is a query placed in parentheses inside another one. In a single statement it answers questions like "the orders above the average" or "the customers with at least one order". Run this script in sqliteonline.com or sqlite3 before you begin.

SQL
CREATE TABLE customers (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  amount REAL NOT NULL
);

INSERT INTO customers (id, name) VALUES
  (1, 'Alice'), (2, 'Karim'),
  (3, 'Mina'), (4, 'Paulo');

INSERT INTO orders (id, customer_id, amount)
VALUES
  (1, 1, 49.90), (2, 1, 15.00),
  (3, 2, 120.50), (4, 2, 80.00),
  (5, 3, 9.90), (6, 1, 200.00);
Dataset: four customers, six orders.

Placed in WHERE, a scalar subquery must return a single value: one row, one column. The engine computes it first, then uses it like any other constant. You cannot do this with a plain WHERE amount > AVG(amount), which is forbidden in SQL.

SQL
SELECT id, customer_id, amount
FROM orders
WHERE amount > (
  SELECT AVG(amount) FROM orders
);
The orders above the average basket (79.22).

IN compares a column against the set of values returned by the subquery. EXISTS tests whether the correlated subquery (it references c.id, a column of the outer query) returns at least one row: the engine stops as soon as the first match is found.

SQL
SELECT name FROM customers
WHERE id IN (
  SELECT customer_id FROM orders
  WHERE amount > 100
);

SELECT c.name FROM customers AS c
WHERE EXISTS (
  SELECT 1 FROM orders AS o
  WHERE o.customer_id = c.id
);
IN: the big spenders. EXISTS: the active customers.

In FROM, a subquery behaves like a temporary table, called a derived table, and must be given an alias. It is the classic tool for aggregating in two steps: first a total per customer, then the average of those totals.

SQL
SELECT ROUND(AVG(t.total), 2) AS avg_basket
FROM (
  SELECT customer_id, SUM(amount) AS total
  FROM orders
  GROUP BY customer_id
) AS t;
Average total basket per active customer: 158.43.

Knowledge check

Make sure you remember the key points of this lesson.

  1. How many values must a subquery used in amount > (...) return?
    • Exactly one
    • One per row of the outer table
    • As many as it likes
  2. What is the main advantage of EXISTS over IN?
    • It can return several columns
    • It stops at the first row found, without materialising the whole set
    • It works without a subquery
  3. What must a subquery placed in FROM always carry to be portable everywhere?
    • An alias, such as AS t
    • An ORDER BY clause
    • An internal semicolon