How Far Does the Failed Supply Line Reach?
Follow a hierarchy or dependency graph using an anchor, a repeated join and explicit safeguards against cycles and excessive traversal.
13 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
Repeated Relationships Needed More Than a Fixed Join
BOTIE
The maintenance question is the same one: which records can we reach from this starting point? Our path guard rejects a return to a node already on the current path. A separate depth limit bounds how far this particular report is allowed to explore.
Sources for this history
The Next Connection Is in Another Row
Supply node 1 feeds nodes 2 and 3. Those nodes feed other equipment. When the supply fails, Botie needs to follow the connections far enough to identify the affected equipment. A single join only follows one connection.
We could write another join for every level, but we do not know the depth of every installation in advance. A recursive query applies the same step to the rows discovered by the previous step. We also need a stopping rule, because a bad record can point back to equipment we already visited.
The records are invented maintenance examples. The workshop exposes record operations; the SQL worksheet executes a real query in an isolated SQLite database.
Step 1 of 1
Expand the frontier before writing the recursion
The links are 1→2, 1→3, 2→4 and 3→4.
| Depth | Nodes reached at this step | What happened? |
|---|---|---|
| 0 | 1 | The chosen starting node |
| 1 | 2, 3 | One outgoing link from node 1 |
| 2 | 4, 4 | Two different paths reach node 4 |
0
- Nodes reached at this step
- 1
- What happened?
- The chosen starting node
1
- Nodes reached at this step
- 2, 3
- What happened?
- One outgoing link from node 1
2
- Nodes reached at this step
- 4, 4
- What happened?
- Two different paths reach node 4
Each recursive step consumes a discovered node
The path guard controls revisits while the final group changes path records into one row per reached node.
Follow the supply connections
Seed a starting node, expand its outgoing links, and keep the shortest discovered depth for each reached node.
BOTIE
Reached node IDs and minimum depths
[
{
"node_id": 1,
"depth": 0
},
{
"node_id": 2,
"depth": 1
},
{
"node_id": 3,
"depth": 1
},
{
"node_id": 4,
"depth": 2
}
]Follow the operations
1. Starting node ID
1
2. Start from one hierarchy node
[ { "node_id": 1, "depth": 0, "path": [ 1 ] } ]3. Supply connections
[ { "parent_id": 1, "child_id": 2 }, { "parent_id": 1, "child_id": 3 }, { "parent_id": 2, "child_id": 4 }, { "parent_id": 3, "child_id": 4 } ]4. Maximum depth
3
5. Repeat a bounded relation expansion
[ { "node_id": 1, "depth": 0, "path": [ 1 ] }, { "node_id": 2, "depth": 1, "path": [ 1, 2 ] }, { "node_id": 3, "depth": 1, "path": [ 1, 3 ] }, { "node_id": 4, "depth": 2, "path": [ 1, 2, 4 ] }, { "node_id": 4, "depth": 2, "path": [ 1, 3, 4 ] } ]6. Summarise each group
[ { "node_id": 1, "depth": 0 }, { "node_id": 2, "depth": 1 }, { "node_id": 3, "depth": 1 }, { "node_id": 4, "depth": 2 } ]7. Order rows by named fields
[ { "node_id": 1, "depth": 0 }, { "node_id": 2, "depth": 1 }, { "node_id": 3, "depth": 1 }, { "node_id": 4, "depth": 2 } ]8. Result
[ { "node_id": 1, "depth": 0 }, { "node_id": 2, "depth": 1 }, { "node_id": 3, "depth": 1 }, { "node_id": 4, "depth": 2 } ]
This bounded traversal is a record demonstration, not a database optimiser or a complete reachability claim beyond max_depth.
1. Keep the relationship in an edge table
An edge row stores parent_id and child_id. The pair says which node supplies another node; it does not contain all descendants. A graph can have several parents for the same child, as node 4 does here.
Decide whether the question wants paths, distinct reached nodes, or quantities propagated along paths. Removing repeated node IDs is suitable for our affected-equipment list, but could destroy useful information in a parts-quantity calculation.
The output grain determines whether two routes to node 4 should remain separate.
2. Choose the anchor before repeating anything
The anchor SELECT creates node 1 at depth 0. Its path text is /1/. It runs without reading the recursive result. The recursive term then receives discovered rows and joins their node IDs to the edge table’s parent_id.
UNION ALL preserves newly discovered paths. We increment depth and append each child ID so later expansion can inspect the route already taken.
The anchor answers where to begin. The recursive term answers how to take one more step.
3. Run the bounded traversal
The query below expands rows only while their current depth is below 3. It therefore includes discoveries at depth 3, then stops expanding them. The path check separately prevents revisiting a node on the current route.
After traversal, grouping by node_id and taking MIN(depth) can produce one reached-node row with the shortest depth among the paths explored. First inspect the paths themselves. The final ORDER BY controls display order; recursive processing order is not the report’s order.
The cap defines a bounded report. It does not prove that no equipment exists beyond the cap.
Inspect the paths before combining reached nodes
Node 4 appears through two routes in the internal walk. The challenge will combine them into one reached-node row.
| parent_id | child_id |
|---|---|
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
| 3 | 4 |
Database setup for this example
CREATE TABLE edges(parent_id INTEGER NOT NULL, child_id INTEGER NOT NULL);
INSERT INTO edges VALUES (1,2),(1,3),(2,4),(3,4);WITH RECURSIVE walk(node_id, depth, path) AS (
SELECT 1, 0, '/1/'
UNION ALL
SELECT e.child_id, w.depth + 1, w.path || e.child_id || '/'
FROM walk AS w JOIN edges AS e ON e.parent_id = w.node_id
WHERE w.depth < 3
AND instr(w.path, '/' || e.child_id || '/') = 0
)
SELECT node_id, depth, path FROM walk ORDER BY depth, node_id, path;| node_id | depth | path |
|---|---|---|
| 1 | 0 | /1/ |
| 2 | 1 | /1/2/ |
| 3 | 1 | /1/3/ |
| 4 | 2 | /1/2/4/ |
| 4 | 2 | /1/3/4/ |
Both routes to node 4 are visible. The starting node has depth zero because no edge was followed to reach it.
Check yourself
Questions on Parts 1 to 3
Question 1 of 2
At which depth is node 4 first reached?
Use the information below.
The edges are 1→2, 1→3, 2→4 and 3→4. The anchor is node 1 at depth 0.4. A cycle changes the stopping problem
Suppose a mistaken edge points from node 4 back to node 1. Without a guard, repeated UNION ALL expansion can revisit the same equipment indefinitely. The slash-delimited path lets instr search for the complete integer ID rather than confusing node 1 with node 11.
Changing UNION ALL to UNION is not a universal cycle repair. If the recursive row contains increasing depth or changing path text, each new row can still be distinct. Deduplication compares whole rows.
Our integer-ID path representation is a teaching choice. Arbitrary text identifiers need a representation whose delimiters cannot be confused with the identifier itself.
5. Separate cycle prevention from a resource bound
A path guard prevents one route from looping. It does not prevent a graph with many branches from containing a very large number of simple paths. A maximum depth, execution budget and an operational row limit can still be necessary.
If the limit is reached, label the result as bounded or incomplete. Quietly treating a truncated report as the full dependency closure could leave affected equipment out of a shutdown plan.
The workshop caps depth and total expanded rows to keep the demonstration responsive. Those limits are not a proof that a real installation is small.
6. Inspect the intermediate paths when the result surprises you
Temporarily select node_id, depth and path from walk to see why a node was reached. If node 4 is missing, check the edge direction. If it appears twice, decide whether you forgot the final node-level summary or whether the task actually needs both routes.
Recursive CTE syntax and built-in cycle facilities differ between engines. This example uses SQLite-compatible string paths rather than assuming another database’s SEARCH or CYCLE syntax.
A final MIN(depth) is a report over discovered paths. It does not repair an incorrect edge direction or missing source records.
Check yourself
Questions on Parts 1 to 6
Question 1 of 2
What can the report honestly claim?
Use the information below.
The traversal permits depth 0 through 3. An unvisited child exists at depth 4.Try it with Botie
Build with Botie
Write and run SQL