How to replace subqueries containing aggregate functions in PostgreSQL with Windows functions?

PostgreSQL Subqueries:

Subqueries with aggregate functions are common in PostgreSQL, especially when summarizing or filtering grouped data. While effective, these subqueries can become performance bottlenecks as data volumes grow. Fortunately, PostgreSQL offers a powerful alternative—window functions. By replacing subqueries with window functions, you can achieve the same results more efficiently and with greater clarity. In this blog, we’ll walk through a step-by-step process to transform aggregate subqueries into window functions using CTEs, partitioning, and optimized SQL patterns that improve both performance and readability.

Step 1: Identify the Subquery with Aggregate Function

Begin by identifying the subquery that contains the aggregate function. Analyze its role in the overall query and take note of the involved table(s) and the grouping criteria it uses.

Step 2: Convert the Subquery into a CTE (Common Table Expression)

Next, transform the subquery into a CTE. Using a CTE allows you to apply window functions while keeping the query more modular and readable.

 WITH subquery_cte AS ( SELECT category, amount, SUM(amount) OVER (PARTITION BY category) AS total_sales FROM sales ) 

Step 3: Define the Window Function

Then, select a window function that corresponds to the aggregate function in your original subquery. Common options include SUM, COUNT, AVG, MIN, and MAX.

In this case, we use the SUM() window function to replicate the behavior of the aggregate subquery.

Step 4: Specify the Window Partition and Order

After selecting the window function, define the PARTITION BY clause to group the data appropriately—in this case, by the category column.

Since we want a total per category and not a running total, we don’t need to specify an ORDER BY clause.

Step 5: Integrate the Window Function in the Main Query

Finally, join the CTE (which replaces the subquery) with the main query using appropriate join conditions based on the tables involved.

Original Query with Subquery:

 SELECT a.category, (SELECT SUM(b.amount) FROM sales b WHERE b.category = a.category) AS total_sales FROM categories a; 

Optimized Query Using Window Function:

SELECT a.category, s.total_sales FROM categories a JOIN subquery_cte s ON s.category = a.category; 

By following these steps, you can effectively replace subqueries with aggregate functions using window functions in PostgreSQL. This method not only improves query performance but also enhances query readability and maintainability.

About Shiv Iyer 500 Articles
Open Source Database Systems Engineer with a deep understanding of Optimizer Internals, Performance Engineering, Scalability and Data SRE. Shiv currently is the Founder, Investor, Board Member and CEO of multiple Database Systems Infrastructure Operations companies in the Transaction Processing Computing and ColumnStores ecosystem. He is also a frequent speaker in open source software conferences globally.