Where to Use Sub-Queries

A sub-query can stand in for a value, a list or a table. Use it in WHERE with =, IN, NOT IN or EXISTS, as a column in the SELECT, or as a table in the FROM.

Journeyman · T-SQL · SQL · Updated

After this lesson you can

  • Use a sub-query in WHERE as one value, with IN or NOT IN as a list, or with EXISTS
  • Use a sub-query as a column in the SELECT, or as a table in the FROM

Key takeaways

  • A sub-query compared with =, < or > must return exactly one value; if it returns two or more, SQL Server stops with an error.
  • A sub-query that returns a column of values works with IN or NOT IN.
  • If a NOT IN sub-query's column holds a NULL, no rows come back, so add IS NOT NULL to the sub-query when the column allows NULL.
  • EXISTS asks whether a correlated sub-query finds any row at all, and stops at the first match.
  • A one-value sub-query can be a column in the SELECT, and a sub-query in the FROM works as a table once it has an alias.

A sub-query can go wherever a query needs a value, a list of values, or a table. What it returns decides where it fits.

One value, in WHERE

A sub-query that returns exactly one value can be compared with =, < or >. The orders of one customer, found by company name:

SELECT OrderId, OrderDate
FROM Orders
WHERE CustomerId = (
    SELECT CustomerId
    FROM Customers
    WHERE CompanyName = 'Alfreds Futterkiste'
);

(6 rows.) If the sub-query returned two or more values, SQL Server would stop with an error: = compares with one value only.

A list, with IN

A sub-query that returns a column of values works with IN. The orders of every customer in Canada:

SELECT COUNT(*) AS Orders
FROM Orders
WHERE CustomerId IN (
    SELECT CustomerId
    FROM Customers
    WHERE Country = 'Canada'
);
Orders
30

NOT IN finds the opposite: customers who have never ordered.

SELECT CompanyName
FROM Customers
WHERE CustomerId NOT IN (
    SELECT CustomerId
    FROM Orders
)
ORDER BY CompanyName;
CompanyName
FISSA Fabrica Inter. Salchichas S.A.
Paris spécialités

(Careful: if the sub-query's column holds a NULL, NOT IN returns no rows at all. Add WHERE CustomerId IS NOT NULL to the sub-query when the column allows NULL.)

Is there one? EXISTS

EXISTS asks whether a correlated sub-query finds any row at all. It stops at the first match, which can make it faster than IN on big tables:

SELECT COUNT(*) AS Orders
FROM Orders AS o
WHERE EXISTS (
    SELECT *
    FROM Customers AS c
    WHERE c.Country = 'Canada'
        AND c.CustomerId = o.CustomerId
);
Orders
30

The same 30 orders. NOT EXISTS asks the opposite.

A column, in the SELECT

A sub-query that returns one value can be a column. Each expensive product, beside the average of them all:

SELECT ProductName
    , UnitPrice
    , (SELECT AVG(UnitPrice) FROM Products) AS AveragePrice
FROM Products
WHERE UnitPrice > (SELECT AVG(UnitPrice) * 3 FROM Products)
ORDER BY UnitPrice DESC;
ProductName UnitPrice AveragePrice
Côte de Blaye 263.50 28.8663
Thüringer Rostbratwurst 123.79 28.8663
Mishi Kobe Niku 97.00 28.8663

A table, in the FROM

A sub-query can even be a table: give it an alias, and join to it. The five customers with the most orders by value, from a sub-query that totals each customer's orders:

SELECT TOP (5) c.CompanyName, t.OrderTotal
FROM Customers AS c
INNER JOIN (
    SELECT o.CustomerId, SUM(od.UnitPrice * od.Quantity) AS OrderTotal
    FROM Orders AS o
    INNER JOIN OrderDetails AS od
        ON o.OrderId = od.OrderId
    GROUP BY o.CustomerId
) AS t
    ON c.CustomerId = t.CustomerId
ORDER BY t.OrderTotal DESC;
CompanyName OrderTotal
QUICK-Stop 117483.39
Save-a-lot Markets 115673.39
Ernst Handel 113236.68
Hungry Owl All-Night Grocers 57317.39
Rattlesnake Canyon Grocery 52245.90

Building a big query from small, tested pieces like this keeps it readable.

The sub-query returns Use it
One value With =, <, > in WHERE, or as a column in the SELECT
A column of values With IN or NOT IN in WHERE
Whether any row matches With EXISTS or NOT EXISTS
A whole result As a table in the FROM, with an alias

Check yourself

2 questions to try on the lesson page.

So what

A sub-query is a question inside a question. Match what it returns (one value, a list, a yes or no, a table) to where you put it, and complex questions break into small steps you can test one at a time.

Key terms

Cheat sheets