lesson 47 Advanced product analysis · advanced
Recursive CTEs
Repeat a query step to explore a hierarchy.
the idea
A recursive CTE has an anchor query that returns the starting rows and a recursive query that joins the CTE to the table to find the next level. SQLite repeats the recursive step until it adds no rows. Carry a depth column to know how far each row is from the start.
Dataset: Developer Platform
The tables the challenges use, with their first 6 rows. Under each column name: the kind of value it holds, and which table it links to.
teams4 columns · 324 rows
| team_idnumber · unique id | organization_idnumber · links to organizations | team_nametext | parent_team_idnumber · links to teams |
|---|---|---|---|
| 1 | 1 | Identity | NULL |
| 2 | 1 | Security | NULL |
| 3 | 1 | Messaging | NULL |
| 4 | 1 | Observability | 1 |
| 5 | 1 | Data | 4 |
| 6 | 1 | Storage | 4 |
- team_idnumber · unique id
- 1
- organization_idnumber · links to organizations
- 1
- team_nametext
- Identity
- parent_team_idnumber · links to teams
- NULL
- team_idnumber · unique id
- 2
- organization_idnumber · links to organizations
- 1
- team_nametext
- Security
- parent_team_idnumber · links to teams
- NULL
- team_idnumber · unique id
- 3
- organization_idnumber · links to organizations
- 1
- team_nametext
- Messaging
- parent_team_idnumber · links to teams
- NULL
- team_idnumber · unique id
- 4
- organization_idnumber · links to organizations
- 1
- team_nametext
- Observability
- parent_team_idnumber · links to teams
- 1
- team_idnumber · unique id
- 5
- organization_idnumber · links to organizations
- 1
- team_nametext
- Data
- parent_team_idnumber · links to teams
- 4
- team_idnumber · unique id
- 6
- organization_idnumber · links to organizations
- 1
- team_nametext
- Storage
- parent_team_idnumber · links to teams
- 4
first 6 of 324 rows · your queries use all of them
developers9 columns · 2,891 rows
| developer_idnumber · unique id | organization_idnumber · links to organizations | team_idnumber · links to teams | usernametext | display_nametext | roletext | manager_idnumber · links to developers | joined_atdate & time | left_attext |
|---|---|---|---|---|---|---|---|---|
| 1 | 1 | NULL | zgreen1 | Zara Green | vp | NULL | 2022-06-03 00:00:00 | NULL |
| 2 | 1 | 1 | dturner2 | Daniela Turner | director | 1 | 2022-07-03 00:00:00 | NULL |
| 3 | 1 | 2 | cthomas3 | Carlos Thomas | director | 1 | 2022-11-30 00:00:00 | NULL |
| 4 | 1 | 3 | mkang4 | Miguel Kang | director | 1 | 2022-09-29 00:00:00 | NULL |
| 5 | 1 | 4 | rnguyen5 | Ryan Nguyen | manager | 2 | 2023-07-27 00:00:00 | NULL |
| 6 | 1 | 5 | jabbott6 | Jose Abbott | manager | 2 | 2022-10-15 00:00:00 | NULL |
- developer_idnumber · unique id
- 1
- organization_idnumber · links to organizations
- 1
- team_idnumber · links to teams
- NULL
- usernametext
- zgreen1
- display_nametext
- Zara Green
- roletext
- vp
- manager_idnumber · links to developers
- NULL
- joined_atdate & time
- 2022-06-03 00:00:00
- left_attext
- NULL
- developer_idnumber · unique id
- 2
- organization_idnumber · links to organizations
- 1
- team_idnumber · links to teams
- 1
- usernametext
- dturner2
- display_nametext
- Daniela Turner
- roletext
- director
- manager_idnumber · links to developers
- 1
- joined_atdate & time
- 2022-07-03 00:00:00
- left_attext
- NULL
- developer_idnumber · unique id
- 3
- organization_idnumber · links to organizations
- 1
- team_idnumber · links to teams
- 2
- usernametext
- cthomas3
- display_nametext
- Carlos Thomas
- roletext
- director
- manager_idnumber · links to developers
- 1
- joined_atdate & time
- 2022-11-30 00:00:00
- left_attext
- NULL
- developer_idnumber · unique id
- 4
- organization_idnumber · links to organizations
- 1
- team_idnumber · links to teams
- 3
- usernametext
- mkang4
- display_nametext
- Miguel Kang
- roletext
- director
- manager_idnumber · links to developers
- 1
- joined_atdate & time
- 2022-09-29 00:00:00
- left_attext
- NULL
- developer_idnumber · unique id
- 5
- organization_idnumber · links to organizations
- 1
- team_idnumber · links to teams
- 4
- usernametext
- rnguyen5
- display_nametext
- Ryan Nguyen
- roletext
- manager
- manager_idnumber · links to developers
- 2
- joined_atdate & time
- 2023-07-27 00:00:00
- left_attext
- NULL
- developer_idnumber · unique id
- 6
- organization_idnumber · links to organizations
- 1
- team_idnumber · links to teams
- 5
- usernametext
- jabbott6
- display_nametext
- Jose Abbott
- roletext
- manager
- manager_idnumber · links to developers
- 2
- joined_atdate & time
- 2022-10-15 00:00:00
- left_attext
- NULL
first 6 of 2,891 rows · your queries use all of them
worked example
Run these steps in order to see the idea before you write your own query. The step marked “the pattern” is what the challenges ask for. Running the example does not count toward progress.
Count from 1 to 5 with recursion. Run this. A recursive CTE starts at 1 and keeps adding rows until the condition fails—the pattern you reuse for org trees.
step-1.sqlWITH RECURSIVE n(x) AS ( SELECT 1 UNION ALL SELECT x + 1 FROM n WHERE x < 5 ) SELECT x FROM n;
See how teams nest. Run this. parent_team_id links each team to its parent; NULL means a top-level team in organization 2.
step-2.sqlSELECT team_id, team_name, parent_team_id FROM teams WHERE organization_id = 2 ORDER BY parent_team_id NULLS FIRST, team_id;
Put it together. The lesson's pattern in one query.the pattern
example.sqlWITH RECURSIVE n(x) AS ( SELECT 1 UNION ALL SELECT x + 1 FROM n WHERE x < 5 ) SELECT x FROM n;
common mistake
Joining in the wrong direction (parent to child versus child to parent) walks up the tree instead of down, or stops after one level.