Xorthax has learned how to calculate values using window functions. But now he runs into a practical problem:
“How do I filter based on those results?”
That’s where CTEs come in.
A CTE (Common Table Expression) is a temporary result set that you can reference in a query.
It is not a window function, but often works alongside them.
WITH name AS (
SELECT ...
)
SELECT ...
FROM name;
Think of it as breaking your query into steps:
Window functions are calculated in the SELECT step.
But filtering usually happens earlier in the query.
That means you often can’t directly filter on a window function—you need a second step.
| CustomerID | OrderDate | Amount |
|---|---|---|
| A | 2024-01-01 | 100 |
| A | 2024-01-05 | 150 |
| B | 2024-01-02 | 200 |
| B | 2024-01-06 | 50 |
WITH numbered_orders AS (
SELECT CustomerID, OrderDate, Amount,
ROW_NUMBER() OVER (
PARTITION BY CustomerID
ORDER BY OrderDate
) AS row_num
FROM Orders
)
SELECT *
FROM numbered_orders
WHERE row_num = 1;
Result:
| CustomerID | OrderDate | Amount |
|---|---|---|
| A | 2024-01-01 | 100 |
| B | 2024-01-02 | 200 |
Each customer now has only their first order.
In the CTE:
ROW_NUMBER() assigns a number to each rowThen in the outer query:
row_num = 1This two-step process is very common when working with window functions.
⚠️ Why not just use WHERE?
Window functions are calculated after the WHERE clause.
So you usually can’t write something like:
WHERE ROW_NUMBER() = 1
Instead, you calculate the window function first (in a CTE), then filter in the outer query.
CTEs aren’t just for filtering.
They also help:
Some SQL engines support a QUALIFY clause.
This lets you filter directly on window functions without using a CTE.
SELECT CustomerID, OrderDate, Amount,
ROW_NUMBER() OVER (
PARTITION BY CustomerID
ORDER BY OrderDate
) AS row_num
FROM Orders
QUALIFY row_num = 1;
Even if your database supports QUALIFY, the CTE pattern is still useful and widely used.
For each customer, return only their first order based on orderdate using a CTE.
| Column | Type |
|---|---|
| orderid | bigint |
| customerid | bigint |
| orderdate | timestamp without time zone |
| totalamount | double precision |
| status | text |
| Column |
|---|
| customerid |
| orderid |
| orderdate |
| totalamount |
You’ve now learned the core building blocks of window functions.
Next, you’re ready to start applying them to real problems.