MySQL

A Complete Guide to the MySQL CTE Mechanism

intro

Let’s dig into the MySQL CTE world, exploring what common table expressions are, how they work, and how they can help you write more readable queries.

Tools used in the tutorial
Tool Description Link
Dbvisualizer DBVISUALIZER
TOP RATED DATABASE MANAGEMENT TOOL AND SQL CLIENT

Extracting necessary data from a MySQL database often requires running multiple queries. One approach is to execute several queries at the application level, which leads to significant overhead. Another option is to use subqueries. Unfortunately, these have notable limitations regarding reusability. The solution? The MySQL CTE mechanism can save your day!

In this article, you will explore the definition of CTEs, learn how to define them with the MySQL WITH clause, and see some real-world examples.

Let's dive in!

What Is a MySQL CTE?

In MySQL, a CTE—short for Common Table Expression—is a named temporary result set that exists within the scope of a single SELECT, INSERT, UPDATE, or DELETE statement.

The CTE mechanism is part of the ISO/ANSI SQL standard, and it was introduced to simplify complex queries by breaking them into more readable parts. CTEs also open the door to recursive queries in SQL.

While the definition between a CTE and a subquery may be similar, there is one major difference between the two. Unlike subqueries, the same CTE can be referenced multiple times within a query. This results in greater clarity and maintainability compared to SQL subqueries.

How to Define a CTE in MySQL Through the WITH Clause

In MySQL, CTEs are defined through the WITH clause using the following syntax:

Copy
        
1 WITH [RECURSIVE] 2 cte_name_1 [(col_name_a1 [, ..., col_name_z1])] AS (subquery_1) 3 [ ... ] 4 [, cte_name_n [(col_name_n1 [, ..., col_name_z1])] AS (subquery_n)] 5 sql_statement

Note that the same MySQL WITH clause can specify one or more CTEs. In particular, each CTE is defined through:

  • cte_name_X : The name associated with a single common table expression. It can be used as a table reference in the SQL statement following the WITH clause.
  • subquery_X: This is called the “subquery of the CTE” and is what produces the CTE result set. The parentheses following AS are required.

In the subquery definition, a CTE can refer to its own name. In this case, a MySQL CTE expression is considered recursive. If WITH contains at least one recursive CTE, the optional RECURSIVE keyword must be included. For more information, refer to the official documentation.

Now, see the WITH clause in action in the MySQL CTE example below:

Copy
        
1 WITH 2 cte_1 AS (SELECT column_1, column_2 FROM table_1), 3 cte_2 AS (SELECT column_3, column_4 FROM table_2) 4 SELECT column_2, column_4 5 FROM cte_1 6 JOIN cte_2 ON cte1.column_1 = cte2.column_3;

The WITH clause in the above query defines the CTEs cte_1 and cte_2. Then, the main SELECT statement after WITH references those CTEs to retrieve the desired data.

MySQL CTEs have a couple rules that need to be followed for our work with them not to become a headache. Many of you know that a CTE can reference another CTE, but there’s much more to it!

CTE Definition Rules

Here are some key rules to remember when defining a CTE in MySQL:

  • A CTE can reference other CTEs, allowing CTEs to be defined based on other CTEs.
  • The subquery of a CTE must be a SELECT statement or a union of multiple SELECT statements.
  • A CTE can refer to CTEs defined earlier in the same WITH clause, but not those defined later. This restriction prevents mutually-recursive CTEs where cte_1 references cte_2 and vice versa. One must refer to a later-defined CTE, which is not allowed.
  • A CTE can reference itself for recursion, forming a recursive CTE. The subquery of a recursive CTE always has two SELECTs, separated by UNION ALL or UNION [DISTINCT]. The first SELECT produces the initial rows for the CTE and does not refer to the CTE name. The second SELECT produces additional rows and recurses by referring to the CTE name in its FROM clause. Recursion ends when the second SELECT produces no new rows.
  • A CTE in a given query block can reference CTEs specified in query blocks at a more outer level, but not CTEs defined in query blocks at a more inner level.

There are two possible scenarios for determining the column names for a CTE MySQL expression:

  1. If a parenthesized list of column names follows the CTE name, those column names will become the column names of the CTE:
Copy
        
1 WITH cte (col_1, col_2) AS ( 2 SELECT col_a, col_b 3 FROM table_x 4 ) 5 SELECT col_1, col_2 6 FROM cte;

In this case, col_1 references col_a and col_2 references col_b. Note that the number of names in the list before AS must match the number of columns specified in subquery statement.

  1. If no list of column names is provided, the column names come from the SELECT statement inside the CTE:
Copy
        
1 WITH cte AS ( 2 SELECT col_a, col_b 3 FROM table_x 4 ) 5 SELECT col_a, col_b 6 FROM cte;

In the above example, the columns of the CTE are col_a and col_b.

WITH Clause Rules

A WITH clause is permitted in these contexts:

  • At the beginning of SELECT, UPDATE, and DELETE statements.
  • At the beginning of subqueries.
  • Immediately preceding the SELECT keyword in statements that allow a SELECT statement, such as INSERT ... WITH ... SELECT ..., CREATE TABLE ... WITH ... SELECT ..., and so on.

Other rules to keep in mind when dealing with a MySQL WITH clause are:

  • Only one WITH clause is allowed at the same level. In other words, you cannot define a CTE inside another CTE.
  • A statement can contain multiple WITH clauses, but only if they occur at different levels in the query.
  • A WITH clause can specify one or more common table expressions, but each CTE name must have a unique name in the clause.

MySQL CTE: Pros and Cons

Discover the benefits and drawbacks associated with the MySQL CTE mechanism.

Pros

  • Improved readability: CTEs break complex MySQL queries into manageable, readable parts, making SQL easier to understand and maintain.
  • Reusability: They can be referenced multiple times within the same query, eliminating the need to repeat the same subquery.
  • Recursion: Recursive CTEs enable you to handle hierarchical data structures, such as organizational charts or tree-like data.

Cons

  • Performance limitations: CTEs may not always perform as efficiently as temporary tables or indexed views, especially with large datasets.
  • No materialization: They are not materialized, meaning they are re-evaluated each time they are referenced, which can impact performance.

CTE MySQL Examples

Explore three examples to understand how MySQL CTEs can be beneficial in real-world scenarios.

Note: The following sample queries will be executed in DbVisualizer, the database client renowned for its high user satisfaction on the market. Since queries with CTEs can become long, we will present an indented version of them generated using DbVisualizer's SQL formatting capabilities.

The first two examples refer to the employees table below:

Exploring the data in the employees table in DbVisualizer
Exploring the data in the employees table in DbVisualizer

Time to see some CTE MySQL examples!

Example 1: Finding the Department with the Highest Average Salary

For the first example, assume you want to identify the department with the highest average salary. You can obtain that information using an elegant query with a MySQL WITH clause, as follows:

Copy
        
1 WITH 2 department_avg_salary AS 3 ( SELECT 4 department_id, 5 AVG(salary) AS avg_salary 6 FROM 7 employees 8 GROUP BY 9 department_id 10 ) 11 SELECT 12 D.name, 13 DAS.avg_salary 14 FROM 15 department_avg_salary DAS 16 JOIN 17 departments D 18 ON 19 D.id = DAS.department_id 20 ORDER BY 21 DAS.avg_salary DESC 22 LIMIT 1;

Here, the CTE department_avg_salary calculates the average salary for each department. The main query then displays the department with the highest average salary using an SQL LIMIT clause.

The result will be:

Executing the query in DbVisualizer. Finance is the department with the highest salary
Executing the query in DbVisualizer. Finance is the department with the highest salary

While you could achieve the same information with a complex SELECT statement, using a CTE greatly improves the readability of the query.

Example 2: Getting an Organization’s Hierarchy

Suppose you want to retrieve the organizational hierarchy under a specific employee. Below is how you can achieve that using a recursive CTE:

Copy
        
1 WITH 2 **RECURSIVE** employee_hierarchy AS 3 ( SELECT 4 id, 5 full_name, 6 manager_id 7 FROM 8 employees 9 WHERE 10 id = 4 11 12 UNION ALL 13 14 SELECT 15 E.id, 16 E.full_name, 17 E.manager_id 18 FROM 19 employees E 20 JOIN 21 employee_hierarchy EH 22 ON 23 EH.id = E.manager_id 24 ) 25 SELECT 26 * 27 FROM 28 employee_hierarchy;

In this example, the MySQL CTE employee_hierarchy selects the employee with ID 4. Then, it recursively joins with the employees table to retrieve employees managed by the current manager, continuing to traverse up the hierarchy until the entire sub-hierarchy is fetched. This is a common query pattern for traversing a tree structure in MySQL.

Execute it, and you will get the following result:

Note the tree-like result
Note the tree-like result

Note that this result cannot be achieved with a simple SELECT statement: you would likely need to use subqueries instead.

Example 3: Retrieving Product Sales in Key Regions

Now, you want to get the products only in the regions with the highest sales. Achieve that with a WITH clause defining two auxiliary CTEs as follows:

Copy
        
1 WITH 2 regional_sales AS 3 ( SELECT 4 region_id, 5 SUM(price) AS total_sales 6 FROM 7 orders 8 GROUP BY 9 region_id 10 ) 11 , 12 top_regions AS 13 ( SELECT 14 region_id 15 FROM 16 regional_sales 17 WHERE 18 total_sales > 19 ( SELECT 20 SUM(total_sales) / 21 ( SELECT 22 COUNT(*) 23 FROM 24 regional_sales) 25 FROM 26 regional_sales ) 27 ) 28 SELECT 29 R.name AS region, 30 P.name AS product, 31 SUM(O.quantity) AS product_units, 32 SUM(O.price) AS product_sales 33 FROM 34 orders O 35 JOIN 36 regions R 37 ON 38 R.id = O.region_id 39 JOIN 40 products P 41 ON 42 P.id = O.product_id 43 WHERE 44 O.region_id IN 45 ( SELECT 46 region_id 47 FROM 48 top_regions) 49 GROUP BY 50 O.region_id, 51 O.product_id;

Let's break down what the above query does:

  1. Stores the total sales for each region in the regional_sales CTE.
  2. Uses the output of the previous CTE in a new top_regions CTE to identify the regions with sales exceeding the regional average total sales.
  3. Retrieves the product names, total sales quantities, and prices for products sold in those top-performing regions.

The result of the query will be:

North America is the only region with sales above the average
North America is the only region with sales above the average

North America is the only region with sales above the average

Wonderful! You are now a MySQL CTE master.

Conclusion

In this article, you saw what a MySQL CTE is and how it functions. You now know that CTEs serve as a powerful tool for enhancing code organization and promoting code reuse. Specifically, you explored the syntax of the MySQL WITH clause to define CTEs.

The main significant drawback of this feature is that it may lead to non-optimal, slow queries. This is where DbVisualizer comes in!

Beyond offering the essential features of a database client and supporting numerous DBMSs, DbVisualizer provides advanced query optimization capabilities that can elevate your MySQL CTEs to the next level. Download DbVisualizer for free now!

FAQ

Is it possible to define a CTE in MySQL 5.7?

While you can make use of subqueries, you cannot define a CTE in MySQL 5.7. The reason is that the WITH clause was only introduced in MySQL 8.0. Thus, you must upgrade to MySQL 8.0 or later to take advantage of the CTE MySQL mechanism.

What is the difference between common table expressions and recursive common table expressions?

Recursive common table expressions are a specific type of CTE that references themselves. In other terms, they enable the execution of recursive queries in SQL. A recursive CTE in MySQL consists of two SELECT statements in a UNION or UNION ALL clause. The first represents the anchor member for the base case, while the second is the recursive member that iteratively processes data.

Does MySQL support recursive CTEs?

Yes, MySQL supports recursive CTEs. This functionality is particularly useful for traversing tree structures, such as organizational charts.

What is the difference between a derived table and a CTE in MySQL?

A derived table and a CTE in MySQL both serve to structure subqueries, but they differ in certain ways. A MySQL derived table is a subquery used directly in the FROM clause and is only usable within that query:

Copy
        
1 SELECT ... FROM (subquery) [AS] table_name ...

Instead, a CTE is defined with the WITH clause and can be referenced multiple times within the same query. CTEs also offer recursive capabilities.

Is the MySQL WITH clause also available in other DBMS technologies?

Yes, WITH is part of the standard ISO/ANSI SQL standard. That means it is also available with the same syntax in other databases and SQL dialects. See it in action in Postgres in our guide on PostgreSQL CTE!

Dbvis download link img
About the author
Antonello Zanini

Antonello is a software engineer, and often refers to himself as a technology bishop. His mission is to spread knowledge through writing.

The Table Icon
Sign up to receive The Table's roundup
More from the table
Title Author Tags Length Published
title

MySQL NOW Function: Get the Current Date and Time

author Antonello Zanini tags DATETIME MySQL 5 min 2025-02-27
title

SQL Operators: A Complete Walkthrough

author Lukas Vileikis tags DbVisualizer MySQL SQL 4 min 2025-02-25
title

How to Truncate Many Tables In SQL at Once

author Antonello Zanini tags MySQL ORACLE POSTGRESQL SQL SQL SERVER 6 min 2025-02-24
title

Clustered vs Non-Clustered Index: Complete SQL Guide

author Antonello Zanini tags MySQL ORACLE POSTGRESQL SQL SQL SERVER 8 min 2025-02-13
title

Queries and Subqueries in SQL: The Definitive Guide

author Lukas Vileikis tags DbVisualizer MySQL POSTGRESQL SQL SQL SERVER 7 min 2025-02-12
title

How to Optimize a Clustered Index Scan in SQL

author Antonello Zanini tags MySQL SQL SQL SERVER 7 min 2025-02-11
title

SQL Derived Table: Everything You Need to Know

author Antonello Zanini tags MySQL POSTGRESQL SQL SQL SERVER 9 min 2025-02-06
title

Understanding MVCC in MySQL

author Lukas Vileikis tags DbVisualizer MySQL SQL 7 min 2025-02-05
title

MySQL UPDATE JOIN Queries: A Complete Guide

author Antonello Zanini tags MySQL 8 min 2025-02-04
title

How to Protect MySQL With Fail2Ban

author Antonello Zanini tags MySQL 12 min 2025-01-30

The content provided on dbvis.com/thetable, including but not limited to code and examples, is intended for educational and informational purposes only. We do not make any warranties or representations of any kind. Read more here.