29th Sep 2022 7 minutes read How the WITH Clause Works in SQL Ignacio L. Bisso Common Table Expressions Table of Contents The WITH Clause in SQL Using Multiple CTEs in One Query Expand Your SQL Limits Using the WITH clause Do you want to master complex SQL queries? Learn how to use the powerful WITH clause! In SQL, there are simple queries and complex queries. A simple query can take data from a table, apply a filter, and return the data as it is in the source table. A complex query may require complex transformations to the source data or may need to read and join data from multiple tables or views. It may even require using subqueries. When we are faced with a complex query, using the "divide and conquer" technique can be a good option. The WITH clause in SQL does exactly that: it allows us to divide our query into stages, where each stage consists of creating a kind of table where we approximate the format of the final solution we are looking for. In this article, we will cover what the SQL WITH clause is and how to use it to make complex queries more readable. The WITH Clause in SQL The example queries in this article will be based on a table called international_operations. It stores all the export and import operations done by the companies in a country. Here’s a sample of its data: operation typeoperation dateproduct familyproduct nameqtyunitsamountlocal companyexternal companyexternal country export6/5/2022grainssoy2500tons1500000AgricAFoodABChina import5/5/2021vehiclescar150units1450000SupCarJeanCarFrance export5/5/2021vehiclestruck120units6000000ManAATruckAAUSA export5/5/2021vehiclescar230units4000000ManAACarsAAFrance import5/5/2021carpartswheels2000units100000ManAAChiWheChina import6/5/2022carpartsengines530units2500000ManAAChiEngChina export8/12/2021grainssoy3500tons2500000AgricAPastaA1Italy export8/24/2020grainssoy3000tons2100000AgricAPastaA1Italy import3/1/2022vehiclestrucks20units670000AgricAAgrtruckUSA Before we dig into the SQL WITH clause, I would like to suggest our interactive Recursive Queries course. It has 100+ exercises demonstrating (among other things) how to use the WITH clause. It also covers creating recursive queries, which are very useful in processing hierarchical data structures like trees or graphs. To start, let’s show a simple example SQL query using the WITH clause. (WITH clauses are also called Common Table Expressions, or CTEs.) Suppose we want to obtain a list of the countries where we exported more than $1,000,000.00 in 2021. The query is: WITH total_exports_by_country_2021 AS ( SELECT external_country, SUM(amount) AS total FROM international_operations WHERE operation_date BETWEEN '1/1/2021' AND '31/1/2021' AND operation_type = 'export' GROUP BY external_country ) SELECT external_country, total FROM total_exports_by_country_2021 WHERE total > 1000000; This is actually made up of two queries. The first query (the CTE, in red) immediately follows the WITH clause; it is executed and the results are put into a virtual table called total_exports_by_country_2021. This is not a regular database table, just a temporary data set that will only be available during query execution. Once the query completes, this data set will be discarded. An important point to mention about CTEs is that once they are defined, they can be referenced in the rest of the SELECT statement like any other database table. In our example, the blue query makes up the second part of the larger query. The result of the query is: External_countryTotal China1500000.00 Italy2500000.00 USA6000000.00 Just like any other table, you can use CTEs in a subquery, a JOIN, or in the FROM clause. In other words, you can place the name of the CTE anywhere where you can place the name of a regular database table. To better understand CTE usage, check out When Should I Use a Common Table Expression (CTE)? and What Is the WITH Clause in SQL?. Using Multiple CTEs in One Query CTEs make complex queries more readable. A widely used approach with long queries is to break them into several simple queries that each use a CTE. The CTE names act as natural language descriptors for the data that each one contains. For example, in our previous query, the name of the CTE is total_exports_by_country_2021, which clearly indicates the data contained. In the next example query, we will create a report showing the names of local import-export companies and their balance (the difference between total yearly export and import amounts) for 2021. Here’s the query: WITH total_exports_by_company_2021 AS ( -- CTE #1 SELECT local_company, SUM(amount) AS total FROM international_operations WHERE operation_date BETWEEN '1/1/2021' AND '31/12/2021' AND operation_type = 'export' GROUP BY local_company ), total_imports_by_company_2021 AS ( -- CTE #2 SELECT local_company, SUM(amount) AS total FROM international_operations WHERE operation_date BETWEEN '1/1/2021' AND '31/12/2021' AND operation_type = 'import' GROUP BY local_company ) SELECT exp.local_company, exp.total - imp.total AS balance FROM total_exports_by_company_2021 exp JOIN total_imports_by_company_2021 imp ON imp.local_company = exp.local_company The above query has two CTEs called total_exports_by_company_2021 and total_imports_by_company_2021. You can see only one SQL WITH clause because the syntax of the WITH clause allows us to include many CTEs subqueries. Finally there is the main query, which joins both CTEs and calculates the difference. The result is: Local_companyBalance AgricA3330000.00 ManAA5900000.0 The next example will also use two CTEs, but with a difference: the second CTE will reference the first CTE. We will create a kind of chain of linked queries, where each query will refer to the previous one. Let’s suppose we want to obtain all the export operations done in 2020 and 2021 by the local companies which experienced an increase in exports in 2021 (vs. 2020). For these operations we want to know the operation_date, local_company, external_company, product_name and amount. This query can be solved in several different ways; let’s see how to solve it using CTEs: WITH total_exports_by_company_year AS ( SELECT local_company AS company, EXTRACT(YEAR from operation_date) AS year, SUM(amount) AS total_exported FROM international_operations WHERE operation_type = 'export' GROUP BY local_company, EXTRACT(YEAR from operation_date) ), companies_raising_exports_in_2021 AS ( SELECT e20.company FROM total_exports_by_company_year e20 JOIN total_exports_by_company_year e21 ON e21.year = 2021 AND e20.company = e21.company AND e20.total_exported < e21.total_exported WHERE e20.year = 2020 ) SELECT io.operation_date, io.product_name, io.local_company , io.external_company, io.amount FROM companies_raising_exports_in_2021 r21 JOIN international_operations io ON io.local_company = r21.company AND EXTRACT(YEAR FROM io.operation_date) IN (2020, 2021) The name of the first CTE is total_exports_by_company_year, which anticipates that this CTE will have the names of the companies, their total export for each year, and the year. The second CTE is called companies_raising_exports_in_2021. It will have the names of the companies that exported more in 2021 than in 2020. If we take a look at this CTE, we will notice that the FROM refers to the CTE total_exports_by_company_year. Moreover, it uses a JOIN, thus referencing the CTE twice (once for the 2020 records and again for the 2021 records). Finally, we have the main query. It refers to the CTE companies_raising_exports_in_2021, since this CTE has the companies that we are interested in showing in the report. The query joins this CTE with the international_operations table, since this table has more data that we want to show. Here is the result: operation_Balanceproduct familyproduct nameqty dateproductlocal_soy2500 companyexternal_vehiclescar150 companyamountvehiclestruck120 5/6/2021soyAgricAFoodAB1500000.00 8/12/2021soyAgricAPastaA12500000.00 8/24/2020soyAgricAPastaA12100000.00 3/1/2021trucksAgricAAgrTruck670000.00 5/52021trucksManAATruckAA6000000.00 5/5/2020carsManAACarsAA4000000.00 5/5/2021wheelsManAAChiWhe100000.00 Want more information? The articles SQL CTEs Explained with Examples and Subquery vs.CTE: A SQL Primer will give you more details. We are not going to cover recursive queries in this article, but they are another thing you can do with the WITH clause in SQL. The article Do it in SQL: Recursive SQL Tree Traversal clearly explains it, using several query examples. Expand Your SQL Limits Using the WITH clause In this article, we introduced the SQL WITH clause (aka CTE or common table expression) by showing several SQL examples. Personally, I see WITH as a wildcard clause in SQL. When you have to solve a complex request in a single SQL query, you can think of an intermediate data set between your data source and the result you need to obtain. Then you can create this intermediate data set using the WITH clause. From this intermediate point, you can construct the final query. If you need many intermediate data sets, you can create as many CTEs as necessary. Finally, I want to remind you about our Recursive Queries course, where you can learn by example and exercise how to create recursive subqueries. Increase your SQL skills, increase your assets! Tags: Common Table Expressions