[{"data":1,"prerenderedAt":5388},["ShallowReactive",2],{"page-\u002Fsql\u002F19-recursive-queries":3},{"id":4,"title":5,"body":6,"description":16,"extension":5382,"meta":5383,"navigation":136,"path":5384,"seo":5385,"stem":5386,"__hash__":5387},"content\u002Fsql\u002F19-recursive-queries.md","19 — Recursive Queries",{"type":7,"value":8,"toc":5356},"minimark",[9,13,17,20,25,33,50,252,263,268,278,285,287,291,514,523,527,731,741,743,747,750,1019,1030,1032,1036,1043,1047,1255,1261,1265,1449,1466,1470,1593,1605,1607,1611,1891,1898,1900,1904,1907,3141,3157,3159,3163,3168,3443,3446,3448,3452,3652,3665,3667,3671,4010,4019,4021,4025,4035,4211,4220,4222,4226,4295,4307,4309,4313,4613,4619,4621,4625,4713,4715,4719,4843,4845,4849,4852,4965,5341,5343,5346,5352],[10,11,5],"h1",{"id":12},"_19-recursive-queries",[14,15,16],"p",{},"Recursive CTEs let a query refer to its own output — enabling tree traversal, graph walking, hierarchical aggregations, and iterative computations that aren't expressible in a single non-recursive query. They are the SQL-native way to express \"repeat until done.\"",[18,19],"hr",{},[21,22,24],"h2",{"id":23},"the-structure","The Structure",[14,26,27,28,32],{},"A recursive CTE has two parts combined by ",[29,30,31],"code",{},"UNION ALL",":",[34,35,36,44],"ol",{},[37,38,39,43],"li",{},[40,41,42],"strong",{},"Anchor"," (base case) — a non-recursive query that seeds the recursion.",[37,45,46,49],{},[40,47,48],{},"Recursive case"," — a query that references the CTE itself, joined to produce the next iteration.",[51,52,54],"code-wrapper",{"language":53},"sql",[55,56,60],"pre",{"className":57,"code":58,"language":53,"meta":59,"style":59},"language-sql shiki shiki-themes github-light github-dark","WITH RECURSIVE descendant AS (\n  -- Anchor: seed with the starting node (employee 1)\n  SELECT id, name, manager_id\n  FROM employees\n  WHERE id = 1\n\n  UNION ALL   -- must be UNION ALL (UNION dedupes per iteration — slower, different semantics)\n\n  -- Recursive: join the previous iteration's output to find the next level\n  SELECT e.id, e.name, e.manager_id\n  FROM employees e\n  JOIN descendant d ON e.manager_id = d.id   -- find direct reports of the previous level\n)\nSELECT * FROM descendant;\n","",[29,61,62,84,91,106,115,131,138,147,152,158,191,199,231,237],{"__ignoreMap":59},[63,64,67,71,74,78,81],"span",{"class":65,"line":66},"line",1,[63,68,70],{"class":69},"svdQ7","WITH",[63,72,73],{"class":69}," RECURSIVE",[63,75,77],{"class":76},"ssxIu"," descendant ",[63,79,80],{"class":69},"AS",[63,82,83],{"class":76}," (\n",[63,85,87],{"class":65,"line":86},2,[63,88,90],{"class":89},"sdCPZ","  -- Anchor: seed with the starting node (employee 1)\n",[63,92,94,97,100,103],{"class":65,"line":93},3,[63,95,96],{"class":69},"  SELECT",[63,98,99],{"class":76}," id, ",[63,101,102],{"class":69},"name",[63,104,105],{"class":76},", manager_id\n",[63,107,109,112],{"class":65,"line":108},4,[63,110,111],{"class":69},"  FROM",[63,113,114],{"class":76}," employees\n",[63,116,118,121,124,127],{"class":65,"line":117},5,[63,119,120],{"class":69},"  WHERE",[63,122,123],{"class":76}," id ",[63,125,126],{"class":69},"=",[63,128,130],{"class":129},"snvgF"," 1\n",[63,132,134],{"class":65,"line":133},6,[63,135,137],{"emptyLinePlaceholder":136},true,"\n",[63,139,141,144],{"class":65,"line":140},7,[63,142,143],{"class":69},"  UNION ALL",[63,145,146],{"class":89},"   -- must be UNION ALL (UNION dedupes per iteration — slower, different semantics)\n",[63,148,150],{"class":65,"line":149},8,[63,151,137],{"emptyLinePlaceholder":136},[63,153,155],{"class":65,"line":154},9,[63,156,157],{"class":89},"  -- Recursive: join the previous iteration's output to find the next level\n",[63,159,161,163,166,169,172,175,178,180,182,184,186,188],{"class":65,"line":160},10,[63,162,96],{"class":69},[63,164,165],{"class":129}," e",[63,167,168],{"class":76},".",[63,170,171],{"class":129},"id",[63,173,174],{"class":76},", ",[63,176,177],{"class":129},"e",[63,179,168],{"class":76},[63,181,102],{"class":129},[63,183,174],{"class":76},[63,185,177],{"class":129},[63,187,168],{"class":76},[63,189,190],{"class":129},"manager_id\n",[63,192,194,196],{"class":65,"line":193},11,[63,195,111],{"class":69},[63,197,198],{"class":76}," employees e\n",[63,200,202,205,208,211,213,215,218,221,224,226,228],{"class":65,"line":201},12,[63,203,204],{"class":69},"  JOIN",[63,206,207],{"class":76}," descendant d ",[63,209,210],{"class":69},"ON",[63,212,165],{"class":129},[63,214,168],{"class":76},[63,216,217],{"class":129},"manager_id",[63,219,220],{"class":69}," =",[63,222,223],{"class":129}," d",[63,225,168],{"class":76},[63,227,171],{"class":129},[63,229,230],{"class":89},"   -- find direct reports of the previous level\n",[63,232,234],{"class":65,"line":233},13,[63,235,236],{"class":76},")\n",[63,238,240,243,246,249],{"class":65,"line":239},14,[63,241,242],{"class":69},"SELECT",[63,244,245],{"class":69}," *",[63,247,248],{"class":69}," FROM",[63,250,251],{"class":76}," descendant;\n",[14,253,254,255,258,259,262],{},"Each iteration takes the ",[40,256,257],{},"previous iteration's output"," (not the accumulated set), joins it to ",[29,260,261],{},"employees",", and produces the next level. The recursion stops when an iteration produces zero rows. The final result is the union of all iterations.",[264,265,267],"h3",{"id":266},"evaluation-model","Evaluation Model",[51,269,271],{"language":270},"text",[55,272,276],{"className":273,"code":275,"language":270,"meta":59},[274],"language-text","Iteration 0 (anchor):   {employee 1}                              → seed\nIteration 1:            join {employee 1} → {direct reports of 1}  → level 1\nIteration 2:            join {level 1}      → {reports of level 1} → level 2\n  ...\nIteration N:            join {level N-1}    → {} (no more reports) → STOP\nResult: UNION ALL of all iterations (anchor + level 1 + level 2 + ... + level N-1)\n",[29,277,275],{"__ignoreMap":59},[14,279,280,281,284],{},"The working set is the ",[40,282,283],{},"previous iteration's rows only"," — not the full accumulated set. This is \"linear recursion\" per the SQL standard. The recursion terminates when an iteration produces zero new rows.",[18,286],{},[21,288,290],{"id":289},"tree-traversal-employee-hierarchy-with-depth","Tree Traversal: Employee Hierarchy with Depth",[51,292,293],{"language":53},[55,294,296],{"className":57,"code":295,"language":53,"meta":59,"style":59},"-- All descendants of employee 1, with their depth in the tree\nWITH RECURSIVE descendant AS (\n  -- Anchor: the root node, depth 0\n  SELECT id, name, manager_id, 0 AS depth\n  FROM employees\n  WHERE id = 1\n\n  UNION ALL\n\n  -- Recursive: find reports of the previous level, increment depth\n  SELECT e.id, e.name, e.manager_id, d.depth + 1\n  FROM employees e\n  JOIN descendant d ON e.manager_id = d.id   -- previous iteration's rows only\n)\nSELECT id, name, depth FROM descendant ORDER BY depth, name;\n--  id | name     | depth\n--  1  | CEO      | 0\n--  2  | VP Eng   | 1\n--  3  | VP Sales | 1\n--  4  | Eng Mgr  | 2\n--  5  | Sales Mgr| 2\n",[29,297,298,303,315,320,340,346,356,360,365,369,374,415,421,446,450,478,484,490,496,502,508],{"__ignoreMap":59},[63,299,300],{"class":65,"line":66},[63,301,302],{"class":89},"-- All descendants of employee 1, with their depth in the tree\n",[63,304,305,307,309,311,313],{"class":65,"line":86},[63,306,70],{"class":69},[63,308,73],{"class":69},[63,310,77],{"class":76},[63,312,80],{"class":69},[63,314,83],{"class":76},[63,316,317],{"class":65,"line":93},[63,318,319],{"class":89},"  -- Anchor: the root node, depth 0\n",[63,321,322,324,326,328,331,334,337],{"class":65,"line":108},[63,323,96],{"class":69},[63,325,99],{"class":76},[63,327,102],{"class":69},[63,329,330],{"class":76},", manager_id, ",[63,332,333],{"class":129},"0",[63,335,336],{"class":69}," AS",[63,338,339],{"class":76}," depth\n",[63,341,342,344],{"class":65,"line":117},[63,343,111],{"class":69},[63,345,114],{"class":76},[63,347,348,350,352,354],{"class":65,"line":133},[63,349,120],{"class":69},[63,351,123],{"class":76},[63,353,126],{"class":69},[63,355,130],{"class":129},[63,357,358],{"class":65,"line":140},[63,359,137],{"emptyLinePlaceholder":136},[63,361,362],{"class":65,"line":149},[63,363,364],{"class":69},"  UNION ALL\n",[63,366,367],{"class":65,"line":154},[63,368,137],{"emptyLinePlaceholder":136},[63,370,371],{"class":65,"line":160},[63,372,373],{"class":89},"  -- Recursive: find reports of the previous level, increment depth\n",[63,375,376,378,380,382,384,386,388,390,392,394,396,398,400,402,405,407,410,413],{"class":65,"line":193},[63,377,96],{"class":69},[63,379,165],{"class":129},[63,381,168],{"class":76},[63,383,171],{"class":129},[63,385,174],{"class":76},[63,387,177],{"class":129},[63,389,168],{"class":76},[63,391,102],{"class":129},[63,393,174],{"class":76},[63,395,177],{"class":129},[63,397,168],{"class":76},[63,399,217],{"class":129},[63,401,174],{"class":76},[63,403,404],{"class":129},"d",[63,406,168],{"class":76},[63,408,409],{"class":129},"depth",[63,411,412],{"class":69}," +",[63,414,130],{"class":129},[63,416,417,419],{"class":65,"line":201},[63,418,111],{"class":69},[63,420,198],{"class":76},[63,422,423,425,427,429,431,433,435,437,439,441,443],{"class":65,"line":233},[63,424,204],{"class":69},[63,426,207],{"class":76},[63,428,210],{"class":69},[63,430,165],{"class":129},[63,432,168],{"class":76},[63,434,217],{"class":129},[63,436,220],{"class":69},[63,438,223],{"class":129},[63,440,168],{"class":76},[63,442,171],{"class":129},[63,444,445],{"class":89},"   -- previous iteration's rows only\n",[63,447,448],{"class":65,"line":239},[63,449,236],{"class":76},[63,451,453,455,457,459,462,465,467,470,473,475],{"class":65,"line":452},15,[63,454,242],{"class":69},[63,456,99],{"class":76},[63,458,102],{"class":69},[63,460,461],{"class":76},", depth ",[63,463,464],{"class":69},"FROM",[63,466,77],{"class":76},[63,468,469],{"class":69},"ORDER BY",[63,471,472],{"class":76}," depth, ",[63,474,102],{"class":69},[63,476,477],{"class":76},";\n",[63,479,481],{"class":65,"line":480},16,[63,482,483],{"class":89},"--  id | name     | depth\n",[63,485,487],{"class":65,"line":486},17,[63,488,489],{"class":89},"--  1  | CEO      | 0\n",[63,491,493],{"class":65,"line":492},18,[63,494,495],{"class":89},"--  2  | VP Eng   | 1\n",[63,497,499],{"class":65,"line":498},19,[63,500,501],{"class":89},"--  3  | VP Sales | 1\n",[63,503,505],{"class":65,"line":504},20,[63,506,507],{"class":89},"--  4  | Eng Mgr  | 2\n",[63,509,511],{"class":65,"line":510},21,[63,512,513],{"class":89},"--  5  | Sales Mgr| 2\n",[14,515,516,518,519,522],{},[29,517,409],{}," is a free byproduct of the recursion — increment it in the recursive case. Use it for limiting depth (",[29,520,521],{},"WHERE depth \u003C 5",") or ordering breadth-first.",[264,524,526],{"id":525},"building-the-full-path","Building the Full Path",[51,528,529],{"language":53},[55,530,532],{"className":57,"code":531,"language":53,"meta":59,"style":59},"-- Each employee with their management chain as an array (root → current)\nWITH RECURSIVE chain AS (\n  -- Anchor: roots (employees with no manager), path starts with their own ID\n  SELECT id, name, manager_id, ARRAY[id] AS path\n  FROM employees\n  WHERE manager_id IS NULL\n\n  UNION ALL\n\n  -- Recursive: append current employee's ID to the parent's path\n  SELECT e.id, e.name, e.manager_id, c.path || e.id\n  FROM employees e\n  JOIN chain c ON e.manager_id = c.id\n)\nSELECT id, name, path FROM chain;\n--  id | name    | path\n--  1  | CEO     | {1}\n--  2  | VP Eng  | {1,2}\n--  4  | Eng Mgr | {1,2,4}\n",[29,533,534,539,552,557,578,584,597,601,605,609,614,660,666,690,694,711,716,721,726],{"__ignoreMap":59},[63,535,536],{"class":65,"line":66},[63,537,538],{"class":89},"-- Each employee with their management chain as an array (root → current)\n",[63,540,541,543,545,548,550],{"class":65,"line":86},[63,542,70],{"class":69},[63,544,73],{"class":69},[63,546,547],{"class":76}," chain ",[63,549,80],{"class":69},[63,551,83],{"class":76},[63,553,554],{"class":65,"line":93},[63,555,556],{"class":89},"  -- Anchor: roots (employees with no manager), path starts with their own ID\n",[63,558,559,561,563,565,567,570,573,575],{"class":65,"line":108},[63,560,96],{"class":69},[63,562,99],{"class":76},[63,564,102],{"class":69},[63,566,330],{"class":76},[63,568,569],{"class":69},"ARRAY",[63,571,572],{"class":76},"[id] ",[63,574,80],{"class":69},[63,576,577],{"class":69}," path\n",[63,579,580,582],{"class":65,"line":117},[63,581,111],{"class":69},[63,583,114],{"class":76},[63,585,586,588,591,594],{"class":65,"line":133},[63,587,120],{"class":69},[63,589,590],{"class":76}," manager_id ",[63,592,593],{"class":69},"IS",[63,595,596],{"class":69}," NULL\n",[63,598,599],{"class":65,"line":140},[63,600,137],{"emptyLinePlaceholder":136},[63,602,603],{"class":65,"line":149},[63,604,364],{"class":69},[63,606,607],{"class":65,"line":154},[63,608,137],{"emptyLinePlaceholder":136},[63,610,611],{"class":65,"line":160},[63,612,613],{"class":89},"  -- Recursive: append current employee's ID to the parent's path\n",[63,615,616,618,620,622,624,626,628,630,632,634,636,638,640,642,645,647,650,653,655,657],{"class":65,"line":193},[63,617,96],{"class":69},[63,619,165],{"class":129},[63,621,168],{"class":76},[63,623,171],{"class":129},[63,625,174],{"class":76},[63,627,177],{"class":129},[63,629,168],{"class":76},[63,631,102],{"class":129},[63,633,174],{"class":76},[63,635,177],{"class":129},[63,637,168],{"class":76},[63,639,217],{"class":129},[63,641,174],{"class":76},[63,643,644],{"class":129},"c",[63,646,168],{"class":76},[63,648,649],{"class":129},"path",[63,651,652],{"class":69}," ||",[63,654,165],{"class":129},[63,656,168],{"class":76},[63,658,659],{"class":129},"id\n",[63,661,662,664],{"class":65,"line":201},[63,663,111],{"class":69},[63,665,198],{"class":76},[63,667,668,670,673,675,677,679,681,683,686,688],{"class":65,"line":233},[63,669,204],{"class":69},[63,671,672],{"class":76}," chain c ",[63,674,210],{"class":69},[63,676,165],{"class":129},[63,678,168],{"class":76},[63,680,217],{"class":129},[63,682,220],{"class":69},[63,684,685],{"class":129}," c",[63,687,168],{"class":76},[63,689,659],{"class":129},[63,691,692],{"class":65,"line":239},[63,693,236],{"class":76},[63,695,696,698,700,702,704,706,708],{"class":65,"line":452},[63,697,242],{"class":69},[63,699,99],{"class":76},[63,701,102],{"class":69},[63,703,174],{"class":76},[63,705,649],{"class":69},[63,707,248],{"class":69},[63,709,710],{"class":76}," chain;\n",[63,712,713],{"class":65,"line":480},[63,714,715],{"class":89},"--  id | name    | path\n",[63,717,718],{"class":65,"line":486},[63,719,720],{"class":89},"--  1  | CEO     | {1}\n",[63,722,723],{"class":65,"line":492},[63,724,725],{"class":89},"--  2  | VP Eng  | {1,2}\n",[63,727,728],{"class":65,"line":498},[63,729,730],{"class":89},"--  4  | Eng Mgr | {1,2,4}\n",[14,732,733,734,736,737,740],{},"The ",[29,735,649],{}," array accumulates the chain from root to current node — ",[29,738,739],{},"||"," (array concatenation) builds it iteratively. This is also the basis for cycle detection.",[18,742],{},[21,744,746],{"id":745},"bidirectional-traversal","Bidirectional Traversal",[14,748,749],{},"The same table can be traversed up (toward roots) or down (toward leaves) — the difference is the join direction:",[51,751,752],{"language":53},[55,753,755],{"className":57,"code":754,"language":53,"meta":59,"style":59},"-- Walk DOWN: find all descendants of employee 1 (reports, reports of reports, ...)\nWITH RECURSIVE descendant AS (\n  SELECT id, name, manager_id FROM employees WHERE id = 1\n  UNION ALL\n  SELECT e.id, e.name, e.manager_id\n  FROM employees e\n  JOIN descendant d ON e.manager_id = d.id   -- join: e's manager is d → d's reports\n)\nSELECT * FROM descendant;\n\n-- Walk UP: find all ancestors of employee 5 (manager, manager's manager, ...)\nWITH RECURSIVE ancestor AS (\n  SELECT id, name, manager_id FROM employees WHERE id = 5\n  UNION ALL\n  SELECT e.id, e.name, e.manager_id\n  FROM employees e\n  JOIN ancestor a ON a.manager_id = e.id    -- join: a's manager is e → a's manager\n)\nSELECT * FROM ancestor;\n--  id | name    | manager_id\n--  5  | Eng Mgr | 2\n--  2  | VP Eng  | 1\n--  1  | CEO     | NULL\n",[29,756,757,762,774,799,803,829,835,860,864,874,878,883,896,919,923,949,955,982,986,997,1002,1007,1013],{"__ignoreMap":59},[63,758,759],{"class":65,"line":66},[63,760,761],{"class":89},"-- Walk DOWN: find all descendants of employee 1 (reports, reports of reports, ...)\n",[63,763,764,766,768,770,772],{"class":65,"line":86},[63,765,70],{"class":69},[63,767,73],{"class":69},[63,769,77],{"class":76},[63,771,80],{"class":69},[63,773,83],{"class":76},[63,775,776,778,780,782,785,787,790,793,795,797],{"class":65,"line":93},[63,777,96],{"class":69},[63,779,99],{"class":76},[63,781,102],{"class":69},[63,783,784],{"class":76},", manager_id ",[63,786,464],{"class":69},[63,788,789],{"class":76}," employees ",[63,791,792],{"class":69},"WHERE",[63,794,123],{"class":76},[63,796,126],{"class":69},[63,798,130],{"class":129},[63,800,801],{"class":65,"line":108},[63,802,364],{"class":69},[63,804,805,807,809,811,813,815,817,819,821,823,825,827],{"class":65,"line":117},[63,806,96],{"class":69},[63,808,165],{"class":129},[63,810,168],{"class":76},[63,812,171],{"class":129},[63,814,174],{"class":76},[63,816,177],{"class":129},[63,818,168],{"class":76},[63,820,102],{"class":129},[63,822,174],{"class":76},[63,824,177],{"class":129},[63,826,168],{"class":76},[63,828,190],{"class":129},[63,830,831,833],{"class":65,"line":133},[63,832,111],{"class":69},[63,834,198],{"class":76},[63,836,837,839,841,843,845,847,849,851,853,855,857],{"class":65,"line":140},[63,838,204],{"class":69},[63,840,207],{"class":76},[63,842,210],{"class":69},[63,844,165],{"class":129},[63,846,168],{"class":76},[63,848,217],{"class":129},[63,850,220],{"class":69},[63,852,223],{"class":129},[63,854,168],{"class":76},[63,856,171],{"class":129},[63,858,859],{"class":89},"   -- join: e's manager is d → d's reports\n",[63,861,862],{"class":65,"line":149},[63,863,236],{"class":76},[63,865,866,868,870,872],{"class":65,"line":154},[63,867,242],{"class":69},[63,869,245],{"class":69},[63,871,248],{"class":69},[63,873,251],{"class":76},[63,875,876],{"class":65,"line":160},[63,877,137],{"emptyLinePlaceholder":136},[63,879,880],{"class":65,"line":193},[63,881,882],{"class":89},"-- Walk UP: find all ancestors of employee 5 (manager, manager's manager, ...)\n",[63,884,885,887,889,892,894],{"class":65,"line":201},[63,886,70],{"class":69},[63,888,73],{"class":69},[63,890,891],{"class":76}," ancestor ",[63,893,80],{"class":69},[63,895,83],{"class":76},[63,897,898,900,902,904,906,908,910,912,914,916],{"class":65,"line":233},[63,899,96],{"class":69},[63,901,99],{"class":76},[63,903,102],{"class":69},[63,905,784],{"class":76},[63,907,464],{"class":69},[63,909,789],{"class":76},[63,911,792],{"class":69},[63,913,123],{"class":76},[63,915,126],{"class":69},[63,917,918],{"class":129}," 5\n",[63,920,921],{"class":65,"line":239},[63,922,364],{"class":69},[63,924,925,927,929,931,933,935,937,939,941,943,945,947],{"class":65,"line":452},[63,926,96],{"class":69},[63,928,165],{"class":129},[63,930,168],{"class":76},[63,932,171],{"class":129},[63,934,174],{"class":76},[63,936,177],{"class":129},[63,938,168],{"class":76},[63,940,102],{"class":129},[63,942,174],{"class":76},[63,944,177],{"class":129},[63,946,168],{"class":76},[63,948,190],{"class":129},[63,950,951,953],{"class":65,"line":480},[63,952,111],{"class":69},[63,954,198],{"class":76},[63,956,957,959,962,964,967,969,971,973,975,977,979],{"class":65,"line":486},[63,958,204],{"class":69},[63,960,961],{"class":76}," ancestor a ",[63,963,210],{"class":69},[63,965,966],{"class":129}," a",[63,968,168],{"class":76},[63,970,217],{"class":129},[63,972,220],{"class":69},[63,974,165],{"class":129},[63,976,168],{"class":76},[63,978,171],{"class":129},[63,980,981],{"class":89},"    -- join: a's manager is e → a's manager\n",[63,983,984],{"class":65,"line":492},[63,985,236],{"class":76},[63,987,988,990,992,994],{"class":65,"line":498},[63,989,242],{"class":69},[63,991,245],{"class":69},[63,993,248],{"class":69},[63,995,996],{"class":76}," ancestor;\n",[63,998,999],{"class":65,"line":504},[63,1000,1001],{"class":89},"--  id | name    | manager_id\n",[63,1003,1004],{"class":65,"line":510},[63,1005,1006],{"class":89},"--  5  | Eng Mgr | 2\n",[63,1008,1010],{"class":65,"line":1009},22,[63,1011,1012],{"class":89},"--  2  | VP Eng  | 1\n",[63,1014,1016],{"class":65,"line":1015},23,[63,1017,1018],{"class":89},"--  1  | CEO     | NULL\n",[14,1020,1021,1022,1025,1026,1029],{},"The join direction determines traversal direction: ",[29,1023,1024],{},"e.manager_id = d.id"," walks down (find reports); ",[29,1027,1028],{},"a.manager_id = e.id"," walks up (find managers).",[18,1031],{},[21,1033,1035],{"id":1034},"cycle-detection","Cycle Detection",[14,1037,1038,1039,1042],{},"A graph with cycles (e.g., a→b→a) would recurse infinitely. PostgreSQL does NOT automatically detect cycles — it relies on the recursion producing zero new rows to terminate. In a true cycle, rows are produced forever (until memory or ",[29,1040,1041],{},"statement_timeout",").",[264,1044,1046],{"id":1045},"approach-1-visited-array-portable","Approach 1: Visited Array (Portable)",[51,1048,1049],{"language":53},[55,1050,1052],{"className":57,"code":1051,"language":53,"meta":59,"style":59},"WITH RECURSIVE walk AS (\n  -- Anchor: start node, visited array contains just this node\n  SELECT id, ARRAY[id] AS visited\n  FROM nodes WHERE id = 1\n\n  UNION ALL\n\n  -- Recursive: follow edges, append target to visited, skip if already visited\n  SELECT n.id, w.visited || n.id\n  FROM walk w\n  JOIN edges e ON e.from_id = w.id              -- outgoing edges from current node\n  JOIN nodes n ON n.id = e.to_id                -- target node\n  WHERE NOT n.id = ANY(w.visited)               -- CYCLE DETECTION: skip already-visited\n)\nSELECT * FROM walk;\n",[29,1053,1054,1067,1072,1087,1102,1106,1110,1114,1119,1148,1155,1183,1210,1240,1244],{"__ignoreMap":59},[63,1055,1056,1058,1060,1063,1065],{"class":65,"line":66},[63,1057,70],{"class":69},[63,1059,73],{"class":69},[63,1061,1062],{"class":76}," walk ",[63,1064,80],{"class":69},[63,1066,83],{"class":76},[63,1068,1069],{"class":65,"line":86},[63,1070,1071],{"class":89},"  -- Anchor: start node, visited array contains just this node\n",[63,1073,1074,1076,1078,1080,1082,1084],{"class":65,"line":93},[63,1075,96],{"class":69},[63,1077,99],{"class":76},[63,1079,569],{"class":69},[63,1081,572],{"class":76},[63,1083,80],{"class":69},[63,1085,1086],{"class":76}," visited\n",[63,1088,1089,1091,1094,1096,1098,1100],{"class":65,"line":108},[63,1090,111],{"class":69},[63,1092,1093],{"class":76}," nodes ",[63,1095,792],{"class":69},[63,1097,123],{"class":76},[63,1099,126],{"class":69},[63,1101,130],{"class":129},[63,1103,1104],{"class":65,"line":117},[63,1105,137],{"emptyLinePlaceholder":136},[63,1107,1108],{"class":65,"line":133},[63,1109,364],{"class":69},[63,1111,1112],{"class":65,"line":140},[63,1113,137],{"emptyLinePlaceholder":136},[63,1115,1116],{"class":65,"line":149},[63,1117,1118],{"class":89},"  -- Recursive: follow edges, append target to visited, skip if already visited\n",[63,1120,1121,1123,1126,1128,1130,1132,1135,1137,1140,1142,1144,1146],{"class":65,"line":154},[63,1122,96],{"class":69},[63,1124,1125],{"class":129}," n",[63,1127,168],{"class":76},[63,1129,171],{"class":129},[63,1131,174],{"class":76},[63,1133,1134],{"class":129},"w",[63,1136,168],{"class":76},[63,1138,1139],{"class":129},"visited",[63,1141,652],{"class":69},[63,1143,1125],{"class":129},[63,1145,168],{"class":76},[63,1147,659],{"class":129},[63,1149,1150,1152],{"class":65,"line":160},[63,1151,111],{"class":69},[63,1153,1154],{"class":76}," walk w\n",[63,1156,1157,1159,1162,1164,1166,1168,1171,1173,1176,1178,1180],{"class":65,"line":193},[63,1158,204],{"class":69},[63,1160,1161],{"class":76}," edges e ",[63,1163,210],{"class":69},[63,1165,165],{"class":129},[63,1167,168],{"class":76},[63,1169,1170],{"class":129},"from_id",[63,1172,220],{"class":69},[63,1174,1175],{"class":129}," w",[63,1177,168],{"class":76},[63,1179,171],{"class":129},[63,1181,1182],{"class":89},"              -- outgoing edges from current node\n",[63,1184,1185,1187,1190,1192,1194,1196,1198,1200,1202,1204,1207],{"class":65,"line":201},[63,1186,204],{"class":69},[63,1188,1189],{"class":76}," nodes n ",[63,1191,210],{"class":69},[63,1193,1125],{"class":129},[63,1195,168],{"class":76},[63,1197,171],{"class":129},[63,1199,220],{"class":69},[63,1201,165],{"class":129},[63,1203,168],{"class":76},[63,1205,1206],{"class":129},"to_id",[63,1208,1209],{"class":89},"                -- target node\n",[63,1211,1212,1214,1217,1219,1221,1223,1225,1228,1230,1232,1234,1237],{"class":65,"line":233},[63,1213,120],{"class":69},[63,1215,1216],{"class":69}," NOT",[63,1218,1125],{"class":129},[63,1220,168],{"class":76},[63,1222,171],{"class":129},[63,1224,220],{"class":69},[63,1226,1227],{"class":76}," ANY(",[63,1229,1134],{"class":129},[63,1231,168],{"class":76},[63,1233,1139],{"class":129},[63,1235,1236],{"class":76},")               ",[63,1238,1239],{"class":89},"-- CYCLE DETECTION: skip already-visited\n",[63,1241,1242],{"class":65,"line":239},[63,1243,236],{"class":76},[63,1245,1246,1248,1250,1252],{"class":65,"line":452},[63,1247,242],{"class":69},[63,1249,245],{"class":69},[63,1251,248],{"class":69},[63,1253,1254],{"class":76}," walk;\n",[14,1256,1257,1260],{},[29,1258,1259],{},"NOT n.id = ANY(w.visited)"," prevents re-entering a node already in the current path. This is the portable approach — works on all databases with recursive CTEs.",[264,1262,1264],{"id":1263},"approach-2-cycle-clause-postgresql-14-standard-sql","Approach 2: CYCLE Clause (PostgreSQL 14+, Standard SQL)",[51,1266,1267],{"language":53},[55,1268,1270],{"className":57,"code":1269,"language":53,"meta":59,"style":59},"WITH RECURSIVE walk AS (\n  SELECT id, ARRAY[id] AS path FROM nodes WHERE id = 1\n  UNION ALL\n  SELECT n.id, w.path || n.id\n  FROM walk w\n  JOIN edges e ON e.from_id = w.id\n  JOIN nodes n ON n.id = e.to_id\n  -- CYCLE clause: track id, set is_cycle flag when a cycle is detected\n  CYCLE id SET is_cycle TO true DEFAULT false USING path\n)\nSELECT * FROM walk WHERE NOT is_cycle;   -- filter out cyclic rows\n",[29,1271,1272,1284,1311,1315,1341,1347,1369,1392,1397,1425,1429],{"__ignoreMap":59},[63,1273,1274,1276,1278,1280,1282],{"class":65,"line":66},[63,1275,70],{"class":69},[63,1277,73],{"class":69},[63,1279,1062],{"class":76},[63,1281,80],{"class":69},[63,1283,83],{"class":76},[63,1285,1286,1288,1290,1292,1294,1296,1299,1301,1303,1305,1307,1309],{"class":65,"line":86},[63,1287,96],{"class":69},[63,1289,99],{"class":76},[63,1291,569],{"class":69},[63,1293,572],{"class":76},[63,1295,80],{"class":69},[63,1297,1298],{"class":69}," path",[63,1300,248],{"class":69},[63,1302,1093],{"class":76},[63,1304,792],{"class":69},[63,1306,123],{"class":76},[63,1308,126],{"class":69},[63,1310,130],{"class":129},[63,1312,1313],{"class":65,"line":93},[63,1314,364],{"class":69},[63,1316,1317,1319,1321,1323,1325,1327,1329,1331,1333,1335,1337,1339],{"class":65,"line":108},[63,1318,96],{"class":69},[63,1320,1125],{"class":129},[63,1322,168],{"class":76},[63,1324,171],{"class":129},[63,1326,174],{"class":76},[63,1328,1134],{"class":129},[63,1330,168],{"class":76},[63,1332,649],{"class":129},[63,1334,652],{"class":69},[63,1336,1125],{"class":129},[63,1338,168],{"class":76},[63,1340,659],{"class":129},[63,1342,1343,1345],{"class":65,"line":117},[63,1344,111],{"class":69},[63,1346,1154],{"class":76},[63,1348,1349,1351,1353,1355,1357,1359,1361,1363,1365,1367],{"class":65,"line":133},[63,1350,204],{"class":69},[63,1352,1161],{"class":76},[63,1354,210],{"class":69},[63,1356,165],{"class":129},[63,1358,168],{"class":76},[63,1360,1170],{"class":129},[63,1362,220],{"class":69},[63,1364,1175],{"class":129},[63,1366,168],{"class":76},[63,1368,659],{"class":129},[63,1370,1371,1373,1375,1377,1379,1381,1383,1385,1387,1389],{"class":65,"line":140},[63,1372,204],{"class":69},[63,1374,1189],{"class":76},[63,1376,210],{"class":69},[63,1378,1125],{"class":129},[63,1380,168],{"class":76},[63,1382,171],{"class":129},[63,1384,220],{"class":69},[63,1386,165],{"class":129},[63,1388,168],{"class":76},[63,1390,1391],{"class":129},"to_id\n",[63,1393,1394],{"class":65,"line":149},[63,1395,1396],{"class":89},"  -- CYCLE clause: track id, set is_cycle flag when a cycle is detected\n",[63,1398,1399,1402,1405,1408,1411,1414,1417,1420,1423],{"class":65,"line":154},[63,1400,1401],{"class":76},"  CYCLE id ",[63,1403,1404],{"class":69},"SET",[63,1406,1407],{"class":76}," is_cycle ",[63,1409,1410],{"class":69},"TO",[63,1412,1413],{"class":76}," true ",[63,1415,1416],{"class":69},"DEFAULT",[63,1418,1419],{"class":76}," false ",[63,1421,1422],{"class":69},"USING",[63,1424,577],{"class":69},[63,1426,1427],{"class":65,"line":160},[63,1428,236],{"class":76},[63,1430,1431,1433,1435,1437,1439,1441,1443,1446],{"class":65,"line":193},[63,1432,242],{"class":69},[63,1434,245],{"class":69},[63,1436,248],{"class":69},[63,1438,1062],{"class":76},[63,1440,792],{"class":69},[63,1442,1216],{"class":69},[63,1444,1445],{"class":76}," is_cycle;   ",[63,1447,1448],{"class":89},"-- filter out cyclic rows\n",[14,1450,733,1451,1454,1455,1457,1458,1461,1462,1465],{},[29,1452,1453],{},"CYCLE"," clause automatically detects when ",[29,1456,171],{}," repeats and sets ",[29,1459,1460],{},"is_cycle = true",". ",[29,1463,1464],{},"USING path"," specifies the column to store the traversal path. This is cleaner but PostgreSQL 14+ only.",[264,1467,1469],{"id":1468},"approach-3-union-not-union-all-for-deduplication","Approach 3: UNION (Not UNION ALL) for Deduplication",[51,1471,1472],{"language":53},[55,1473,1475],{"className":57,"code":1474,"language":53,"meta":59,"style":59},"-- UNION (not UNION ALL) dedupes per iteration — slower but can break some cycles\nWITH RECURSIVE walk AS (\n  SELECT id FROM nodes WHERE id = 1\n  UNION   -- ← dedupes: if the same row appears again, it's not \"new\" → eventually stops\n  SELECT n.id\n  FROM walk w JOIN edges e ON e.from_id = w.id JOIN nodes n ON n.id = e.to_id\n)\nSELECT * FROM walk;\n",[29,1476,1477,1482,1494,1512,1520,1530,1579,1583],{"__ignoreMap":59},[63,1478,1479],{"class":65,"line":66},[63,1480,1481],{"class":89},"-- UNION (not UNION ALL) dedupes per iteration — slower but can break some cycles\n",[63,1483,1484,1486,1488,1490,1492],{"class":65,"line":86},[63,1485,70],{"class":69},[63,1487,73],{"class":69},[63,1489,1062],{"class":76},[63,1491,80],{"class":69},[63,1493,83],{"class":76},[63,1495,1496,1498,1500,1502,1504,1506,1508,1510],{"class":65,"line":93},[63,1497,96],{"class":69},[63,1499,123],{"class":76},[63,1501,464],{"class":69},[63,1503,1093],{"class":76},[63,1505,792],{"class":69},[63,1507,123],{"class":76},[63,1509,126],{"class":69},[63,1511,130],{"class":129},[63,1513,1514,1517],{"class":65,"line":108},[63,1515,1516],{"class":69},"  UNION",[63,1518,1519],{"class":89},"   -- ← dedupes: if the same row appears again, it's not \"new\" → eventually stops\n",[63,1521,1522,1524,1526,1528],{"class":65,"line":117},[63,1523,96],{"class":69},[63,1525,1125],{"class":129},[63,1527,168],{"class":76},[63,1529,659],{"class":129},[63,1531,1532,1534,1537,1540,1542,1544,1546,1548,1550,1552,1554,1556,1558,1561,1563,1565,1567,1569,1571,1573,1575,1577],{"class":65,"line":133},[63,1533,111],{"class":69},[63,1535,1536],{"class":76}," walk w ",[63,1538,1539],{"class":69},"JOIN",[63,1541,1161],{"class":76},[63,1543,210],{"class":69},[63,1545,165],{"class":129},[63,1547,168],{"class":76},[63,1549,1170],{"class":129},[63,1551,220],{"class":69},[63,1553,1175],{"class":129},[63,1555,168],{"class":76},[63,1557,171],{"class":129},[63,1559,1560],{"class":69}," JOIN",[63,1562,1189],{"class":76},[63,1564,210],{"class":69},[63,1566,1125],{"class":129},[63,1568,168],{"class":76},[63,1570,171],{"class":129},[63,1572,220],{"class":69},[63,1574,165],{"class":129},[63,1576,168],{"class":76},[63,1578,1391],{"class":129},[63,1580,1581],{"class":65,"line":140},[63,1582,236],{"class":76},[63,1584,1585,1587,1589,1591],{"class":65,"line":149},[63,1586,242],{"class":69},[63,1588,245],{"class":69},[63,1590,248],{"class":69},[63,1592,1254],{"class":76},[14,1594,1595,1598,1599,1601,1602,1604],{},[29,1596,1597],{},"UNION"," dedupes per iteration — if a cycle keeps producing the same rows, they're deduped to zero \"new\" rows and the recursion stops. But ",[29,1600,1597],{}," is slower (sort\u002Fhash per iteration) and doesn't track the path. Use ",[29,1603,31],{}," + explicit cycle detection for production.",[18,1606],{},[21,1608,1610],{"id":1609},"graph-traversal-shortest-path-bfs","Graph Traversal: Shortest Path (BFS)",[51,1612,1613],{"language":53},[55,1614,1616],{"className":57,"code":1615,"language":53,"meta":59,"style":59},"-- Shortest path (in hops) from node 1 to all reachable nodes — BFS via recursive CTE\nWITH RECURSIVE bfs AS (\n  -- Anchor: all nodes directly reachable from node 1\n  SELECT\n    to_id        AS node,\n    1            AS hops,\n    ARRAY[from_id, to_id] AS path   -- path starts with source + first hop\n  FROM edges\n  WHERE from_id = 1\n\n  UNION ALL\n\n  -- Recursive: extend the path by one hop, skip visited nodes (cycle detection)\n  SELECT\n    e.to_id,\n    b.hops + 1,\n    b.path || e.to_id\n  FROM bfs b\n  JOIN edges e ON e.from_id = b.node\n  WHERE NOT e.to_id = ANY(b.path)   -- don't revisit nodes in the current path\n)\n-- The recursive CTE produces all paths; take the minimum hops per node\nSELECT node, MIN(hops) AS min_hops, array_agg(path ORDER BY hops) AS all_paths\nFROM bfs\nGROUP BY node\nORDER BY min_hops;\n",[29,1617,1618,1623,1636,1641,1646,1656,1667,1682,1689,1700,1704,1708,1712,1717,1721,1733,1750,1766,1773,1797,1826,1830,1835,1866,1874,1883],{"__ignoreMap":59},[63,1619,1620],{"class":65,"line":66},[63,1621,1622],{"class":89},"-- Shortest path (in hops) from node 1 to all reachable nodes — BFS via recursive CTE\n",[63,1624,1625,1627,1629,1632,1634],{"class":65,"line":86},[63,1626,70],{"class":69},[63,1628,73],{"class":69},[63,1630,1631],{"class":76}," bfs ",[63,1633,80],{"class":69},[63,1635,83],{"class":76},[63,1637,1638],{"class":65,"line":93},[63,1639,1640],{"class":89},"  -- Anchor: all nodes directly reachable from node 1\n",[63,1642,1643],{"class":65,"line":108},[63,1644,1645],{"class":69},"  SELECT\n",[63,1647,1648,1651,1653],{"class":65,"line":117},[63,1649,1650],{"class":76},"    to_id        ",[63,1652,80],{"class":69},[63,1654,1655],{"class":76}," node,\n",[63,1657,1658,1661,1664],{"class":65,"line":133},[63,1659,1660],{"class":129},"    1",[63,1662,1663],{"class":69},"            AS",[63,1665,1666],{"class":76}," hops,\n",[63,1668,1669,1672,1675,1677,1679],{"class":65,"line":140},[63,1670,1671],{"class":69},"    ARRAY",[63,1673,1674],{"class":76},"[from_id, to_id] ",[63,1676,80],{"class":69},[63,1678,1298],{"class":69},[63,1680,1681],{"class":89},"   -- path starts with source + first hop\n",[63,1683,1684,1686],{"class":65,"line":149},[63,1685,111],{"class":69},[63,1687,1688],{"class":76}," edges\n",[63,1690,1691,1693,1696,1698],{"class":65,"line":154},[63,1692,120],{"class":69},[63,1694,1695],{"class":76}," from_id ",[63,1697,126],{"class":69},[63,1699,130],{"class":129},[63,1701,1702],{"class":65,"line":160},[63,1703,137],{"emptyLinePlaceholder":136},[63,1705,1706],{"class":65,"line":193},[63,1707,364],{"class":69},[63,1709,1710],{"class":65,"line":201},[63,1711,137],{"emptyLinePlaceholder":136},[63,1713,1714],{"class":65,"line":233},[63,1715,1716],{"class":89},"  -- Recursive: extend the path by one hop, skip visited nodes (cycle detection)\n",[63,1718,1719],{"class":65,"line":239},[63,1720,1645],{"class":69},[63,1722,1723,1726,1728,1730],{"class":65,"line":452},[63,1724,1725],{"class":129},"    e",[63,1727,168],{"class":76},[63,1729,1206],{"class":129},[63,1731,1732],{"class":76},",\n",[63,1734,1735,1738,1740,1743,1745,1748],{"class":65,"line":480},[63,1736,1737],{"class":129},"    b",[63,1739,168],{"class":76},[63,1741,1742],{"class":129},"hops",[63,1744,412],{"class":69},[63,1746,1747],{"class":129}," 1",[63,1749,1732],{"class":76},[63,1751,1752,1754,1756,1758,1760,1762,1764],{"class":65,"line":486},[63,1753,1737],{"class":129},[63,1755,168],{"class":76},[63,1757,649],{"class":129},[63,1759,652],{"class":69},[63,1761,165],{"class":129},[63,1763,168],{"class":76},[63,1765,1391],{"class":129},[63,1767,1768,1770],{"class":65,"line":492},[63,1769,111],{"class":69},[63,1771,1772],{"class":76}," bfs b\n",[63,1774,1775,1777,1779,1781,1783,1785,1787,1789,1792,1794],{"class":65,"line":498},[63,1776,204],{"class":69},[63,1778,1161],{"class":76},[63,1780,210],{"class":69},[63,1782,165],{"class":129},[63,1784,168],{"class":76},[63,1786,1170],{"class":129},[63,1788,220],{"class":69},[63,1790,1791],{"class":129}," b",[63,1793,168],{"class":76},[63,1795,1796],{"class":129},"node\n",[63,1798,1799,1801,1803,1805,1807,1809,1811,1813,1816,1818,1820,1823],{"class":65,"line":504},[63,1800,120],{"class":69},[63,1802,1216],{"class":69},[63,1804,165],{"class":129},[63,1806,168],{"class":76},[63,1808,1206],{"class":129},[63,1810,220],{"class":69},[63,1812,1227],{"class":76},[63,1814,1815],{"class":129},"b",[63,1817,168],{"class":76},[63,1819,649],{"class":129},[63,1821,1822],{"class":76},")   ",[63,1824,1825],{"class":89},"-- don't revisit nodes in the current path\n",[63,1827,1828],{"class":65,"line":510},[63,1829,236],{"class":76},[63,1831,1832],{"class":65,"line":1009},[63,1833,1834],{"class":89},"-- The recursive CTE produces all paths; take the minimum hops per node\n",[63,1836,1837,1839,1842,1845,1848,1850,1853,1855,1858,1861,1863],{"class":65,"line":1015},[63,1838,242],{"class":69},[63,1840,1841],{"class":76}," node, ",[63,1843,1844],{"class":129},"MIN",[63,1846,1847],{"class":76},"(hops) ",[63,1849,80],{"class":69},[63,1851,1852],{"class":76}," min_hops, array_agg(",[63,1854,649],{"class":69},[63,1856,1857],{"class":69}," ORDER BY",[63,1859,1860],{"class":76}," hops) ",[63,1862,80],{"class":69},[63,1864,1865],{"class":76}," all_paths\n",[63,1867,1869,1871],{"class":65,"line":1868},24,[63,1870,464],{"class":69},[63,1872,1873],{"class":76}," bfs\n",[63,1875,1877,1880],{"class":65,"line":1876},25,[63,1878,1879],{"class":69},"GROUP BY",[63,1881,1882],{"class":76}," node\n",[63,1884,1886,1888],{"class":65,"line":1885},26,[63,1887,469],{"class":69},[63,1889,1890],{"class":76}," min_hops;\n",[14,1892,1893,1894,1897],{},"This is a breadth-first search expressed as a recursive CTE. Each iteration explores one more hop. The ",[29,1895,1896],{},"MIN(hops)"," in the outer query finds the shortest path. For weighted graphs, recursive CTEs are inefficient — use pgRouting or a dedicated graph algorithm.",[18,1899],{},[21,1901,1903],{"id":1902},"complex-implementation-bill-of-materials-explosion","Complex Implementation: Bill-of-Materials Explosion",[14,1905,1906],{},"A multi-level product decomposition that aggregates quantities through the component tree, with cycle detection for safety:",[51,1908,1909],{"language":53},[55,1910,1912],{"className":57,"code":1911,"language":53,"meta":59,"style":59},"-- ============================================================================\n-- Schema: bill of materials (products composed of sub-components, recursively)\n-- ============================================================================\nCREATE TABLE components (\n  id SERIAL PRIMARY KEY,\n  name TEXT NOT NULL,\n  is_assembly BOOLEAN NOT NULL DEFAULT false   -- true = composed of sub-components\n);\n\nCREATE TABLE bom (\n  assembly_id INT NOT NULL REFERENCES components(id),   -- parent product\n  component_id INT NOT NULL REFERENCES components(id),  -- child component\n  quantity INT NOT NULL DEFAULT 1,                       -- how many of this child per parent\n  PRIMARY KEY (assembly_id, component_id)\n);\n\n-- Sample data: a \"Bicycle\" is made of a frame, wheels (each wheel has a rim + tire)\nINSERT INTO components (id, name, is_assembly) VALUES\n  (1, 'Bicycle', true),\n  (2, 'Frame', false),\n  (3, 'Wheel Assembly', true),\n  (4, 'Rim', false),\n  (5, 'Tire', false),\n  (6, 'Spoke', false);\n\nINSERT INTO bom (assembly_id, component_id, quantity) VALUES\n  (1, 2, 1),    -- 1 Bicycle  → 1 Frame\n  (1, 3, 2),    -- 1 Bicycle  → 2 Wheel Assemblies\n  (3, 4, 1),    -- 1 Wheel    → 1 Rim\n  (3, 5, 1),    -- 1 Wheel    → 1 Tire\n  (3, 6, 12);   -- 1 Wheel    → 12 Spokes\n\n-- ============================================================================\n-- BOM explosion: decompose product 1 (Bicycle) into all leaf components\n-- with total quantities aggregated through the tree\n-- ============================================================================\nWITH RECURSIVE bom_explosion AS (\n  -- Anchor: direct children of the Bicycle, with their per-unit quantity\n  SELECT\n    b.component_id                                   AS component_id,\n    c.name                                           AS component_name,\n    b.quantity                                       AS total_quantity,\n    ARRAY[b.assembly_id, b.component_id]             AS path,   -- cycle detection\n    1                                                AS depth,\n    NOT c.is_assembly                                AS is_leaf\n  FROM bom b\n  JOIN components c ON c.id = b.component_id\n  WHERE b.assembly_id = 1   -- start from the Bicycle\n\n  UNION ALL\n\n  -- Recursive: for each assembly component, explode its sub-components\n  -- Multiply quantity: parent's total × child's per-unit quantity\n  SELECT\n    b.component_id,\n    c.name,\n    be.total_quantity * b.quantity,                   -- aggregate quantity through the tree\n    be.path || b.component_id,                        -- extend the path\n    be.depth + 1,\n    NOT c.is_assembly\n  FROM bom_explosion be\n  JOIN bom b ON b.assembly_id = be.component_id       -- explode assemblies only\n  JOIN components c ON c.id = b.component_id\n  WHERE NOT b.component_id = ANY(be.path)             -- CYCLE DETECTION: skip visited\n)\n-- ============================================================================\n-- Result: all leaf components with their total quantities\n-- ============================================================================\nSELECT\n  component_id,\n  component_name,\n  total_quantity,   -- total number of this component needed for 1 Bicycle\n  depth\nFROM bom_explosion\nWHERE is_leaf       -- only leaf components (raw materials)\nORDER BY depth, component_name;\n--  component_id | component_name | total_quantity | depth\n--  2            | Frame          | 1              | 1\n--  4            | Rim            | 2              | 2      (2 wheels × 1 rim)\n--  5            | Tire           | 2              | 2      (2 wheels × 1 tire)\n--  6            | Spoke          | 24             | 2      (2 wheels × 12 spokes)\n\n-- Full explosion (all levels, including sub-assemblies)\nSELECT component_id, component_name, total_quantity, depth, path\nFROM bom_explosion\nORDER BY depth, component_name;\n\n-- ============================================================================\n-- Aggregation: total cost of a Bicycle from leaf component costs\n-- ============================================================================\nCREATE TABLE component_costs (\n  component_id INT PRIMARY KEY REFERENCES components(id),\n  unit_cost NUMERIC(10,2) NOT NULL\n);\n\nINSERT INTO component_costs VALUES\n  (2, 150.00),   -- Frame: $150\n  (4, 40.00),    -- Rim: $40\n  (5, 25.00),    -- Tire: $25\n  (6, 1.50);     -- Spoke: $1.50\n\nSELECT\n  SUM(be.total_quantity * cc.unit_cost) AS total_cost\nFROM bom_explosion be\nJOIN component_costs cc ON cc.component_id = be.component_id\nWHERE be.is_leaf;\n-- total_cost: 150 + (2×40) + (2×25) + (24×1.50) = 150 + 80 + 50 + 36 = 316.00\n",[29,1913,1914,1919,1924,1928,1942,1955,1968,1987,1992,1996,2007,2026,2043,2062,2070,2074,2078,2083,2099,2116,2131,2145,2159,2173,2188,2192,2201,2222,2242,2262,2282,2304,2309,2314,2320,2326,2331,2345,2351,2356,2372,2388,2404,2422,2433,2452,2460,2485,2504,2509,2514,2519,2525,2531,2536,2547,2558,2583,2606,2621,2633,2641,2669,2692,2722,2727,2732,2738,2743,2749,2755,2761,2770,2776,2784,2795,2803,2809,2815,2821,2827,2833,2838,2844,2855,2862,2869,2874,2879,2885,2890,2902,2916,2942,2947,2952,2962,2985,3006,3027,3049,3054,3059,3090,3097,3121,3135],{"__ignoreMap":59},[63,1915,1916],{"class":65,"line":66},[63,1917,1918],{"class":89},"-- ============================================================================\n",[63,1920,1921],{"class":65,"line":86},[63,1922,1923],{"class":89},"-- Schema: bill of materials (products composed of sub-components, recursively)\n",[63,1925,1926],{"class":65,"line":93},[63,1927,1918],{"class":89},[63,1929,1930,1933,1936,1940],{"class":65,"line":108},[63,1931,1932],{"class":69},"CREATE",[63,1934,1935],{"class":69}," TABLE",[63,1937,1939],{"class":1938},"sIsaT"," components",[63,1941,83],{"class":76},[63,1943,1944,1947,1950,1953],{"class":65,"line":117},[63,1945,1946],{"class":76},"  id ",[63,1948,1949],{"class":69},"SERIAL",[63,1951,1952],{"class":69}," PRIMARY KEY",[63,1954,1732],{"class":76},[63,1956,1957,1960,1963,1966],{"class":65,"line":133},[63,1958,1959],{"class":69},"  name",[63,1961,1962],{"class":69}," TEXT",[63,1964,1965],{"class":69}," NOT NULL",[63,1967,1732],{"class":76},[63,1969,1970,1973,1976,1978,1981,1984],{"class":65,"line":140},[63,1971,1972],{"class":76},"  is_assembly ",[63,1974,1975],{"class":69},"BOOLEAN",[63,1977,1965],{"class":69},[63,1979,1980],{"class":69}," DEFAULT",[63,1982,1983],{"class":76}," false   ",[63,1985,1986],{"class":89},"-- true = composed of sub-components\n",[63,1988,1989],{"class":65,"line":149},[63,1990,1991],{"class":76},");\n",[63,1993,1994],{"class":65,"line":154},[63,1995,137],{"emptyLinePlaceholder":136},[63,1997,1998,2000,2002,2005],{"class":65,"line":160},[63,1999,1932],{"class":69},[63,2001,1935],{"class":69},[63,2003,2004],{"class":1938}," bom",[63,2006,83],{"class":76},[63,2008,2009,2012,2015,2017,2020,2023],{"class":65,"line":193},[63,2010,2011],{"class":76},"  assembly_id ",[63,2013,2014],{"class":69},"INT",[63,2016,1965],{"class":69},[63,2018,2019],{"class":69}," REFERENCES",[63,2021,2022],{"class":76}," components(id),   ",[63,2024,2025],{"class":89},"-- parent product\n",[63,2027,2028,2031,2033,2035,2037,2040],{"class":65,"line":201},[63,2029,2030],{"class":76},"  component_id ",[63,2032,2014],{"class":69},[63,2034,1965],{"class":69},[63,2036,2019],{"class":69},[63,2038,2039],{"class":76}," components(id),  ",[63,2041,2042],{"class":89},"-- child component\n",[63,2044,2045,2048,2050,2052,2054,2056,2059],{"class":65,"line":233},[63,2046,2047],{"class":76},"  quantity ",[63,2049,2014],{"class":69},[63,2051,1965],{"class":69},[63,2053,1980],{"class":69},[63,2055,1747],{"class":129},[63,2057,2058],{"class":76},",                       ",[63,2060,2061],{"class":89},"-- how many of this child per parent\n",[63,2063,2064,2067],{"class":65,"line":239},[63,2065,2066],{"class":69},"  PRIMARY KEY",[63,2068,2069],{"class":76}," (assembly_id, component_id)\n",[63,2071,2072],{"class":65,"line":452},[63,2073,1991],{"class":76},[63,2075,2076],{"class":65,"line":480},[63,2077,137],{"emptyLinePlaceholder":136},[63,2079,2080],{"class":65,"line":486},[63,2081,2082],{"class":89},"-- Sample data: a \"Bicycle\" is made of a frame, wheels (each wheel has a rim + tire)\n",[63,2084,2085,2088,2091,2093,2096],{"class":65,"line":492},[63,2086,2087],{"class":69},"INSERT INTO",[63,2089,2090],{"class":76}," components (id, ",[63,2092,102],{"class":69},[63,2094,2095],{"class":76},", is_assembly) ",[63,2097,2098],{"class":69},"VALUES\n",[63,2100,2101,2104,2107,2109,2113],{"class":65,"line":498},[63,2102,2103],{"class":76},"  (",[63,2105,2106],{"class":129},"1",[63,2108,174],{"class":76},[63,2110,2112],{"class":2111},"sJ6F3","'Bicycle'",[63,2114,2115],{"class":76},", true),\n",[63,2117,2118,2120,2123,2125,2128],{"class":65,"line":504},[63,2119,2103],{"class":76},[63,2121,2122],{"class":129},"2",[63,2124,174],{"class":76},[63,2126,2127],{"class":2111},"'Frame'",[63,2129,2130],{"class":76},", false),\n",[63,2132,2133,2135,2138,2140,2143],{"class":65,"line":510},[63,2134,2103],{"class":76},[63,2136,2137],{"class":129},"3",[63,2139,174],{"class":76},[63,2141,2142],{"class":2111},"'Wheel Assembly'",[63,2144,2115],{"class":76},[63,2146,2147,2149,2152,2154,2157],{"class":65,"line":1009},[63,2148,2103],{"class":76},[63,2150,2151],{"class":129},"4",[63,2153,174],{"class":76},[63,2155,2156],{"class":2111},"'Rim'",[63,2158,2130],{"class":76},[63,2160,2161,2163,2166,2168,2171],{"class":65,"line":1015},[63,2162,2103],{"class":76},[63,2164,2165],{"class":129},"5",[63,2167,174],{"class":76},[63,2169,2170],{"class":2111},"'Tire'",[63,2172,2130],{"class":76},[63,2174,2175,2177,2180,2182,2185],{"class":65,"line":1868},[63,2176,2103],{"class":76},[63,2178,2179],{"class":129},"6",[63,2181,174],{"class":76},[63,2183,2184],{"class":2111},"'Spoke'",[63,2186,2187],{"class":76},", false);\n",[63,2189,2190],{"class":65,"line":1876},[63,2191,137],{"emptyLinePlaceholder":136},[63,2193,2194,2196,2199],{"class":65,"line":1885},[63,2195,2087],{"class":69},[63,2197,2198],{"class":76}," bom (assembly_id, component_id, quantity) ",[63,2200,2098],{"class":69},[63,2202,2204,2206,2208,2210,2212,2214,2216,2219],{"class":65,"line":2203},27,[63,2205,2103],{"class":76},[63,2207,2106],{"class":129},[63,2209,174],{"class":76},[63,2211,2122],{"class":129},[63,2213,174],{"class":76},[63,2215,2106],{"class":129},[63,2217,2218],{"class":76},"),    ",[63,2220,2221],{"class":89},"-- 1 Bicycle  → 1 Frame\n",[63,2223,2225,2227,2229,2231,2233,2235,2237,2239],{"class":65,"line":2224},28,[63,2226,2103],{"class":76},[63,2228,2106],{"class":129},[63,2230,174],{"class":76},[63,2232,2137],{"class":129},[63,2234,174],{"class":76},[63,2236,2122],{"class":129},[63,2238,2218],{"class":76},[63,2240,2241],{"class":89},"-- 1 Bicycle  → 2 Wheel Assemblies\n",[63,2243,2245,2247,2249,2251,2253,2255,2257,2259],{"class":65,"line":2244},29,[63,2246,2103],{"class":76},[63,2248,2137],{"class":129},[63,2250,174],{"class":76},[63,2252,2151],{"class":129},[63,2254,174],{"class":76},[63,2256,2106],{"class":129},[63,2258,2218],{"class":76},[63,2260,2261],{"class":89},"-- 1 Wheel    → 1 Rim\n",[63,2263,2265,2267,2269,2271,2273,2275,2277,2279],{"class":65,"line":2264},30,[63,2266,2103],{"class":76},[63,2268,2137],{"class":129},[63,2270,174],{"class":76},[63,2272,2165],{"class":129},[63,2274,174],{"class":76},[63,2276,2106],{"class":129},[63,2278,2218],{"class":76},[63,2280,2281],{"class":89},"-- 1 Wheel    → 1 Tire\n",[63,2283,2285,2287,2289,2291,2293,2295,2298,2301],{"class":65,"line":2284},31,[63,2286,2103],{"class":76},[63,2288,2137],{"class":129},[63,2290,174],{"class":76},[63,2292,2179],{"class":129},[63,2294,174],{"class":76},[63,2296,2297],{"class":129},"12",[63,2299,2300],{"class":76},");   ",[63,2302,2303],{"class":89},"-- 1 Wheel    → 12 Spokes\n",[63,2305,2307],{"class":65,"line":2306},32,[63,2308,137],{"emptyLinePlaceholder":136},[63,2310,2312],{"class":65,"line":2311},33,[63,2313,1918],{"class":89},[63,2315,2317],{"class":65,"line":2316},34,[63,2318,2319],{"class":89},"-- BOM explosion: decompose product 1 (Bicycle) into all leaf components\n",[63,2321,2323],{"class":65,"line":2322},35,[63,2324,2325],{"class":89},"-- with total quantities aggregated through the tree\n",[63,2327,2329],{"class":65,"line":2328},36,[63,2330,1918],{"class":89},[63,2332,2334,2336,2338,2341,2343],{"class":65,"line":2333},37,[63,2335,70],{"class":69},[63,2337,73],{"class":69},[63,2339,2340],{"class":76}," bom_explosion ",[63,2342,80],{"class":69},[63,2344,83],{"class":76},[63,2346,2348],{"class":65,"line":2347},38,[63,2349,2350],{"class":89},"  -- Anchor: direct children of the Bicycle, with their per-unit quantity\n",[63,2352,2354],{"class":65,"line":2353},39,[63,2355,1645],{"class":69},[63,2357,2359,2361,2363,2366,2369],{"class":65,"line":2358},40,[63,2360,1737],{"class":129},[63,2362,168],{"class":76},[63,2364,2365],{"class":129},"component_id",[63,2367,2368],{"class":69},"                                   AS",[63,2370,2371],{"class":76}," component_id,\n",[63,2373,2375,2378,2380,2382,2385],{"class":65,"line":2374},41,[63,2376,2377],{"class":129},"    c",[63,2379,168],{"class":76},[63,2381,102],{"class":129},[63,2383,2384],{"class":69},"                                           AS",[63,2386,2387],{"class":76}," component_name,\n",[63,2389,2391,2393,2395,2398,2401],{"class":65,"line":2390},42,[63,2392,1737],{"class":129},[63,2394,168],{"class":76},[63,2396,2397],{"class":129},"quantity",[63,2399,2400],{"class":69},"                                       AS",[63,2402,2403],{"class":76}," total_quantity,\n",[63,2405,2407,2409,2412,2414,2416,2419],{"class":65,"line":2406},43,[63,2408,1671],{"class":69},[63,2410,2411],{"class":76},"[b.assembly_id, b.component_id]             ",[63,2413,80],{"class":69},[63,2415,1298],{"class":69},[63,2417,2418],{"class":76},",   ",[63,2420,2421],{"class":89},"-- cycle detection\n",[63,2423,2425,2427,2430],{"class":65,"line":2424},44,[63,2426,1660],{"class":129},[63,2428,2429],{"class":69},"                                                AS",[63,2431,2432],{"class":76}," depth,\n",[63,2434,2436,2439,2441,2443,2446,2449],{"class":65,"line":2435},45,[63,2437,2438],{"class":69},"    NOT",[63,2440,685],{"class":129},[63,2442,168],{"class":76},[63,2444,2445],{"class":129},"is_assembly",[63,2447,2448],{"class":69},"                                AS",[63,2450,2451],{"class":76}," is_leaf\n",[63,2453,2455,2457],{"class":65,"line":2454},46,[63,2456,111],{"class":69},[63,2458,2459],{"class":76}," bom b\n",[63,2461,2463,2465,2468,2470,2472,2474,2476,2478,2480,2482],{"class":65,"line":2462},47,[63,2464,204],{"class":69},[63,2466,2467],{"class":76}," components c ",[63,2469,210],{"class":69},[63,2471,685],{"class":129},[63,2473,168],{"class":76},[63,2475,171],{"class":129},[63,2477,220],{"class":69},[63,2479,1791],{"class":129},[63,2481,168],{"class":76},[63,2483,2484],{"class":129},"component_id\n",[63,2486,2488,2490,2492,2494,2497,2499,2501],{"class":65,"line":2487},48,[63,2489,120],{"class":69},[63,2491,1791],{"class":129},[63,2493,168],{"class":76},[63,2495,2496],{"class":129},"assembly_id",[63,2498,220],{"class":69},[63,2500,1747],{"class":129},[63,2502,2503],{"class":89},"   -- start from the Bicycle\n",[63,2505,2507],{"class":65,"line":2506},49,[63,2508,137],{"emptyLinePlaceholder":136},[63,2510,2512],{"class":65,"line":2511},50,[63,2513,364],{"class":69},[63,2515,2517],{"class":65,"line":2516},51,[63,2518,137],{"emptyLinePlaceholder":136},[63,2520,2522],{"class":65,"line":2521},52,[63,2523,2524],{"class":89},"  -- Recursive: for each assembly component, explode its sub-components\n",[63,2526,2528],{"class":65,"line":2527},53,[63,2529,2530],{"class":89},"  -- Multiply quantity: parent's total × child's per-unit quantity\n",[63,2532,2534],{"class":65,"line":2533},54,[63,2535,1645],{"class":69},[63,2537,2539,2541,2543,2545],{"class":65,"line":2538},55,[63,2540,1737],{"class":129},[63,2542,168],{"class":76},[63,2544,2365],{"class":129},[63,2546,1732],{"class":76},[63,2548,2550,2552,2554,2556],{"class":65,"line":2549},56,[63,2551,2377],{"class":129},[63,2553,168],{"class":76},[63,2555,102],{"class":129},[63,2557,1732],{"class":76},[63,2559,2561,2564,2566,2569,2571,2573,2575,2577,2580],{"class":65,"line":2560},57,[63,2562,2563],{"class":129},"    be",[63,2565,168],{"class":76},[63,2567,2568],{"class":129},"total_quantity",[63,2570,245],{"class":69},[63,2572,1791],{"class":129},[63,2574,168],{"class":76},[63,2576,2397],{"class":129},[63,2578,2579],{"class":76},",                   ",[63,2581,2582],{"class":89},"-- aggregate quantity through the tree\n",[63,2584,2586,2588,2590,2592,2594,2596,2598,2600,2603],{"class":65,"line":2585},58,[63,2587,2563],{"class":129},[63,2589,168],{"class":76},[63,2591,649],{"class":129},[63,2593,652],{"class":69},[63,2595,1791],{"class":129},[63,2597,168],{"class":76},[63,2599,2365],{"class":129},[63,2601,2602],{"class":76},",                        ",[63,2604,2605],{"class":89},"-- extend the path\n",[63,2607,2609,2611,2613,2615,2617,2619],{"class":65,"line":2608},59,[63,2610,2563],{"class":129},[63,2612,168],{"class":76},[63,2614,409],{"class":129},[63,2616,412],{"class":69},[63,2618,1747],{"class":129},[63,2620,1732],{"class":76},[63,2622,2624,2626,2628,2630],{"class":65,"line":2623},60,[63,2625,2438],{"class":69},[63,2627,685],{"class":129},[63,2629,168],{"class":76},[63,2631,2632],{"class":129},"is_assembly\n",[63,2634,2636,2638],{"class":65,"line":2635},61,[63,2637,111],{"class":69},[63,2639,2640],{"class":76}," bom_explosion be\n",[63,2642,2644,2646,2649,2651,2653,2655,2657,2659,2662,2664,2666],{"class":65,"line":2643},62,[63,2645,204],{"class":69},[63,2647,2648],{"class":76}," bom b ",[63,2650,210],{"class":69},[63,2652,1791],{"class":129},[63,2654,168],{"class":76},[63,2656,2496],{"class":129},[63,2658,220],{"class":69},[63,2660,2661],{"class":129}," be",[63,2663,168],{"class":76},[63,2665,2365],{"class":129},[63,2667,2668],{"class":89},"       -- explode assemblies only\n",[63,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690],{"class":65,"line":2671},63,[63,2673,204],{"class":69},[63,2675,2467],{"class":76},[63,2677,210],{"class":69},[63,2679,685],{"class":129},[63,2681,168],{"class":76},[63,2683,171],{"class":129},[63,2685,220],{"class":69},[63,2687,1791],{"class":129},[63,2689,168],{"class":76},[63,2691,2484],{"class":129},[63,2693,2695,2697,2699,2701,2703,2705,2707,2709,2712,2714,2716,2719],{"class":65,"line":2694},64,[63,2696,120],{"class":69},[63,2698,1216],{"class":69},[63,2700,1791],{"class":129},[63,2702,168],{"class":76},[63,2704,2365],{"class":129},[63,2706,220],{"class":69},[63,2708,1227],{"class":76},[63,2710,2711],{"class":129},"be",[63,2713,168],{"class":76},[63,2715,649],{"class":129},[63,2717,2718],{"class":76},")             ",[63,2720,2721],{"class":89},"-- CYCLE DETECTION: skip visited\n",[63,2723,2725],{"class":65,"line":2724},65,[63,2726,236],{"class":76},[63,2728,2730],{"class":65,"line":2729},66,[63,2731,1918],{"class":89},[63,2733,2735],{"class":65,"line":2734},67,[63,2736,2737],{"class":89},"-- Result: all leaf components with their total quantities\n",[63,2739,2741],{"class":65,"line":2740},68,[63,2742,1918],{"class":89},[63,2744,2746],{"class":65,"line":2745},69,[63,2747,2748],{"class":69},"SELECT\n",[63,2750,2752],{"class":65,"line":2751},70,[63,2753,2754],{"class":76},"  component_id,\n",[63,2756,2758],{"class":65,"line":2757},71,[63,2759,2760],{"class":76},"  component_name,\n",[63,2762,2764,2767],{"class":65,"line":2763},72,[63,2765,2766],{"class":76},"  total_quantity,   ",[63,2768,2769],{"class":89},"-- total number of this component needed for 1 Bicycle\n",[63,2771,2773],{"class":65,"line":2772},73,[63,2774,2775],{"class":76},"  depth\n",[63,2777,2779,2781],{"class":65,"line":2778},74,[63,2780,464],{"class":69},[63,2782,2783],{"class":76}," bom_explosion\n",[63,2785,2787,2789,2792],{"class":65,"line":2786},75,[63,2788,792],{"class":69},[63,2790,2791],{"class":76}," is_leaf       ",[63,2793,2794],{"class":89},"-- only leaf components (raw materials)\n",[63,2796,2798,2800],{"class":65,"line":2797},76,[63,2799,469],{"class":69},[63,2801,2802],{"class":76}," depth, component_name;\n",[63,2804,2806],{"class":65,"line":2805},77,[63,2807,2808],{"class":89},"--  component_id | component_name | total_quantity | depth\n",[63,2810,2812],{"class":65,"line":2811},78,[63,2813,2814],{"class":89},"--  2            | Frame          | 1              | 1\n",[63,2816,2818],{"class":65,"line":2817},79,[63,2819,2820],{"class":89},"--  4            | Rim            | 2              | 2      (2 wheels × 1 rim)\n",[63,2822,2824],{"class":65,"line":2823},80,[63,2825,2826],{"class":89},"--  5            | Tire           | 2              | 2      (2 wheels × 1 tire)\n",[63,2828,2830],{"class":65,"line":2829},81,[63,2831,2832],{"class":89},"--  6            | Spoke          | 24             | 2      (2 wheels × 12 spokes)\n",[63,2834,2836],{"class":65,"line":2835},82,[63,2837,137],{"emptyLinePlaceholder":136},[63,2839,2841],{"class":65,"line":2840},83,[63,2842,2843],{"class":89},"-- Full explosion (all levels, including sub-assemblies)\n",[63,2845,2847,2849,2852],{"class":65,"line":2846},84,[63,2848,242],{"class":69},[63,2850,2851],{"class":76}," component_id, component_name, total_quantity, depth, ",[63,2853,2854],{"class":69},"path\n",[63,2856,2858,2860],{"class":65,"line":2857},85,[63,2859,464],{"class":69},[63,2861,2783],{"class":76},[63,2863,2865,2867],{"class":65,"line":2864},86,[63,2866,469],{"class":69},[63,2868,2802],{"class":76},[63,2870,2872],{"class":65,"line":2871},87,[63,2873,137],{"emptyLinePlaceholder":136},[63,2875,2877],{"class":65,"line":2876},88,[63,2878,1918],{"class":89},[63,2880,2882],{"class":65,"line":2881},89,[63,2883,2884],{"class":89},"-- Aggregation: total cost of a Bicycle from leaf component costs\n",[63,2886,2888],{"class":65,"line":2887},90,[63,2889,1918],{"class":89},[63,2891,2893,2895,2897,2900],{"class":65,"line":2892},91,[63,2894,1932],{"class":69},[63,2896,1935],{"class":69},[63,2898,2899],{"class":1938}," component_costs",[63,2901,83],{"class":76},[63,2903,2905,2907,2909,2911,2913],{"class":65,"line":2904},92,[63,2906,2030],{"class":76},[63,2908,2014],{"class":69},[63,2910,1952],{"class":69},[63,2912,2019],{"class":69},[63,2914,2915],{"class":76}," components(id),\n",[63,2917,2919,2922,2925,2928,2931,2934,2936,2939],{"class":65,"line":2918},93,[63,2920,2921],{"class":76},"  unit_cost ",[63,2923,2924],{"class":69},"NUMERIC",[63,2926,2927],{"class":76},"(",[63,2929,2930],{"class":129},"10",[63,2932,2933],{"class":76},",",[63,2935,2122],{"class":129},[63,2937,2938],{"class":76},") ",[63,2940,2941],{"class":69},"NOT NULL\n",[63,2943,2945],{"class":65,"line":2944},94,[63,2946,1991],{"class":76},[63,2948,2950],{"class":65,"line":2949},95,[63,2951,137],{"emptyLinePlaceholder":136},[63,2953,2955,2957,2960],{"class":65,"line":2954},96,[63,2956,2087],{"class":69},[63,2958,2959],{"class":76}," component_costs ",[63,2961,2098],{"class":69},[63,2963,2965,2967,2969,2971,2974,2976,2979,2982],{"class":65,"line":2964},97,[63,2966,2103],{"class":76},[63,2968,2122],{"class":129},[63,2970,174],{"class":76},[63,2972,2973],{"class":129},"150",[63,2975,168],{"class":76},[63,2977,2978],{"class":129},"00",[63,2980,2981],{"class":76},"),   ",[63,2983,2984],{"class":89},"-- Frame: $150\n",[63,2986,2988,2990,2992,2994,2997,2999,3001,3003],{"class":65,"line":2987},98,[63,2989,2103],{"class":76},[63,2991,2151],{"class":129},[63,2993,174],{"class":76},[63,2995,2996],{"class":129},"40",[63,2998,168],{"class":76},[63,3000,2978],{"class":129},[63,3002,2218],{"class":76},[63,3004,3005],{"class":89},"-- Rim: $40\n",[63,3007,3009,3011,3013,3015,3018,3020,3022,3024],{"class":65,"line":3008},99,[63,3010,2103],{"class":76},[63,3012,2165],{"class":129},[63,3014,174],{"class":76},[63,3016,3017],{"class":129},"25",[63,3019,168],{"class":76},[63,3021,2978],{"class":129},[63,3023,2218],{"class":76},[63,3025,3026],{"class":89},"-- Tire: $25\n",[63,3028,3030,3032,3034,3036,3038,3040,3043,3046],{"class":65,"line":3029},100,[63,3031,2103],{"class":76},[63,3033,2179],{"class":129},[63,3035,174],{"class":76},[63,3037,2106],{"class":129},[63,3039,168],{"class":76},[63,3041,3042],{"class":129},"50",[63,3044,3045],{"class":76},");     ",[63,3047,3048],{"class":89},"-- Spoke: $1.50\n",[63,3050,3052],{"class":65,"line":3051},101,[63,3053,137],{"emptyLinePlaceholder":136},[63,3055,3057],{"class":65,"line":3056},102,[63,3058,2748],{"class":69},[63,3060,3062,3065,3067,3069,3071,3073,3075,3078,3080,3083,3085,3087],{"class":65,"line":3061},103,[63,3063,3064],{"class":129},"  SUM",[63,3066,2927],{"class":76},[63,3068,2711],{"class":129},[63,3070,168],{"class":76},[63,3072,2568],{"class":129},[63,3074,245],{"class":69},[63,3076,3077],{"class":129}," cc",[63,3079,168],{"class":76},[63,3081,3082],{"class":129},"unit_cost",[63,3084,2938],{"class":76},[63,3086,80],{"class":69},[63,3088,3089],{"class":76}," total_cost\n",[63,3091,3093,3095],{"class":65,"line":3092},104,[63,3094,464],{"class":69},[63,3096,2640],{"class":76},[63,3098,3100,3102,3105,3107,3109,3111,3113,3115,3117,3119],{"class":65,"line":3099},105,[63,3101,1539],{"class":69},[63,3103,3104],{"class":76}," component_costs cc ",[63,3106,210],{"class":69},[63,3108,3077],{"class":129},[63,3110,168],{"class":76},[63,3112,2365],{"class":129},[63,3114,220],{"class":69},[63,3116,2661],{"class":129},[63,3118,168],{"class":76},[63,3120,2484],{"class":129},[63,3122,3124,3126,3128,3130,3133],{"class":65,"line":3123},106,[63,3125,792],{"class":69},[63,3127,2661],{"class":129},[63,3129,168],{"class":76},[63,3131,3132],{"class":129},"is_leaf",[63,3134,477],{"class":76},[63,3136,3138],{"class":65,"line":3137},107,[63,3139,3140],{"class":89},"-- total_cost: 150 + (2×40) + (2×25) + (24×1.50) = 150 + 80 + 50 + 36 = 316.00\n",[14,3142,3143,3146,3147,3149,3150,3153,3154,3156],{},[40,3144,3145],{},"Key mechanics",": the ",[29,3148,2568],{}," is multiplied at each level (",[29,3151,3152],{},"be.total_quantity * b.quantity","), so a Bicycle needs 2 Wheels, each Wheel needs 12 Spokes, so 24 Spokes total. The ",[29,3155,649],{}," array provides cycle detection — if a component appears in its own ancestry, it's skipped.",[18,3158],{},[21,3160,3162],{"id":3161},"multiple-anchors","Multiple Anchors",[14,3164,3165,3166,32],{},"A recursive CTE can have multiple anchor queries combined with ",[29,3167,31],{},[51,3169,3170],{"language":53},[55,3171,3173],{"className":57,"code":3172,"language":53,"meta":59,"style":59},"-- Find all employees reachable from EITHER employee 1 OR employee 10\nWITH RECURSIVE descendant AS (\n  -- Anchor 1: start from employee 1\n  SELECT id, name, manager_id, 0 AS depth, ARRAY[id] AS path\n  FROM employees WHERE id = 1\n\n  UNION ALL\n\n  -- Anchor 2: start from employee 10 (a different tree root)\n  SELECT id, name, manager_id, 0 AS depth, ARRAY[id] AS path\n  FROM employees WHERE id = 10\n\n  UNION ALL\n\n  -- Recursive case (shared by both anchors)\n  SELECT e.id, e.name, e.manager_id, d.depth + 1, d.path || e.id\n  FROM employees e\n  JOIN descendant d ON e.manager_id = d.id\n  WHERE NOT e.id = ANY(d.path)   -- cycle detection\n)\nSELECT DISTINCT id, name, depth FROM descendant ORDER BY depth, name;\n",[29,3174,3175,3180,3192,3197,3221,3235,3239,3243,3247,3252,3276,3291,3295,3299,3303,3308,3362,3368,3390,3416,3420],{"__ignoreMap":59},[63,3176,3177],{"class":65,"line":66},[63,3178,3179],{"class":89},"-- Find all employees reachable from EITHER employee 1 OR employee 10\n",[63,3181,3182,3184,3186,3188,3190],{"class":65,"line":86},[63,3183,70],{"class":69},[63,3185,73],{"class":69},[63,3187,77],{"class":76},[63,3189,80],{"class":69},[63,3191,83],{"class":76},[63,3193,3194],{"class":65,"line":93},[63,3195,3196],{"class":89},"  -- Anchor 1: start from employee 1\n",[63,3198,3199,3201,3203,3205,3207,3209,3211,3213,3215,3217,3219],{"class":65,"line":108},[63,3200,96],{"class":69},[63,3202,99],{"class":76},[63,3204,102],{"class":69},[63,3206,330],{"class":76},[63,3208,333],{"class":129},[63,3210,336],{"class":69},[63,3212,472],{"class":76},[63,3214,569],{"class":69},[63,3216,572],{"class":76},[63,3218,80],{"class":69},[63,3220,577],{"class":69},[63,3222,3223,3225,3227,3229,3231,3233],{"class":65,"line":117},[63,3224,111],{"class":69},[63,3226,789],{"class":76},[63,3228,792],{"class":69},[63,3230,123],{"class":76},[63,3232,126],{"class":69},[63,3234,130],{"class":129},[63,3236,3237],{"class":65,"line":133},[63,3238,137],{"emptyLinePlaceholder":136},[63,3240,3241],{"class":65,"line":140},[63,3242,364],{"class":69},[63,3244,3245],{"class":65,"line":149},[63,3246,137],{"emptyLinePlaceholder":136},[63,3248,3249],{"class":65,"line":154},[63,3250,3251],{"class":89},"  -- Anchor 2: start from employee 10 (a different tree root)\n",[63,3253,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272,3274],{"class":65,"line":160},[63,3255,96],{"class":69},[63,3257,99],{"class":76},[63,3259,102],{"class":69},[63,3261,330],{"class":76},[63,3263,333],{"class":129},[63,3265,336],{"class":69},[63,3267,472],{"class":76},[63,3269,569],{"class":69},[63,3271,572],{"class":76},[63,3273,80],{"class":69},[63,3275,577],{"class":69},[63,3277,3278,3280,3282,3284,3286,3288],{"class":65,"line":193},[63,3279,111],{"class":69},[63,3281,789],{"class":76},[63,3283,792],{"class":69},[63,3285,123],{"class":76},[63,3287,126],{"class":69},[63,3289,3290],{"class":129}," 10\n",[63,3292,3293],{"class":65,"line":201},[63,3294,137],{"emptyLinePlaceholder":136},[63,3296,3297],{"class":65,"line":233},[63,3298,364],{"class":69},[63,3300,3301],{"class":65,"line":239},[63,3302,137],{"emptyLinePlaceholder":136},[63,3304,3305],{"class":65,"line":452},[63,3306,3307],{"class":89},"  -- Recursive case (shared by both anchors)\n",[63,3309,3310,3312,3314,3316,3318,3320,3322,3324,3326,3328,3330,3332,3334,3336,3338,3340,3342,3344,3346,3348,3350,3352,3354,3356,3358,3360],{"class":65,"line":480},[63,3311,96],{"class":69},[63,3313,165],{"class":129},[63,3315,168],{"class":76},[63,3317,171],{"class":129},[63,3319,174],{"class":76},[63,3321,177],{"class":129},[63,3323,168],{"class":76},[63,3325,102],{"class":129},[63,3327,174],{"class":76},[63,3329,177],{"class":129},[63,3331,168],{"class":76},[63,3333,217],{"class":129},[63,3335,174],{"class":76},[63,3337,404],{"class":129},[63,3339,168],{"class":76},[63,3341,409],{"class":129},[63,3343,412],{"class":69},[63,3345,1747],{"class":129},[63,3347,174],{"class":76},[63,3349,404],{"class":129},[63,3351,168],{"class":76},[63,3353,649],{"class":129},[63,3355,652],{"class":69},[63,3357,165],{"class":129},[63,3359,168],{"class":76},[63,3361,659],{"class":129},[63,3363,3364,3366],{"class":65,"line":486},[63,3365,111],{"class":69},[63,3367,198],{"class":76},[63,3369,3370,3372,3374,3376,3378,3380,3382,3384,3386,3388],{"class":65,"line":492},[63,3371,204],{"class":69},[63,3373,207],{"class":76},[63,3375,210],{"class":69},[63,3377,165],{"class":129},[63,3379,168],{"class":76},[63,3381,217],{"class":129},[63,3383,220],{"class":69},[63,3385,223],{"class":129},[63,3387,168],{"class":76},[63,3389,659],{"class":129},[63,3391,3392,3394,3396,3398,3400,3402,3404,3406,3408,3410,3412,3414],{"class":65,"line":498},[63,3393,120],{"class":69},[63,3395,1216],{"class":69},[63,3397,165],{"class":129},[63,3399,168],{"class":76},[63,3401,171],{"class":129},[63,3403,220],{"class":69},[63,3405,1227],{"class":76},[63,3407,404],{"class":129},[63,3409,168],{"class":76},[63,3411,649],{"class":129},[63,3413,1822],{"class":76},[63,3415,2421],{"class":89},[63,3417,3418],{"class":65,"line":504},[63,3419,236],{"class":76},[63,3421,3422,3425,3427,3429,3431,3433,3435,3437,3439,3441],{"class":65,"line":510},[63,3423,3424],{"class":69},"SELECT DISTINCT",[63,3426,99],{"class":76},[63,3428,102],{"class":69},[63,3430,461],{"class":76},[63,3432,464],{"class":69},[63,3434,77],{"class":76},[63,3436,469],{"class":69},[63,3438,472],{"class":76},[63,3440,102],{"class":69},[63,3442,477],{"class":76},[14,3444,3445],{},"Multiple anchors are useful when the recursion starts from multiple seed nodes (e.g., \"find all descendants of these 5 managers\").",[18,3447],{},[21,3449,3451],{"id":3450},"iterative-computation-fibonacci","Iterative Computation: Fibonacci",[51,3453,3454],{"language":53},[55,3455,3457],{"className":57,"code":3456,"language":53,"meta":59,"style":59},"-- Fibonacci sequence via recursive CTE (iterative computation)\nWITH RECURSIVE fib AS (\n  -- Anchor: base cases F(0)=0, F(1)=1\n  SELECT 0 AS n, 0::bigint AS a, 1::bigint AS b\n\n  UNION ALL\n\n  -- Recursive: shift (a, b) → (b, a+b), increment n\n  SELECT n + 1, b, a + b\n  FROM fib\n  WHERE n \u003C 10   -- termination condition (without it, runs until bigint overflow)\n)\nSELECT n, a FROM fib;\n--  n  | a\n--  0  | 0\n--  1  | 1\n--  2  | 1\n--  3  | 2\n--  4  | 3\n--  5  | 5\n--  6  | 8\n--  7  | 13\n--  8  | 21\n--  9  | 34\n--  10 | 55\n",[29,3458,3459,3464,3477,3482,3518,3522,3526,3530,3535,3554,3561,3576,3580,3592,3597,3602,3607,3612,3617,3622,3627,3632,3637,3642,3647],{"__ignoreMap":59},[63,3460,3461],{"class":65,"line":66},[63,3462,3463],{"class":89},"-- Fibonacci sequence via recursive CTE (iterative computation)\n",[63,3465,3466,3468,3470,3473,3475],{"class":65,"line":86},[63,3467,70],{"class":69},[63,3469,73],{"class":69},[63,3471,3472],{"class":76}," fib ",[63,3474,80],{"class":69},[63,3476,83],{"class":76},[63,3478,3479],{"class":65,"line":93},[63,3480,3481],{"class":89},"  -- Anchor: base cases F(0)=0, F(1)=1\n",[63,3483,3484,3486,3489,3491,3494,3496,3499,3502,3504,3507,3509,3511,3513,3515],{"class":65,"line":108},[63,3485,96],{"class":69},[63,3487,3488],{"class":129}," 0",[63,3490,336],{"class":69},[63,3492,3493],{"class":76}," n, ",[63,3495,333],{"class":129},[63,3497,3498],{"class":76},"::",[63,3500,3501],{"class":69},"bigint",[63,3503,336],{"class":69},[63,3505,3506],{"class":76}," a, ",[63,3508,2106],{"class":129},[63,3510,3498],{"class":76},[63,3512,3501],{"class":69},[63,3514,336],{"class":69},[63,3516,3517],{"class":76}," b\n",[63,3519,3520],{"class":65,"line":117},[63,3521,137],{"emptyLinePlaceholder":136},[63,3523,3524],{"class":65,"line":133},[63,3525,364],{"class":69},[63,3527,3528],{"class":65,"line":140},[63,3529,137],{"emptyLinePlaceholder":136},[63,3531,3532],{"class":65,"line":149},[63,3533,3534],{"class":89},"  -- Recursive: shift (a, b) → (b, a+b), increment n\n",[63,3536,3537,3539,3542,3545,3547,3550,3552],{"class":65,"line":154},[63,3538,96],{"class":69},[63,3540,3541],{"class":76}," n ",[63,3543,3544],{"class":69},"+",[63,3546,1747],{"class":129},[63,3548,3549],{"class":76},", b, a ",[63,3551,3544],{"class":69},[63,3553,3517],{"class":76},[63,3555,3556,3558],{"class":65,"line":160},[63,3557,111],{"class":69},[63,3559,3560],{"class":76}," fib\n",[63,3562,3563,3565,3567,3570,3573],{"class":65,"line":193},[63,3564,120],{"class":69},[63,3566,3541],{"class":76},[63,3568,3569],{"class":69},"\u003C",[63,3571,3572],{"class":129}," 10",[63,3574,3575],{"class":89},"   -- termination condition (without it, runs until bigint overflow)\n",[63,3577,3578],{"class":65,"line":201},[63,3579,236],{"class":76},[63,3581,3582,3584,3587,3589],{"class":65,"line":233},[63,3583,242],{"class":69},[63,3585,3586],{"class":76}," n, a ",[63,3588,464],{"class":69},[63,3590,3591],{"class":76}," fib;\n",[63,3593,3594],{"class":65,"line":239},[63,3595,3596],{"class":89},"--  n  | a\n",[63,3598,3599],{"class":65,"line":452},[63,3600,3601],{"class":89},"--  0  | 0\n",[63,3603,3604],{"class":65,"line":480},[63,3605,3606],{"class":89},"--  1  | 1\n",[63,3608,3609],{"class":65,"line":486},[63,3610,3611],{"class":89},"--  2  | 1\n",[63,3613,3614],{"class":65,"line":492},[63,3615,3616],{"class":89},"--  3  | 2\n",[63,3618,3619],{"class":65,"line":498},[63,3620,3621],{"class":89},"--  4  | 3\n",[63,3623,3624],{"class":65,"line":504},[63,3625,3626],{"class":89},"--  5  | 5\n",[63,3628,3629],{"class":65,"line":510},[63,3630,3631],{"class":89},"--  6  | 8\n",[63,3633,3634],{"class":65,"line":1009},[63,3635,3636],{"class":89},"--  7  | 13\n",[63,3638,3639],{"class":65,"line":1015},[63,3640,3641],{"class":89},"--  8  | 21\n",[63,3643,3644],{"class":65,"line":1868},[63,3645,3646],{"class":89},"--  9  | 34\n",[63,3648,3649],{"class":65,"line":1876},[63,3650,3651],{"class":89},"--  10 | 55\n",[14,3653,733,3654,3657,3658,3661,3662,3664],{},[29,3655,3656],{},"WHERE n \u003C 10"," is the termination condition. Without it, the recursion runs until ",[29,3659,3660],{},"a + b"," overflows ",[29,3663,3501],{}," — a very large number, but eventually an overflow error.",[18,3666],{},[21,3668,3670],{"id":3669},"hierarchical-aggregation-roll-up","Hierarchical Aggregation (Roll-Up)",[51,3672,3673],{"language":53},[55,3674,3676],{"className":57,"code":3675,"language":53,"meta":59,"style":59},"-- Each manager's total sales including their reports' totals (roll-up)\n-- Approach: collect all descendants first, then aggregate in an outer query\n\nWITH RECURSIVE descendant AS (\n  -- Anchor: each employee is their own descendant (for self-inclusion)\n  SELECT id AS root_id, id AS emp_id, manager_id, ARRAY[id] AS path\n  FROM employees\n\n  UNION ALL\n\n  -- Recursive: find reports of the current node, track the root\n  SELECT d.root_id, e.id, e.manager_id, d.path || e.id\n  FROM descendant d\n  JOIN employees e ON e.manager_id = d.emp_id\n  WHERE NOT e.id = ANY(d.path)   -- cycle detection\n)\n-- For each root (manager), sum the sales of all their descendants (including themselves)\nSELECT\n  d.root_id AS manager_id,\n  e.name AS manager_name,\n  SUM(e2.sales) AS total_team_sales\nFROM descendant d\nJOIN employees e  ON e.id  = d.root_id   -- manager\nJOIN employees e2 ON e2.id = d.emp_id    -- each descendant\nGROUP BY d.root_id, e.name\nORDER BY total_team_sales DESC;\n",[29,3677,3678,3683,3688,3692,3704,3709,3733,3739,3743,3747,3751,3756,3799,3806,3830,3856,3860,3865,3869,3883,3897,3918,3924,3951,3979,3998],{"__ignoreMap":59},[63,3679,3680],{"class":65,"line":66},[63,3681,3682],{"class":89},"-- Each manager's total sales including their reports' totals (roll-up)\n",[63,3684,3685],{"class":65,"line":86},[63,3686,3687],{"class":89},"-- Approach: collect all descendants first, then aggregate in an outer query\n",[63,3689,3690],{"class":65,"line":93},[63,3691,137],{"emptyLinePlaceholder":136},[63,3693,3694,3696,3698,3700,3702],{"class":65,"line":108},[63,3695,70],{"class":69},[63,3697,73],{"class":69},[63,3699,77],{"class":76},[63,3701,80],{"class":69},[63,3703,83],{"class":76},[63,3705,3706],{"class":65,"line":117},[63,3707,3708],{"class":89},"  -- Anchor: each employee is their own descendant (for self-inclusion)\n",[63,3710,3711,3713,3715,3717,3720,3722,3725,3727,3729,3731],{"class":65,"line":133},[63,3712,96],{"class":69},[63,3714,123],{"class":76},[63,3716,80],{"class":69},[63,3718,3719],{"class":76}," root_id, id ",[63,3721,80],{"class":69},[63,3723,3724],{"class":76}," emp_id, manager_id, ",[63,3726,569],{"class":69},[63,3728,572],{"class":76},[63,3730,80],{"class":69},[63,3732,577],{"class":69},[63,3734,3735,3737],{"class":65,"line":140},[63,3736,111],{"class":69},[63,3738,114],{"class":76},[63,3740,3741],{"class":65,"line":149},[63,3742,137],{"emptyLinePlaceholder":136},[63,3744,3745],{"class":65,"line":154},[63,3746,364],{"class":69},[63,3748,3749],{"class":65,"line":160},[63,3750,137],{"emptyLinePlaceholder":136},[63,3752,3753],{"class":65,"line":193},[63,3754,3755],{"class":89},"  -- Recursive: find reports of the current node, track the root\n",[63,3757,3758,3760,3762,3764,3767,3769,3771,3773,3775,3777,3779,3781,3783,3785,3787,3789,3791,3793,3795,3797],{"class":65,"line":201},[63,3759,96],{"class":69},[63,3761,223],{"class":129},[63,3763,168],{"class":76},[63,3765,3766],{"class":129},"root_id",[63,3768,174],{"class":76},[63,3770,177],{"class":129},[63,3772,168],{"class":76},[63,3774,171],{"class":129},[63,3776,174],{"class":76},[63,3778,177],{"class":129},[63,3780,168],{"class":76},[63,3782,217],{"class":129},[63,3784,174],{"class":76},[63,3786,404],{"class":129},[63,3788,168],{"class":76},[63,3790,649],{"class":129},[63,3792,652],{"class":69},[63,3794,165],{"class":129},[63,3796,168],{"class":76},[63,3798,659],{"class":129},[63,3800,3801,3803],{"class":65,"line":233},[63,3802,111],{"class":69},[63,3804,3805],{"class":76}," descendant d\n",[63,3807,3808,3810,3813,3815,3817,3819,3821,3823,3825,3827],{"class":65,"line":239},[63,3809,204],{"class":69},[63,3811,3812],{"class":76}," employees e ",[63,3814,210],{"class":69},[63,3816,165],{"class":129},[63,3818,168],{"class":76},[63,3820,217],{"class":129},[63,3822,220],{"class":69},[63,3824,223],{"class":129},[63,3826,168],{"class":76},[63,3828,3829],{"class":129},"emp_id\n",[63,3831,3832,3834,3836,3838,3840,3842,3844,3846,3848,3850,3852,3854],{"class":65,"line":452},[63,3833,120],{"class":69},[63,3835,1216],{"class":69},[63,3837,165],{"class":129},[63,3839,168],{"class":76},[63,3841,171],{"class":129},[63,3843,220],{"class":69},[63,3845,1227],{"class":76},[63,3847,404],{"class":129},[63,3849,168],{"class":76},[63,3851,649],{"class":129},[63,3853,1822],{"class":76},[63,3855,2421],{"class":89},[63,3857,3858],{"class":65,"line":480},[63,3859,236],{"class":76},[63,3861,3862],{"class":65,"line":486},[63,3863,3864],{"class":89},"-- For each root (manager), sum the sales of all their descendants (including themselves)\n",[63,3866,3867],{"class":65,"line":492},[63,3868,2748],{"class":69},[63,3870,3871,3874,3876,3878,3880],{"class":65,"line":498},[63,3872,3873],{"class":129},"  d",[63,3875,168],{"class":76},[63,3877,3766],{"class":129},[63,3879,336],{"class":69},[63,3881,3882],{"class":76}," manager_id,\n",[63,3884,3885,3888,3890,3892,3894],{"class":65,"line":504},[63,3886,3887],{"class":129},"  e",[63,3889,168],{"class":76},[63,3891,102],{"class":129},[63,3893,336],{"class":69},[63,3895,3896],{"class":76}," manager_name,\n",[63,3898,3899,3901,3903,3906,3908,3911,3913,3915],{"class":65,"line":510},[63,3900,3064],{"class":129},[63,3902,2927],{"class":76},[63,3904,3905],{"class":129},"e2",[63,3907,168],{"class":76},[63,3909,3910],{"class":129},"sales",[63,3912,2938],{"class":76},[63,3914,80],{"class":69},[63,3916,3917],{"class":76}," total_team_sales\n",[63,3919,3920,3922],{"class":65,"line":1009},[63,3921,464],{"class":69},[63,3923,3805],{"class":76},[63,3925,3926,3928,3931,3933,3935,3937,3939,3942,3944,3946,3948],{"class":65,"line":1015},[63,3927,1539],{"class":69},[63,3929,3930],{"class":76}," employees e  ",[63,3932,210],{"class":69},[63,3934,165],{"class":129},[63,3936,168],{"class":76},[63,3938,171],{"class":129},[63,3940,3941],{"class":69},"  =",[63,3943,223],{"class":129},[63,3945,168],{"class":76},[63,3947,3766],{"class":129},[63,3949,3950],{"class":89},"   -- manager\n",[63,3952,3953,3955,3958,3960,3963,3965,3967,3969,3971,3973,3976],{"class":65,"line":1868},[63,3954,1539],{"class":69},[63,3956,3957],{"class":76}," employees e2 ",[63,3959,210],{"class":69},[63,3961,3962],{"class":129}," e2",[63,3964,168],{"class":76},[63,3966,171],{"class":129},[63,3968,220],{"class":69},[63,3970,223],{"class":129},[63,3972,168],{"class":76},[63,3974,3975],{"class":129},"emp_id",[63,3977,3978],{"class":89},"    -- each descendant\n",[63,3980,3981,3983,3985,3987,3989,3991,3993,3995],{"class":65,"line":1876},[63,3982,1879],{"class":69},[63,3984,223],{"class":129},[63,3986,168],{"class":76},[63,3988,3766],{"class":129},[63,3990,174],{"class":76},[63,3992,177],{"class":129},[63,3994,168],{"class":76},[63,3996,3997],{"class":129},"name\n",[63,3999,4000,4002,4005,4008],{"class":65,"line":1885},[63,4001,469],{"class":69},[63,4003,4004],{"class":76}," total_team_sales ",[63,4006,4007],{"class":69},"DESC",[63,4009,477],{"class":76},[14,4011,4012,4015,4016,4018],{},[40,4013,4014],{},"Why not aggregate inside the recursion?"," ",[29,4017,1879],{},"\u002Faggregates in the recursive case are legal but tricky — the aggregation happens per iteration, not across all iterations. The clean approach: collect all descendants in the recursive CTE, then aggregate in the outer query.",[18,4020],{},[21,4022,4024],{"id":4023},"recursion-limits-and-safety","Recursion Limits and Safety",[14,4026,4027,4028,4034],{},"PostgreSQL has ",[40,4029,4030,4031],{},"no built-in ",[29,4032,4033],{},"max_recursion_depth"," (unlike SQL Server's default 100). An infinite recursion runs until it exhausts memory or hits a statement timeout.",[51,4036,4037],{"language":53},[55,4038,4040],{"className":57,"code":4039,"language":53,"meta":59,"style":59},"-- Safety net: set a statement timeout before running recursive CTEs on untrusted data\nSET statement_timeout = '10s';\n\n-- Limit depth explicitly (for tree traversal where you know the max useful depth)\nWITH RECURSIVE descendant AS (\n  SELECT id, name, manager_id, 0 AS depth FROM employees WHERE id = 1\n  UNION ALL\n  SELECT e.id, e.name, e.manager_id, d.depth + 1\n  FROM employees e JOIN descendant d ON e.manager_id = d.id\n  WHERE d.depth \u003C 10   -- hard depth limit: stop at level 10\n)\nSELECT * FROM descendant;\n",[29,4041,4042,4047,4061,4065,4070,4082,4111,4115,4153,4179,4197,4201],{"__ignoreMap":59},[63,4043,4044],{"class":65,"line":66},[63,4045,4046],{"class":89},"-- Safety net: set a statement timeout before running recursive CTEs on untrusted data\n",[63,4048,4049,4051,4054,4056,4059],{"class":65,"line":86},[63,4050,1404],{"class":69},[63,4052,4053],{"class":76}," statement_timeout ",[63,4055,126],{"class":69},[63,4057,4058],{"class":2111}," '10s'",[63,4060,477],{"class":76},[63,4062,4063],{"class":65,"line":93},[63,4064,137],{"emptyLinePlaceholder":136},[63,4066,4067],{"class":65,"line":108},[63,4068,4069],{"class":89},"-- Limit depth explicitly (for tree traversal where you know the max useful depth)\n",[63,4071,4072,4074,4076,4078,4080],{"class":65,"line":117},[63,4073,70],{"class":69},[63,4075,73],{"class":69},[63,4077,77],{"class":76},[63,4079,80],{"class":69},[63,4081,83],{"class":76},[63,4083,4084,4086,4088,4090,4092,4094,4096,4099,4101,4103,4105,4107,4109],{"class":65,"line":133},[63,4085,96],{"class":69},[63,4087,99],{"class":76},[63,4089,102],{"class":69},[63,4091,330],{"class":76},[63,4093,333],{"class":129},[63,4095,336],{"class":69},[63,4097,4098],{"class":76}," depth ",[63,4100,464],{"class":69},[63,4102,789],{"class":76},[63,4104,792],{"class":69},[63,4106,123],{"class":76},[63,4108,126],{"class":69},[63,4110,130],{"class":129},[63,4112,4113],{"class":65,"line":140},[63,4114,364],{"class":69},[63,4116,4117,4119,4121,4123,4125,4127,4129,4131,4133,4135,4137,4139,4141,4143,4145,4147,4149,4151],{"class":65,"line":149},[63,4118,96],{"class":69},[63,4120,165],{"class":129},[63,4122,168],{"class":76},[63,4124,171],{"class":129},[63,4126,174],{"class":76},[63,4128,177],{"class":129},[63,4130,168],{"class":76},[63,4132,102],{"class":129},[63,4134,174],{"class":76},[63,4136,177],{"class":129},[63,4138,168],{"class":76},[63,4140,217],{"class":129},[63,4142,174],{"class":76},[63,4144,404],{"class":129},[63,4146,168],{"class":76},[63,4148,409],{"class":129},[63,4150,412],{"class":69},[63,4152,130],{"class":129},[63,4154,4155,4157,4159,4161,4163,4165,4167,4169,4171,4173,4175,4177],{"class":65,"line":154},[63,4156,111],{"class":69},[63,4158,3812],{"class":76},[63,4160,1539],{"class":69},[63,4162,207],{"class":76},[63,4164,210],{"class":69},[63,4166,165],{"class":129},[63,4168,168],{"class":76},[63,4170,217],{"class":129},[63,4172,220],{"class":69},[63,4174,223],{"class":129},[63,4176,168],{"class":76},[63,4178,659],{"class":129},[63,4180,4181,4183,4185,4187,4189,4192,4194],{"class":65,"line":160},[63,4182,120],{"class":69},[63,4184,223],{"class":129},[63,4186,168],{"class":76},[63,4188,409],{"class":129},[63,4190,4191],{"class":69}," \u003C",[63,4193,3572],{"class":129},[63,4195,4196],{"class":89},"   -- hard depth limit: stop at level 10\n",[63,4198,4199],{"class":65,"line":193},[63,4200,236],{"class":76},[63,4202,4203,4205,4207,4209],{"class":65,"line":201},[63,4204,242],{"class":69},[63,4206,245],{"class":69},[63,4208,248],{"class":69},[63,4210,251],{"class":76},[14,4212,4213,4214,4216,4217,4219],{},"Always include a termination condition (a ",[29,4215,792],{}," that eventually produces zero rows) AND set ",[29,4218,1041],{}," as a safety net.",[18,4221],{},[21,4223,4225],{"id":4224},"union-vs-union-all-in-recursion","UNION vs UNION ALL in Recursion",[4227,4228,4229,4247],"table",{},[4230,4231,4232],"thead",{},[4233,4234,4235,4239,4243],"tr",{},[4236,4237,4238],"th",{},"Property",[4236,4240,4241],{},[29,4242,31],{},[4236,4244,4245],{},[29,4246,1597],{},[4248,4249,4250,4262,4273,4284],"tbody",{},[4233,4251,4252,4256,4259],{},[4253,4254,4255],"td",{},"Deduplication",[4253,4257,4258],{},"No (keeps all rows)",[4253,4260,4261],{},"Yes (per iteration)",[4233,4263,4264,4267,4270],{},[4253,4265,4266],{},"Speed",[4253,4268,4269],{},"Faster (no sort\u002Fhash)",[4253,4271,4272],{},"Slower (sort\u002Fhash per iteration)",[4233,4274,4275,4278,4281],{},[4253,4276,4277],{},"Cycle prevention",[4253,4279,4280],{},"❌ No",[4253,4282,4283],{},"✅ Partial (same rows → stop)",[4233,4285,4286,4289,4292],{},[4253,4287,4288],{},"Use case",[4253,4290,4291],{},"Trees (no duplicates)",[4253,4293,4294],{},"Graphs with simple cycles",[14,4296,4297,4298,4300,4301,4303,4304,4306],{},"Use ",[29,4299,31],{}," (keeps duplicates) — it's faster and the usual choice. ",[29,4302,1597],{}," (dedupes) is slower but can prevent some infinite loops (a cycle that keeps producing the same rows won't grow). For tree traversal (no duplicates expected), ",[29,4305,31],{}," is correct and fast.",[18,4308],{},[21,4310,4312],{"id":4311},"anti-pattern-infinite-recursion-on-cyclic-graphs","Anti-Pattern: Infinite Recursion on Cyclic Graphs",[51,4314,4315],{"language":53},[55,4316,4318],{"className":57,"code":4317,"language":53,"meta":59,"style":59},"-- ❌ WRONG: no cycle detection on a graph with a cycle (a→b→c→a)\nWITH RECURSIVE walk AS (\n  SELECT id FROM nodes WHERE id = 1\n  UNION ALL\n  SELECT n.id\n  FROM walk w\n  JOIN edges e ON e.from_id = w.id\n  JOIN nodes n ON n.id = e.to_id\n  -- No cycle detection → if the graph has a cycle, this runs FOREVER\n)\nSELECT * FROM walk;\n-- Eventually: out of memory, or statement_timeout kills it\n\n-- ✅ RIGHT: track visited nodes, skip already-visited\nWITH RECURSIVE walk AS (\n  SELECT id, ARRAY[id] AS visited\n  FROM nodes WHERE id = 1\n  UNION ALL\n  SELECT n.id, w.visited || n.id\n  FROM walk w\n  JOIN edges e ON e.from_id = w.id\n  JOIN nodes n ON n.id = e.to_id\n  WHERE NOT n.id = ANY(w.visited)   -- CYCLE DETECTION\n)\nSELECT * FROM walk;\n",[29,4319,4320,4325,4337,4355,4359,4369,4375,4397,4419,4424,4428,4438,4443,4447,4452,4464,4478,4492,4496,4522,4528,4550,4572,4599,4603],{"__ignoreMap":59},[63,4321,4322],{"class":65,"line":66},[63,4323,4324],{"class":89},"-- ❌ WRONG: no cycle detection on a graph with a cycle (a→b→c→a)\n",[63,4326,4327,4329,4331,4333,4335],{"class":65,"line":86},[63,4328,70],{"class":69},[63,4330,73],{"class":69},[63,4332,1062],{"class":76},[63,4334,80],{"class":69},[63,4336,83],{"class":76},[63,4338,4339,4341,4343,4345,4347,4349,4351,4353],{"class":65,"line":93},[63,4340,96],{"class":69},[63,4342,123],{"class":76},[63,4344,464],{"class":69},[63,4346,1093],{"class":76},[63,4348,792],{"class":69},[63,4350,123],{"class":76},[63,4352,126],{"class":69},[63,4354,130],{"class":129},[63,4356,4357],{"class":65,"line":108},[63,4358,364],{"class":69},[63,4360,4361,4363,4365,4367],{"class":65,"line":117},[63,4362,96],{"class":69},[63,4364,1125],{"class":129},[63,4366,168],{"class":76},[63,4368,659],{"class":129},[63,4370,4371,4373],{"class":65,"line":133},[63,4372,111],{"class":69},[63,4374,1154],{"class":76},[63,4376,4377,4379,4381,4383,4385,4387,4389,4391,4393,4395],{"class":65,"line":140},[63,4378,204],{"class":69},[63,4380,1161],{"class":76},[63,4382,210],{"class":69},[63,4384,165],{"class":129},[63,4386,168],{"class":76},[63,4388,1170],{"class":129},[63,4390,220],{"class":69},[63,4392,1175],{"class":129},[63,4394,168],{"class":76},[63,4396,659],{"class":129},[63,4398,4399,4401,4403,4405,4407,4409,4411,4413,4415,4417],{"class":65,"line":149},[63,4400,204],{"class":69},[63,4402,1189],{"class":76},[63,4404,210],{"class":69},[63,4406,1125],{"class":129},[63,4408,168],{"class":76},[63,4410,171],{"class":129},[63,4412,220],{"class":69},[63,4414,165],{"class":129},[63,4416,168],{"class":76},[63,4418,1391],{"class":129},[63,4420,4421],{"class":65,"line":154},[63,4422,4423],{"class":89},"  -- No cycle detection → if the graph has a cycle, this runs FOREVER\n",[63,4425,4426],{"class":65,"line":160},[63,4427,236],{"class":76},[63,4429,4430,4432,4434,4436],{"class":65,"line":193},[63,4431,242],{"class":69},[63,4433,245],{"class":69},[63,4435,248],{"class":69},[63,4437,1254],{"class":76},[63,4439,4440],{"class":65,"line":201},[63,4441,4442],{"class":89},"-- Eventually: out of memory, or statement_timeout kills it\n",[63,4444,4445],{"class":65,"line":233},[63,4446,137],{"emptyLinePlaceholder":136},[63,4448,4449],{"class":65,"line":239},[63,4450,4451],{"class":89},"-- ✅ RIGHT: track visited nodes, skip already-visited\n",[63,4453,4454,4456,4458,4460,4462],{"class":65,"line":452},[63,4455,70],{"class":69},[63,4457,73],{"class":69},[63,4459,1062],{"class":76},[63,4461,80],{"class":69},[63,4463,83],{"class":76},[63,4465,4466,4468,4470,4472,4474,4476],{"class":65,"line":480},[63,4467,96],{"class":69},[63,4469,99],{"class":76},[63,4471,569],{"class":69},[63,4473,572],{"class":76},[63,4475,80],{"class":69},[63,4477,1086],{"class":76},[63,4479,4480,4482,4484,4486,4488,4490],{"class":65,"line":486},[63,4481,111],{"class":69},[63,4483,1093],{"class":76},[63,4485,792],{"class":69},[63,4487,123],{"class":76},[63,4489,126],{"class":69},[63,4491,130],{"class":129},[63,4493,4494],{"class":65,"line":492},[63,4495,364],{"class":69},[63,4497,4498,4500,4502,4504,4506,4508,4510,4512,4514,4516,4518,4520],{"class":65,"line":498},[63,4499,96],{"class":69},[63,4501,1125],{"class":129},[63,4503,168],{"class":76},[63,4505,171],{"class":129},[63,4507,174],{"class":76},[63,4509,1134],{"class":129},[63,4511,168],{"class":76},[63,4513,1139],{"class":129},[63,4515,652],{"class":69},[63,4517,1125],{"class":129},[63,4519,168],{"class":76},[63,4521,659],{"class":129},[63,4523,4524,4526],{"class":65,"line":504},[63,4525,111],{"class":69},[63,4527,1154],{"class":76},[63,4529,4530,4532,4534,4536,4538,4540,4542,4544,4546,4548],{"class":65,"line":510},[63,4531,204],{"class":69},[63,4533,1161],{"class":76},[63,4535,210],{"class":69},[63,4537,165],{"class":129},[63,4539,168],{"class":76},[63,4541,1170],{"class":129},[63,4543,220],{"class":69},[63,4545,1175],{"class":129},[63,4547,168],{"class":76},[63,4549,659],{"class":129},[63,4551,4552,4554,4556,4558,4560,4562,4564,4566,4568,4570],{"class":65,"line":1009},[63,4553,204],{"class":69},[63,4555,1189],{"class":76},[63,4557,210],{"class":69},[63,4559,1125],{"class":129},[63,4561,168],{"class":76},[63,4563,171],{"class":129},[63,4565,220],{"class":69},[63,4567,165],{"class":129},[63,4569,168],{"class":76},[63,4571,1391],{"class":129},[63,4573,4574,4576,4578,4580,4582,4584,4586,4588,4590,4592,4594,4596],{"class":65,"line":1015},[63,4575,120],{"class":69},[63,4577,1216],{"class":69},[63,4579,1125],{"class":129},[63,4581,168],{"class":76},[63,4583,171],{"class":129},[63,4585,220],{"class":69},[63,4587,1227],{"class":76},[63,4589,1134],{"class":129},[63,4591,168],{"class":76},[63,4593,1139],{"class":129},[63,4595,1822],{"class":76},[63,4597,4598],{"class":89},"-- CYCLE DETECTION\n",[63,4600,4601],{"class":65,"line":1868},[63,4602,236],{"class":76},[63,4604,4605,4607,4609,4611],{"class":65,"line":1876},[63,4606,242],{"class":69},[63,4608,245],{"class":69},[63,4610,248],{"class":69},[63,4612,1254],{"class":76},[14,4614,4615,4618],{},[40,4616,4617],{},"Why the wrong way fails",": in a cyclic graph, each iteration re-visits nodes and produces their edges again. The working set never empties — the recursion runs until memory is exhausted or a timeout kills it.",[18,4620],{},[21,4622,4624],{"id":4623},"tips-tricks","💡 Tips & Tricks",[4626,4627,4628,4639,4647,4656,4670,4687,4692],"ul",{},[37,4629,4630,4633,4634,4636,4637,522],{},[40,4631,4632],{},"Idiom"," — add a ",[29,4635,409],{}," column to recursive CTEs for tree traversal: it's a free byproduct (increment in the recursive case) and invaluable for limiting depth (",[29,4638,521],{},[37,4640,4641,4643,4644,4646],{},[40,4642,4632],{}," — track the ",[29,4645,649],{}," (array of IDs from root to current node) in recursive traversals: it's the cycle-detection mechanism, the debugging aid (see the full chain), and the basis for shortest-path queries.",[37,4648,4649,4652,4653,4655],{},[40,4650,4651],{},"Safety"," — set ",[29,4654,1041],{}," before running recursive CTEs on data that might have cycles: it's the safety net that prevents an infinite recursion from hanging the session.",[37,4657,4658,4661,4662,4665,4666,4669],{},[40,4659,4660],{},"Performance"," — recursive CTEs are ",[40,4663,4664],{},"iterative, not truly recursive",": each iteration is a full query, and the planner can't always optimize across iterations. For deep trees or large graphs, profile with ",[29,4667,4668],{},"EXPLAIN ANALYZE"," and consider a procedural loop with a temp table.",[37,4671,4672,4674,4675,4677,4678,4680,4681,4683,4684,4686],{},[40,4673,4632],{}," — use ",[29,4676,31],{}," (not ",[29,4679,1597],{},") for tree traversal: trees don't have duplicates, so ",[29,4682,31],{}," is faster (no dedup overhead) and correct. Use ",[29,4685,1597],{}," only when you need per-iteration deduplication for simple cycle prevention.",[37,4688,4689,4691],{},[40,4690,4632],{}," — collect all descendants in the recursive CTE, then aggregate in the outer query: don't try to aggregate inside the recursion (aggregates happen per iteration, not across all iterations).",[37,4693,4694,4697,4698,4701,4702,4705,4706,4709,4710,4712],{},[40,4695,4696],{},"Portability"," — ",[29,4699,4700],{},"WITH RECURSIVE"," is standard SQL (PostgreSQL, MySQL 8.0+, SQLite 3.8+). SQL Server infers recursion without the ",[29,4703,4704],{},"RECURSIVE"," keyword. Oracle uses ",[29,4707,4708],{},"CONNECT BY",". The ",[29,4711,1453],{}," clause is PostgreSQL 14+\u002Fstandard.",[18,4714],{},[21,4716,4718],{"id":4717},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[4626,4720,4721,4737,4751,4757,4765,4773,4785,4796,4802,4816,4828],{},[37,4722,4723,4726,4727,4729,4730,4733,4734,4736],{},[40,4724,4725],{},"Infinite recursion on cycles",": a graph cycle (a→b→a) recurses forever. Use a ",[29,4728,649],{}," array with ",[29,4731,4732],{},"WHERE NOT id = ANY(path)"," or the ",[29,4735,1453],{}," clause (PostgreSQL 14+).",[37,4738,4739,4745,4746,4748,4749,168],{},[40,4740,4741,4742,4744],{},"No ",[29,4743,4033],{}," in PostgreSQL",": unlike SQL Server (default 100), PostgreSQL recurses until termination or resource exhaustion. Always include a ",[29,4747,792],{}," termination condition and\u002For ",[29,4750,1041],{},[37,4752,4753,4756],{},[40,4754,4755],{},"The anchor and recursive cases must have matching columns",": same number, compatible types. Column names come from the anchor.",[37,4758,4759,4764],{},[40,4760,4761,4763],{},[29,4762,1879],{},"\u002Faggregates in the recursive case",": legal but tricky — aggregation happens per iteration, not across all iterations. For roll-up, collect descendants first, then aggregate in the outer query.",[37,4766,4767,4772],{},[40,4768,4769,4771],{},[29,4770,469],{}," in the recursive case is ignored",": order within each iteration is unspecified. Order the final result in the outer query.",[37,4774,4775,4781,4782,4784],{},[40,4776,4777,4780],{},[29,4778,4779],{},"LIMIT"," in the recursive case doesn't limit the recursion",": it limits each iteration's output, not the total. Use ",[29,4783,4779],{}," in the outer query.",[37,4786,4787,4792,4793,4795],{},[40,4788,4789,4791],{},[29,4790,4700],{}," keyword",": forgetting ",[29,4794,4704],{}," on a self-referencing CTE is a syntax error in PostgreSQL. (SQL Server infers recursion without the keyword; MySQL requires it.)",[37,4797,4798,4801],{},[40,4799,4800],{},"The recursive case sees only the previous iteration's output",": not the accumulated set. This is \"linear recursion\" per the standard.",[37,4803,4804,4807,4808,4811,4812,4815],{},[40,4805,4806],{},"Recursive CTEs are always materialized",": they can't be ",[29,4809,4810],{},"MATERIALIZED","\u002F",[29,4813,4814],{},"NOT MATERIALIZED"," in PostgreSQL — each iteration depends on the prior's output, so materialization is inherent.",[37,4817,4818,4821,4822,4824,4825,4827],{},[40,4819,4820],{},"NULL in the recursive join",": if ",[29,4823,217],{}," is NULL (root nodes), the join ",[29,4826,1024],{}," won't match them — roots are only included via the anchor, not the recursive case.",[37,4829,4830,4833,4834,4836,4837,4839,4840,4842],{},[40,4831,4832],{},"Self-referencing table edge case",": if the anchor and recursive case can produce the same row, ",[29,4835,31],{}," includes it twice. Use ",[29,4838,1597],{}," or a ",[29,4841,649],{}," check if uniqueness matters.",[18,4844],{},[21,4846,4848],{"id":4847},"spot-the-bug","🧠 Spot the Bug",[14,4850,4851],{},"This recursive query to find all descendants of employee 1 runs on a hierarchy with a data cycle (a misguided data fix created employee 3's manager as employee 5, and employee 5's manager as employee 3). The query hangs and never returns.",[51,4853,4854],{"language":53},[55,4855,4857],{"className":57,"code":4856,"language":53,"meta":59,"style":59},"WITH RECURSIVE descendant AS (\n  SELECT id, name, manager_id FROM employees WHERE id = 1\n  UNION ALL\n  SELECT e.id, e.name, e.manager_id\n  FROM employees e\n  JOIN descendant d ON e.manager_id = d.id\n)\nSELECT * FROM descendant;\n",[29,4858,4859,4871,4893,4897,4923,4929,4951,4955],{"__ignoreMap":59},[63,4860,4861,4863,4865,4867,4869],{"class":65,"line":66},[63,4862,70],{"class":69},[63,4864,73],{"class":69},[63,4866,77],{"class":76},[63,4868,80],{"class":69},[63,4870,83],{"class":76},[63,4872,4873,4875,4877,4879,4881,4883,4885,4887,4889,4891],{"class":65,"line":86},[63,4874,96],{"class":69},[63,4876,99],{"class":76},[63,4878,102],{"class":69},[63,4880,784],{"class":76},[63,4882,464],{"class":69},[63,4884,789],{"class":76},[63,4886,792],{"class":69},[63,4888,123],{"class":76},[63,4890,126],{"class":69},[63,4892,130],{"class":129},[63,4894,4895],{"class":65,"line":93},[63,4896,364],{"class":69},[63,4898,4899,4901,4903,4905,4907,4909,4911,4913,4915,4917,4919,4921],{"class":65,"line":108},[63,4900,96],{"class":69},[63,4902,165],{"class":129},[63,4904,168],{"class":76},[63,4906,171],{"class":129},[63,4908,174],{"class":76},[63,4910,177],{"class":129},[63,4912,168],{"class":76},[63,4914,102],{"class":129},[63,4916,174],{"class":76},[63,4918,177],{"class":129},[63,4920,168],{"class":76},[63,4922,190],{"class":129},[63,4924,4925,4927],{"class":65,"line":117},[63,4926,111],{"class":69},[63,4928,198],{"class":76},[63,4930,4931,4933,4935,4937,4939,4941,4943,4945,4947,4949],{"class":65,"line":133},[63,4932,204],{"class":69},[63,4934,207],{"class":76},[63,4936,210],{"class":69},[63,4938,165],{"class":129},[63,4940,168],{"class":76},[63,4942,217],{"class":129},[63,4944,220],{"class":69},[63,4946,223],{"class":129},[63,4948,168],{"class":76},[63,4950,659],{"class":129},[63,4952,4953],{"class":65,"line":140},[63,4954,236],{"class":76},[63,4956,4957,4959,4961,4963],{"class":65,"line":149},[63,4958,242],{"class":69},[63,4960,245],{"class":69},[63,4962,248],{"class":69},[63,4964,251],{"class":76},[4966,4967,4968,4972,4975,5150,5294,5323],"details",{},[4969,4970,4971],"summary",{},"Answer",[14,4973,4974],{},"There's no cycle detection. If the hierarchy has a cycle (employee 3 → manager 5 → manager 3 → ...), the recursion never terminates — each iteration keeps finding the cycle's rows, producing them again, forever.",[51,4976,4977],{"language":53},[55,4978,4980],{"className":57,"code":4979,"language":53,"meta":59,"style":59},"-- ✅ Fix 1: track visited nodes with a path array\nWITH RECURSIVE descendant AS (\n  SELECT id, name, manager_id, ARRAY[id] AS visited\n  FROM employees WHERE id = 1\n  UNION ALL\n  SELECT e.id, e.name, e.manager_id, d.visited || e.id\n  FROM employees e\n  JOIN descendant d ON e.manager_id = d.id\n  WHERE NOT e.id = ANY(d.visited)   -- skip if already in the chain\n)\nSELECT id, name, manager_id FROM descendant;\n",[29,4981,4982,4987,4999,5017,5031,5035,5077,5083,5105,5132,5136],{"__ignoreMap":59},[63,4983,4984],{"class":65,"line":66},[63,4985,4986],{"class":89},"-- ✅ Fix 1: track visited nodes with a path array\n",[63,4988,4989,4991,4993,4995,4997],{"class":65,"line":86},[63,4990,70],{"class":69},[63,4992,73],{"class":69},[63,4994,77],{"class":76},[63,4996,80],{"class":69},[63,4998,83],{"class":76},[63,5000,5001,5003,5005,5007,5009,5011,5013,5015],{"class":65,"line":93},[63,5002,96],{"class":69},[63,5004,99],{"class":76},[63,5006,102],{"class":69},[63,5008,330],{"class":76},[63,5010,569],{"class":69},[63,5012,572],{"class":76},[63,5014,80],{"class":69},[63,5016,1086],{"class":76},[63,5018,5019,5021,5023,5025,5027,5029],{"class":65,"line":108},[63,5020,111],{"class":69},[63,5022,789],{"class":76},[63,5024,792],{"class":69},[63,5026,123],{"class":76},[63,5028,126],{"class":69},[63,5030,130],{"class":129},[63,5032,5033],{"class":65,"line":117},[63,5034,364],{"class":69},[63,5036,5037,5039,5041,5043,5045,5047,5049,5051,5053,5055,5057,5059,5061,5063,5065,5067,5069,5071,5073,5075],{"class":65,"line":133},[63,5038,96],{"class":69},[63,5040,165],{"class":129},[63,5042,168],{"class":76},[63,5044,171],{"class":129},[63,5046,174],{"class":76},[63,5048,177],{"class":129},[63,5050,168],{"class":76},[63,5052,102],{"class":129},[63,5054,174],{"class":76},[63,5056,177],{"class":129},[63,5058,168],{"class":76},[63,5060,217],{"class":129},[63,5062,174],{"class":76},[63,5064,404],{"class":129},[63,5066,168],{"class":76},[63,5068,1139],{"class":129},[63,5070,652],{"class":69},[63,5072,165],{"class":129},[63,5074,168],{"class":76},[63,5076,659],{"class":129},[63,5078,5079,5081],{"class":65,"line":140},[63,5080,111],{"class":69},[63,5082,198],{"class":76},[63,5084,5085,5087,5089,5091,5093,5095,5097,5099,5101,5103],{"class":65,"line":149},[63,5086,204],{"class":69},[63,5088,207],{"class":76},[63,5090,210],{"class":69},[63,5092,165],{"class":129},[63,5094,168],{"class":76},[63,5096,217],{"class":129},[63,5098,220],{"class":69},[63,5100,223],{"class":129},[63,5102,168],{"class":76},[63,5104,659],{"class":129},[63,5106,5107,5109,5111,5113,5115,5117,5119,5121,5123,5125,5127,5129],{"class":65,"line":154},[63,5108,120],{"class":69},[63,5110,1216],{"class":69},[63,5112,165],{"class":129},[63,5114,168],{"class":76},[63,5116,171],{"class":129},[63,5118,220],{"class":69},[63,5120,1227],{"class":76},[63,5122,404],{"class":129},[63,5124,168],{"class":76},[63,5126,1139],{"class":129},[63,5128,1822],{"class":76},[63,5130,5131],{"class":89},"-- skip if already in the chain\n",[63,5133,5134],{"class":65,"line":160},[63,5135,236],{"class":76},[63,5137,5138,5140,5142,5144,5146,5148],{"class":65,"line":193},[63,5139,242],{"class":69},[63,5141,99],{"class":76},[63,5143,102],{"class":69},[63,5145,784],{"class":76},[63,5147,464],{"class":69},[63,5149,251],{"class":76},[51,5151,5152],{"language":53},[55,5153,5155],{"className":57,"code":5154,"language":53,"meta":59,"style":59},"-- ✅ Fix 2: CYCLE clause (PostgreSQL 14+)\nWITH RECURSIVE descendant AS (\n  SELECT id, name, manager_id FROM employees WHERE id = 1\n  UNION ALL\n  SELECT e.id, e.name, e.manager_id\n  FROM employees e JOIN descendant d ON e.manager_id = d.id\n  CYCLE id SET is_cycle TO true DEFAULT false\n)\nSELECT id, name, manager_id FROM descendant WHERE NOT is_cycle;\n",[29,5156,5157,5162,5174,5196,5200,5226,5252,5269,5273],{"__ignoreMap":59},[63,5158,5159],{"class":65,"line":66},[63,5160,5161],{"class":89},"-- ✅ Fix 2: CYCLE clause (PostgreSQL 14+)\n",[63,5163,5164,5166,5168,5170,5172],{"class":65,"line":86},[63,5165,70],{"class":69},[63,5167,73],{"class":69},[63,5169,77],{"class":76},[63,5171,80],{"class":69},[63,5173,83],{"class":76},[63,5175,5176,5178,5180,5182,5184,5186,5188,5190,5192,5194],{"class":65,"line":93},[63,5177,96],{"class":69},[63,5179,99],{"class":76},[63,5181,102],{"class":69},[63,5183,784],{"class":76},[63,5185,464],{"class":69},[63,5187,789],{"class":76},[63,5189,792],{"class":69},[63,5191,123],{"class":76},[63,5193,126],{"class":69},[63,5195,130],{"class":129},[63,5197,5198],{"class":65,"line":108},[63,5199,364],{"class":69},[63,5201,5202,5204,5206,5208,5210,5212,5214,5216,5218,5220,5222,5224],{"class":65,"line":117},[63,5203,96],{"class":69},[63,5205,165],{"class":129},[63,5207,168],{"class":76},[63,5209,171],{"class":129},[63,5211,174],{"class":76},[63,5213,177],{"class":129},[63,5215,168],{"class":76},[63,5217,102],{"class":129},[63,5219,174],{"class":76},[63,5221,177],{"class":129},[63,5223,168],{"class":76},[63,5225,190],{"class":129},[63,5227,5228,5230,5232,5234,5236,5238,5240,5242,5244,5246,5248,5250],{"class":65,"line":133},[63,5229,111],{"class":69},[63,5231,3812],{"class":76},[63,5233,1539],{"class":69},[63,5235,207],{"class":76},[63,5237,210],{"class":69},[63,5239,165],{"class":129},[63,5241,168],{"class":76},[63,5243,217],{"class":129},[63,5245,220],{"class":69},[63,5247,223],{"class":129},[63,5249,168],{"class":76},[63,5251,659],{"class":129},[63,5253,5254,5256,5258,5260,5262,5264,5266],{"class":65,"line":140},[63,5255,1401],{"class":76},[63,5257,1404],{"class":69},[63,5259,1407],{"class":76},[63,5261,1410],{"class":69},[63,5263,1413],{"class":76},[63,5265,1416],{"class":69},[63,5267,5268],{"class":76}," false\n",[63,5270,5271],{"class":65,"line":149},[63,5272,236],{"class":76},[63,5274,5275,5277,5279,5281,5283,5285,5287,5289,5291],{"class":65,"line":154},[63,5276,242],{"class":69},[63,5278,99],{"class":76},[63,5280,102],{"class":69},[63,5282,784],{"class":76},[63,5284,464],{"class":69},[63,5286,77],{"class":76},[63,5288,792],{"class":69},[63,5290,1216],{"class":69},[63,5292,5293],{"class":76}," is_cycle;\n",[51,5295,5296],{"language":53},[55,5297,5299],{"className":57,"code":5298,"language":53,"meta":59,"style":59},"-- ✅ Fix 3: safety net — set statement_timeout before running\nSET statement_timeout = '10s';\n-- Even without cycle detection, the query is killed after 10 seconds\n",[29,5300,5301,5306,5318],{"__ignoreMap":59},[63,5302,5303],{"class":65,"line":66},[63,5304,5305],{"class":89},"-- ✅ Fix 3: safety net — set statement_timeout before running\n",[63,5307,5308,5310,5312,5314,5316],{"class":65,"line":86},[63,5309,1404],{"class":69},[63,5311,4053],{"class":76},[63,5313,126],{"class":69},[63,5315,4058],{"class":2111},[63,5317,477],{"class":76},[63,5319,5320],{"class":65,"line":93},[63,5321,5322],{"class":89},"-- Even without cycle detection, the query is killed after 10 seconds\n",[14,5324,5325,5328,5329,5331,5332,5334,5335,5337,5338,5340],{},[40,5326,5327],{},"The lesson",": recursive CTEs on self-referencing data (",[29,5330,217],{},", graph edges) must have cycle detection — either a ",[29,5333,1139],{}," array, the ",[29,5336,1453],{}," clause, or a ",[29,5339,1041],{}," safety net. Without it, a single data cycle creates an infinite loop.",[18,5342],{},[21,5344,5345],{"id":4969},"Summary",[14,5347,5348,5349,5351],{},"You can now write recursive CTEs with an anchor and recursive case, traverse trees (down and up) with depth and path tracking, walk graphs with cycle detection, explode bills of materials with quantity aggregation, and express iterative computations — with termination conditions and ",[29,5350,1041],{}," as safety nets. Next: full-text search.",[5353,5354,5355],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}",{"title":59,"searchDepth":86,"depth":86,"links":5357},[5358,5361,5364,5365,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379,5380,5381],{"id":23,"depth":86,"text":24,"children":5359},[5360],{"id":266,"depth":93,"text":267},{"id":289,"depth":86,"text":290,"children":5362},[5363],{"id":525,"depth":93,"text":526},{"id":745,"depth":86,"text":746},{"id":1034,"depth":86,"text":1035,"children":5366},[5367,5368,5369],{"id":1045,"depth":93,"text":1046},{"id":1263,"depth":93,"text":1264},{"id":1468,"depth":93,"text":1469},{"id":1609,"depth":86,"text":1610},{"id":1902,"depth":86,"text":1903},{"id":3161,"depth":86,"text":3162},{"id":3450,"depth":86,"text":3451},{"id":3669,"depth":86,"text":3670},{"id":4023,"depth":86,"text":4024},{"id":4224,"depth":86,"text":4225},{"id":4311,"depth":86,"text":4312},{"id":4623,"depth":86,"text":4624},{"id":4717,"depth":86,"text":4718},{"id":4847,"depth":86,"text":4848},{"id":4969,"depth":86,"text":5345},"md",{},"\u002Fsql\u002F19-recursive-queries",{"title":5,"description":16},"sql\u002F19-recursive-queries","erJEfC8UUt5MOWy_t9eiJ6VV4giWkLzB7iw1UZtoEHI",1789924654776]