MySQL
POSTGRESQL
SQL
SQL SERVER

How to Use a Recursive CTE in SQL

intro

Let's find out everything you need to know about the recursive CTE mechanism to master recursion in SQL statements.

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

Most developers are familiar with recursion—a programming technique where a function calls itself to solve a problem through smaller, repeated steps. This powerful strategy enables complex operations by breaking them down into simpler, manageable parts. A similar mechanism is available through a recursive CTE in SQL, too.

In this article, you will see what recursion-based CTEs are, how to use them in MySQL, PostgreSQL, and SQL Server, and when they are useful through practical examples.

Let’s dive in!

What Is a Recursive CTE?

A recursive CTE (Common Table Expression) is a special type of CTE that references itself, opening the door to recursive queries in SQL. In most DBMSs, that is the only supported mechanism for implementing recursion.

Recursive CTEs come in handy in all scenarios where recursion is useful, such as working with hierarchical, tree-structured, or self-referential data.

Recursive CTE SQL Syntax

An SQL recursive CTE must have two parts:

  1. Anchor member: The initial SELECT query that produces the initial row or rows for the CTE. It defines the base result set and serves as the starting point of the recursion.
  2. Recursive member: Another SELECT query that references the CTE itself in its FROM clause, allowing it to repeatedly retrieve and combine results. The recursion ends when this part produces no new rows.

In simpler terms, a recursion-based CTE consists of a nonrecursive SELECT part followed by a recursive SELECT part. These two parts are usually connected by an SQL UNION or UNION ALL operator.

The SQL standard enforcers these constraints on a CTE recursive query:

  • The recursive SELECT part must not contain aggregate functions (e.g. functions like SUM(), AVG(), or COUNT(), window functions, GROUP BY, ORDER BY, and DISTINCT.)
  • The number of columns returned by anchor and recursive members must match.
  • Each column in the recursive member must have the same data type as the corresponding column in the anchor member.
  • The recursive SELECT part must reference the CTE exactly once, and only within its FROM clause—not within any SQL subquery. It can also reference other tables and join them with the CTE. However, if the CTE is used in a JOIN, it cannot appear on the right side of a LEFT JOIN.

Time to explore the syntax of recursive CTEs in MySQL, PostgreSQL, and SQL Server!

MySQL

A MySQL recursive CTE is defined as a recursive common table expression having a subquery that refers to its own name. This is the syntax supported by MySQL:

Copy
        
1 WITH RECURSIVE cte_name AS ( 2 -- anchor member 3 SELECT column_1, column_2, ..., column_n 4 FROM table_name 5 [WHERE condition] 6 7 {UNION ALL | UNION [DISTINCT] } 8 9 -- recursive member 10 SELECT column_1, column_2, ..., column_n 11 FROM cte_name 12 [WHERE termination_condition] 13 ) 14 SELECT * FROM cte_name;

Notes:

  • If any CTE in the WITH clause refers to itself, the WITH clause must begin with WITH RECURSIVE. If you do not specify the RECURSIVE keyword, you will get a “Table 'cte_name' doesn't exist” error.
  • If no CTE refers to itself, RECURSIVE is permitted but not required.
  • The two parts of the CTE must be separated by a UNION ALL or UNION operator. If the nonrecursive and recursive parts are separated by UNION, duplicate rows are eliminated.
  • Do not forget that UNION and UNION DISTINCT are the same thing in MySQL.
  • Each SELECT part can itself be a union of multiple SELECT statements.
  • The data types of the CTE result columns are determined based solely on the column types in the non-recursive part of the SELECT statement, and all these columns are treated as nullable.

PostgreSQL

The syntax of a Postgres recursive CTE is mostly the same as that of MySQL.

Notes:

  • The RECURSIVE keyword is required for WITH queries that need to refer to their own output.
  • While the CTE is specified recursively, it is internally evaluated iteratively.
  • Execution involves using a temporary working table to store records that will be processed in each recursive step.
  • If UNION (instead of UNION ALL) is used, the query discards duplicate rows and rows that duplicate any previous result row.

SQL Server

This is the recursive CTE SQL Server syntax:

Copy
        
1 WITH cte_name (column_1, column_2, ..., , column_n) AS ( 2 -- anchor member 3 SELECT column_1, column_2, ..., column_n 4 FROM base_table 5 WHERE condition 6 7 UNION ALL 8 9 -- recursive member 10 SELECT column_1, column_2, ..., column_n 11 FROM cte_name 12 [WHERE termination_condition] 13 ) 14 SELECT * FROM cte_name;

Notes:

  • An SQL Server recursive CTE can define multiple anchor and recursive members, but all anchor members must be listed before the first recursive member.
  • Anchor members must be combined with one of the following set operators: UNION ALL, UNION, INTERSECT, or EXCEPT. Only UNION ALL is allowed between the last anchor member and the first recursive member, as well as when combining multiple recursive members.
  • The following elements are not allowed in the SELECT statement of a recursive member: SELECT DISTINCT, GROUP BY, PIVOT, HAVING, scalar aggregates, TOP, LEFT JOIN, RIGHT JOIN, or OUTER JOIN (but INNER JOIN is permitted), and subqueries.
  • All columns returned by a recursive CTE are treated as nullable, regardless of the nullability of columns in the participating SELECT statements.
  • An improperly constructed recursive CTE can result in an infinite loop. To prevent this, you can limit recursion levels by using the MAXRECURSION option.
  • A view containing a recursive CTE cannot be used to update data.

CTE Recursive Query Examples

You now understand what a recursive CTE SQL statement is and how to write it. Still, you can grasp the power of its mechanism only through real-world examples. Time to dig into them!

Note: The queries below will be executed in DbVisualizer, a top-rated, feature-rich, visual database client. However, any other MySQL database client will work as well.

Example #1: Generate Sequences

SQL sequences are useful in various scenarios, from filling in gaps in datasets (e.g., missing days in a time series), to generating values for testing and debugging. Also, you can use them to implement MySQL counters.

This is how you can define a sequence with a CTE recursive query:

Copy
        
1 WITH RECURSIVE sequence AS ( 2 SELECT 2 AS number 3 4 UNION ALL 5 6 SELECT number + 2 7 FROM sequence 8 WHERE number <= 100 9 ) 10 SELECT * FROM sequence;

The above statement returns a sequence that starts from 2 and keeps adding values up to 100. In detail, it generates all even numbers from 2 to 100.

Verify that by executing it:

Note the sequence of even numbers in the result set
Note the sequence of even numbers in the result set

Example #2: Query Nested Data Structures

Assume that your application allows users to participate in threaded discussions with comments that can have nested replies. This data is stored in the comments table.

Given the ID of a comment, you may want to retrieve its entire hierarchy of sub-comments—essentially, extracting the complete content thread. Below is how you can use a recursive CTE to achieve that:

Copy
        
1 WITH RECURSIVE comment_hierarchy AS ( 2 SELECT 3 id, 4 parent_id, 5 content, 6 author, 7 created_at, 8 1 AS level 9 FROM comments 10 WHERE id = 1 11 12 UNION ALL 13 14 SELECT 15 c.id, 16 c.parent_id, 17 c.content, 18 c.author, 19 c.created_at, 20 ch.level + 1 AS level 21 FROM comments c 22 JOIN comment_hierarchy ch ON c.parent_id = ch.id 23 ) 24 SELECT content, author, created_at, level + 1 AS level 25 FROM comment_hierarchy 26 ORDER BY level, created_at;

In this example, our recursive CTE essentially has two SELECT queries looping through the entire comment hierarchy.

The result of this recursive CTE query will be as follows:

Note the entire comment thread in the result set
Note the entire comment thread in the result set

Thanks to the level column, the structure of the comment thread is easy to interpret. Without recursion, retrieving this data in a single query would be far more challenging, if not impossible.

Note that recursive CTE queries can become lengthy and complex. Thankfully, DbVisualizer Pro provides an SQL formatting feature that lets you neatly format queries with just a couple of clicks:

Formatting an SQL query in DbVisualizer
Formatting an SQL query in DbVisualizer

Formatting an SQL query in DbVisualizer

In this case, the formatted query will be formatted as follows:

Copy
        
1 WITH 2 RECURSIVE comment_hierarchy AS 3 ( SELECT 4 id, 5 parent_id, 6 content, 7 author, 8 created_at, 9 1 AS LEVEL 10 FROM 11 comments 12 WHERE 13 id = 1 14 15 UNION ALL 16 17 SELECT 18 c.id, 19 c.parent_id, 20 c.content, 21 c.author, 22 c.created_at, 23 ch.level + 1 AS LEVEL 24 FROM 25 comments c 26 JOIN 27 comment_hierarchy ch 28 ON 29 c.parent_id = ch.id 30 ) 31 SELECT 32 content, 33 author, 34 created_at, 35 LEVEL + 1 AS LEVEL 36 FROM 37 comment_hierarchy 38 ORDER BY 39 LEVEL, 40 created_at;

Example #3: Perform Cumulative Calculations

Suppose you have a sales table that records the amount of sales made each day of the year. Your goal is to retrieve a cumulative total of sales by date.

This is how you can achieve that through recursion:

Copy
        
1 WITH RECURSIVE cumulative_sales AS ( 2 SELECT date, sales, sales AS cumulative_total 3 FROM sales 4 WHERE date = "2025-01-01" 5 6 UNION ALL 7 8 SELECT s.date, s.sales, cs.cumulative_total + s.sales 9 FROM sales s 10 JOIN cumulative_sales cs ON s.date = DATE_ADD(cs.date, INTERVAL 1 DAY) 11 ) 12 SELECT date, sales, cumulative_total 13 FROM cumulative_sales;

The result will be:

Note the cumulated totals in the last column
Note the cumulated totals in the last column

As shown, the query starts from the first date and then accumulates the total sales in the cumulative_total column, day by day, through recursion. This allows you to see how sales add up over time.

Conclusion

In this guide, you learned about the SQL recursive CTE mechanism and how it works in MySQL, PostgreSQL, and SQL Server. You now understand how recursion works and how it can be applied to your queries.

SQL recursive queries can become complex, but they become easier to handle with a tool like DbVisualizer. This powerful database client supports multiple DBMS technologies and offers advanced features such as visual data exploration, query optimization, SQL formatting, and ERD-like schema generation. Try DbVisualizer for free today!

FAQ

Is the SQL recursive CTE mechanism part of the SQL standard?

Yes, recursive CTEs are part of the SQL ISO/ANSI standard. At the same time, implementations may vary slightly across databases, with some differences in syntax, maximum recursion limits, and supported operations.

What is the difference between a regular CTE and a recursive CTE?

A regular CTE defines a temporary result set through in a single query that does not reference itself. On the contrary, a recursive CTE references itself within its definition to perform recursive logic.

How does an SQL server recursive CTE work?

SQL Server executes the anchor member to get the initial result set, while the recursive member references the CTE itself to expand the result set iteratively. The recursion continues until a termination condition is met. Each iteration combines new results with the previous ones, and the process repeats until a stopping point is reached, or the MAXRECURSION limit is hit.

How does a Postgres recursive CTE work?

This is how Postgre recursive CTE query evaluation works:

  • Evaluate the non-recursive term: For a UNION (but not UNION ALL), remove duplicate rows. Include the remaining rows in the result set and store them in a temporary working table.
  • Repeat these two steps as long as the working table is not empty: First, evaluate the recursive term and substitute the current contents of the working table for the recursive self-reference. For UNION, remove duplicate rows and any rows that match previous results. Include the remaining rows in the result set and store them in an intermediate table. Second, update the working table by replacing the contents of the working table with the contents of the intermediate table. Then, clear the intermediate table.

Why should I use a database client?

Adopting an SQL client like DbVisualizer allows you to visually deal with data in your databases. A robust SQL client offers a range of tools to simplify data management and analysis, no matter which DBMS you are using. These features enhance productivity, streamline workflows, and help you gain insights from your data more effectively.

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

All You Need to Know About PostgreSQL UNIQUE Constraint

author Leslie S. Gyamfi tags POSTGRESQL 7 min 2025-01-23
title

Guidelines for a Proper SQL Script: ACID, CRUD, and Other Things You Need to Know

author Lukas Vileikis tags DbVisualizer MySQL OPTIMIZATION SQL 6 min 2025-01-21
title

How to Use MERGE in SQL Query Statements: Complete Guide

author Antonello Zanini tags ORACLE POSTGRESQL SQL SQL SERVER 8 min 2025-01-20
title

Automating SQL Queries with SQL CLI and SQL Job Scheduling

author Bonnie tags AUTOMATION SQL 6 min 2025-01-14
title

A Guide to SQL Server Indexes on Partitioned Tables

author Antonello Zanini tags SQL SERVER 7 min 2025-01-13
title

How to Display All Duplicate Records in a MySQL Table

author Antonello Zanini tags MySQL 6 min 2025-01-08
title

Understanding the SQL UNIQUE Constraint

author Leslie S. Gyamfi tags DbVisualizer SQL 6 min 2025-01-07
title

How to Drop an Index By Partition Number in SQL Server

author Antonello Zanini tags SQL SERVER 7 min 2025-01-06
title

Exploring Cursors and Temporary Tables in SQL

author Ochuko Onojakpor tags Cursors SQL 10 min 2024-12-30
title

The Most Interesting Functions in MySQL – A Guide

author Lukas Vileikis tags MySQL 5 min 2024-12-27

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.