Skip to content
wannacode

A report in steps: WITH

In short: WITH (a common table expression, CTE) names each step of a query so it reads top to bottom and one step can be used more than once.

Problem

A report from the inside out

The owner wants a list of the customers who have spent over 100 € (without the cancelled orders), with their number of orders. With subqueries in FROM you can already do it:

SQLdatabase: shop

The result is correct. But try to say what the query does: you have to start from the innermost brackets and work outwards. In a month you will not recognise it yourself.

The idea

WITH

WITH gives a step a name, before the main query:

SQLdatabase: shop

order_totals is the value of every order that is not cancelled: 53 rows. After WITH, the name is used like a table. Such a named step is called a CTE (common table expression).

There can be several steps, separated by commas. Each one sees the steps before it:

SQLdatabase: shop

The same report as at the start, but it reads from the top down: the value of every order, then a total for every customer, then only those over 100 €. Seven customers come out, led by Maria (375.70 € in 12 orders).

Try it

Orders above average

Show the number and value of the orders (without the cancelled ones) whose value is above the average order value. The field already has the order_totals step.

SQLdatabase: shop

See it

How long the name lives

A name from WITH holds only for the query it is part of. Here there are two queries: the first counts the orders, the second asks for order_totals again:

SQLdatabase: shop
1. Why does the second query not find order_totals?

WITH does not create a table in the database. If you need a step in many reports, there are views (VIEW) for that, which we will meet in the module on the schema.

The idea

When WITH, when a subquery

The result is the same. The difference is for the person reading:

  • a short condition such as price > (SELECT AVG(price) FROM products) is clearest in place, as a subquery;
  • a report of several steps is clearer with WITH: every step has a name that says what it is;
  • a step you need twice is written once.

It also helps when hunting for a mistake: replace the last SELECT with SELECT * FROM order_totals and you see what each step gives.

More precisely, if you are curious

Whether the database runs every step separately or merges them into one query is up to the database. In PostgreSQL before version 12 every WITH was run separately, and from version 12 on the database can merge it into the main query. If a WITH query is ever slower than the same one with subqueries, the reason is often there.

Exercise

Three tasks

1. The sales team wants the revenue by city, to decide where to advertise.

Show the city and the revenue from the orders of its customers (without the cancelled ones), rounded to two decimal places. Start from the order_totals step.

SQLdatabase: shop

2. Which categories have brought in over 200 € (without the cancelled orders)?

Show the category name and its revenue (the sum of quantity times unit price), only for the categories with revenue over 200 €. Make one step with the revenue by category_id and then join it to categories.

SQLdatabase: shop

3. In the module on joins you made the grid of every city and every subcategory with CROSS JOIN. Now fill it in: the revenue for every pair, with 0 where there are no sales.

Show the city, the subcategory name (column category) and the revenue (column revenue) from the customers of that city in that subcategory (without the cancelled orders), rounded to two decimal places. For pairs without sales the revenue is 0. The field already has the grid.

SQLdatabase: shop

Test

Check yourself

1. How do you write two steps in one WITH?
2. What does WITH order_totals AS (…) do to the database?
3. What does WITH give you that a subquery in FROM does not?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·