[{"data":1,"prerenderedAt":1315},["ShallowReactive",2],{"page-\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fcommon-table-expressions-ctes-and-recursive-queries\u002Fimplementing-recursive-ctes-for-hierarchical-data-in-sqlalchemy\u002F":3},{"id":4,"title":5,"body":6,"description":47,"extension":1309,"meta":1310,"navigation":100,"path":1311,"seo":1312,"stem":1313,"__hash__":1314},"content\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fcommon-table-expressions-ctes-and-recursive-queries\u002Fimplementing-recursive-ctes-for-hierarchical-data-in-sqlalchemy\u002Findex.md","Implementing Recursive CTEs for Hierarchical Data in SQLAlchemy",{"type":7,"value":8,"toc":1296},"minimark",[9,13,18,41,376,380,387,654,658,669,840,844,855,985,993,997,1000,1007,1026,1101,1111,1129,1173,1177,1238,1242,1261,1276,1292],[10,11,5],"h1",{"id":12},"implementing-recursive-ctes-for-hierarchical-data-in-sqlalchemy",[14,15,17],"h2",{"id":16},"direct-answer-core-syntax-async-execution-pattern","Direct Answer: Core Syntax & Async Execution Pattern",[19,20,21,22,26,27,30,31,34,35,40],"p",{},"To implement a ",[23,24,25],"code",{},"recursive cte sqlalchemy hierarchical data"," workflow in SQLAlchemy 2.0, initialize a non-recursive anchor query, attach ",[23,28,29],{},".cte(recursive=True)",", and merge it with a recursive step using ",[23,32,33],{},".union_all()",". The compiled statement executes identically across supported dialects but requires strict column alignment and explicit async handling. For foundational mechanics on anchor\u002Frecursive union boundaries, reference ",[36,37,39],"a",{"href":38},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fcommon-table-expressions-ctes-and-recursive-queries\u002F","Common Table Expressions (CTEs) and Recursive Queries",".",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-python shiki shiki-themes github-light github-dark","from sqlalchemy import select, Integer, String\nfrom sqlalchemy.orm import AsyncSession\nfrom typing import AsyncGenerator\n\nasync def traverse_tree(session: AsyncSession, root_id: int) -> AsyncGenerator[tuple, None]:\n # 1. Anchor query\n anchor = select(\n Node.id, Node.parent_id, Node.name,\n 0.label(\"depth\")\n ).where(Node.id == root_id)\n\n # 2. Initialize recursive CTE\n tree_cte = anchor.cte(name=\"hierarchy\", recursive=True)\n tree_alias = tree_cte.alias()\n\n # 3. Recursive step\n recursive_step = select(\n Node.id, Node.parent_id, Node.name,\n (tree_alias.c.depth + 1).label(\"depth\")\n ).join(tree_alias, Node.parent_id == tree_alias.c.id)\n\n # 4. Union and execute asynchronously\n full_stmt = select(tree_cte.union_all(recursive_step)).order_by(tree_cte.c.depth)\n result = await session.execute(full_stmt.execution_options(stream_results=True))\n \n for row in result:\n yield row\n","python","",[23,49,50,69,82,95,102,137,144,156,162,178,190,195,201,233,244,249,255,265,270,289,300,305,311,322,346,352,367],{"__ignoreMap":47},[51,52,55,59,63,66],"span",{"class":53,"line":54},"line",1,[51,56,58],{"class":57},"szBVR","from",[51,60,62],{"class":61},"sVt8B"," sqlalchemy ",[51,64,65],{"class":57},"import",[51,67,68],{"class":61}," select, Integer, String\n",[51,70,72,74,77,79],{"class":53,"line":71},2,[51,73,58],{"class":57},[51,75,76],{"class":61}," sqlalchemy.orm ",[51,78,65],{"class":57},[51,80,81],{"class":61}," AsyncSession\n",[51,83,85,87,90,92],{"class":53,"line":84},3,[51,86,58],{"class":57},[51,88,89],{"class":61}," typing ",[51,91,65],{"class":57},[51,93,94],{"class":61}," AsyncGenerator\n",[51,96,98],{"class":53,"line":97},4,[51,99,101],{"emptyLinePlaceholder":100},true,"\n",[51,103,105,108,111,115,118,122,125,128,131,134],{"class":53,"line":104},5,[51,106,107],{"class":57},"async",[51,109,110],{"class":57}," def",[51,112,114],{"class":113},"sScJk"," traverse_tree",[51,116,117],{"class":61},"(session: AsyncSession, root_id: ",[51,119,121],{"class":120},"sj4cs","int",[51,123,124],{"class":61},") -> AsyncGenerator[",[51,126,127],{"class":120},"tuple",[51,129,130],{"class":61},", ",[51,132,133],{"class":120},"None",[51,135,136],{"class":61},"]:\n",[51,138,140],{"class":53,"line":139},6,[51,141,143],{"class":142},"sJ8bj"," # 1. Anchor query\n",[51,145,147,150,153],{"class":53,"line":146},7,[51,148,149],{"class":61}," anchor ",[51,151,152],{"class":57},"=",[51,154,155],{"class":61}," select(\n",[51,157,159],{"class":53,"line":158},8,[51,160,161],{"class":61}," Node.id, Node.parent_id, Node.name,\n",[51,163,165,168,171,175],{"class":53,"line":164},9,[51,166,167],{"class":120}," 0.",[51,169,170],{"class":61},"label(",[51,172,174],{"class":173},"sZZnC","\"depth\"",[51,176,177],{"class":61},")\n",[51,179,181,184,187],{"class":53,"line":180},10,[51,182,183],{"class":61}," ).where(Node.id ",[51,185,186],{"class":57},"==",[51,188,189],{"class":61}," root_id)\n",[51,191,193],{"class":53,"line":192},11,[51,194,101],{"emptyLinePlaceholder":100},[51,196,198],{"class":53,"line":197},12,[51,199,200],{"class":142}," # 2. Initialize recursive CTE\n",[51,202,204,207,209,212,216,218,221,223,226,228,231],{"class":53,"line":203},13,[51,205,206],{"class":61}," tree_cte ",[51,208,152],{"class":57},[51,210,211],{"class":61}," anchor.cte(",[51,213,215],{"class":214},"s4XuR","name",[51,217,152],{"class":57},[51,219,220],{"class":173},"\"hierarchy\"",[51,222,130],{"class":61},[51,224,225],{"class":214},"recursive",[51,227,152],{"class":57},[51,229,230],{"class":120},"True",[51,232,177],{"class":61},[51,234,236,239,241],{"class":53,"line":235},14,[51,237,238],{"class":61}," tree_alias ",[51,240,152],{"class":57},[51,242,243],{"class":61}," tree_cte.alias()\n",[51,245,247],{"class":53,"line":246},15,[51,248,101],{"emptyLinePlaceholder":100},[51,250,252],{"class":53,"line":251},16,[51,253,254],{"class":142}," # 3. Recursive step\n",[51,256,258,261,263],{"class":53,"line":257},17,[51,259,260],{"class":61}," recursive_step ",[51,262,152],{"class":57},[51,264,155],{"class":61},[51,266,268],{"class":53,"line":267},18,[51,269,161],{"class":61},[51,271,273,276,279,282,285,287],{"class":53,"line":272},19,[51,274,275],{"class":61}," (tree_alias.c.depth ",[51,277,278],{"class":57},"+",[51,280,281],{"class":120}," 1",[51,283,284],{"class":61},").label(",[51,286,174],{"class":173},[51,288,177],{"class":61},[51,290,292,295,297],{"class":53,"line":291},20,[51,293,294],{"class":61}," ).join(tree_alias, Node.parent_id ",[51,296,186],{"class":57},[51,298,299],{"class":61}," tree_alias.c.id)\n",[51,301,303],{"class":53,"line":302},21,[51,304,101],{"emptyLinePlaceholder":100},[51,306,308],{"class":53,"line":307},22,[51,309,310],{"class":142}," # 4. Union and execute asynchronously\n",[51,312,314,317,319],{"class":53,"line":313},23,[51,315,316],{"class":61}," full_stmt ",[51,318,152],{"class":57},[51,320,321],{"class":61}," select(tree_cte.union_all(recursive_step)).order_by(tree_cte.c.depth)\n",[51,323,325,328,330,333,336,339,341,343],{"class":53,"line":324},24,[51,326,327],{"class":61}," result ",[51,329,152],{"class":57},[51,331,332],{"class":57}," await",[51,334,335],{"class":61}," session.execute(full_stmt.execution_options(",[51,337,338],{"class":214},"stream_results",[51,340,152],{"class":57},[51,342,230],{"class":120},[51,344,345],{"class":61},"))\n",[51,347,349],{"class":53,"line":348},25,[51,350,351],{"class":61}," \n",[51,353,355,358,361,364],{"class":53,"line":354},26,[51,356,357],{"class":57}," for",[51,359,360],{"class":61}," row ",[51,362,363],{"class":57},"in",[51,365,366],{"class":61}," result:\n",[51,368,370,373],{"class":53,"line":369},27,[51,371,372],{"class":57}," yield",[51,374,375],{"class":61}," row\n",[14,377,379],{"id":378},"step-1-defining-the-base-model-recursive-anchor","Step 1: Defining the Base Model & Recursive Anchor",[19,381,382,383,386],{},"Map your adjacency list using SQLAlchemy 2.0's declarative typing. The anchor query must explicitly select columns that will propagate through the recursion. Avoid legacy ",[23,384,385],{},"session.query()","; it triggers deprecation warnings and breaks async compatibility.",[42,388,390],{"className":44,"code":389,"language":46,"meta":47,"style":47},"from sqlalchemy import Column, Integer, String, ForeignKey\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship\n\nclass Node(DeclarativeBase):\n __tablename__ = \"nodes\"\n id: Mapped[int] = mapped_column(primary_key=True)\n parent_id: Mapped[int | None] = mapped_column(ForeignKey(\"nodes.id\"))\n name: Mapped[str] = mapped_column(String(100))\n children: Mapped[list[\"Node\"]] = relationship(\n back_populates=\"parent\", remote_side=[id]\n )\n parent: Mapped[\"Node | None\"] = relationship(\n back_populates=\"children\", remote_side=[parent_id]\n )\n\n# Anchor query: explicit column selection with static typing\nanchor = select(\n Node.id,\n Node.parent_id,\n Node.name,\n 0.label(\"depth\")\n).where(Node.id == 1)\n",[23,391,392,403,414,418,435,445,472,497,517,533,559,564,578,596,600,604,609,618,623,628,633,643],{"__ignoreMap":47},[51,393,394,396,398,400],{"class":53,"line":54},[51,395,58],{"class":57},[51,397,62],{"class":61},[51,399,65],{"class":57},[51,401,402],{"class":61}," Column, Integer, String, ForeignKey\n",[51,404,405,407,409,411],{"class":53,"line":71},[51,406,58],{"class":57},[51,408,76],{"class":61},[51,410,65],{"class":57},[51,412,413],{"class":61}," DeclarativeBase, Mapped, mapped_column, relationship\n",[51,415,416],{"class":53,"line":84},[51,417,101],{"emptyLinePlaceholder":100},[51,419,420,423,426,429,432],{"class":53,"line":97},[51,421,422],{"class":57},"class",[51,424,425],{"class":113}," Node",[51,427,428],{"class":61},"(",[51,430,431],{"class":113},"DeclarativeBase",[51,433,434],{"class":61},"):\n",[51,436,437,440,442],{"class":53,"line":104},[51,438,439],{"class":61}," __tablename__ ",[51,441,152],{"class":57},[51,443,444],{"class":173}," \"nodes\"\n",[51,446,447,450,453,455,458,460,463,466,468,470],{"class":53,"line":139},[51,448,449],{"class":120}," id",[51,451,452],{"class":61},": Mapped[",[51,454,121],{"class":120},[51,456,457],{"class":61},"] ",[51,459,152],{"class":57},[51,461,462],{"class":61}," mapped_column(",[51,464,465],{"class":214},"primary_key",[51,467,152],{"class":57},[51,469,230],{"class":120},[51,471,177],{"class":61},[51,473,474,477,479,482,485,487,489,492,495],{"class":53,"line":146},[51,475,476],{"class":61}," parent_id: Mapped[",[51,478,121],{"class":120},[51,480,481],{"class":57}," |",[51,483,484],{"class":120}," None",[51,486,457],{"class":61},[51,488,152],{"class":57},[51,490,491],{"class":61}," mapped_column(ForeignKey(",[51,493,494],{"class":173},"\"nodes.id\"",[51,496,345],{"class":61},[51,498,499,502,505,507,509,512,515],{"class":53,"line":158},[51,500,501],{"class":61}," name: Mapped[",[51,503,504],{"class":120},"str",[51,506,457],{"class":61},[51,508,152],{"class":57},[51,510,511],{"class":61}," mapped_column(String(",[51,513,514],{"class":120},"100",[51,516,345],{"class":61},[51,518,519,522,525,528,530],{"class":53,"line":164},[51,520,521],{"class":61}," children: Mapped[list[",[51,523,524],{"class":173},"\"Node\"",[51,526,527],{"class":61},"]] ",[51,529,152],{"class":57},[51,531,532],{"class":61}," relationship(\n",[51,534,535,538,540,543,545,548,550,553,556],{"class":53,"line":180},[51,536,537],{"class":214}," back_populates",[51,539,152],{"class":57},[51,541,542],{"class":173},"\"parent\"",[51,544,130],{"class":61},[51,546,547],{"class":214},"remote_side",[51,549,152],{"class":57},[51,551,552],{"class":61},"[",[51,554,555],{"class":120},"id",[51,557,558],{"class":61},"]\n",[51,560,561],{"class":53,"line":192},[51,562,563],{"class":61}," )\n",[51,565,566,569,572,574,576],{"class":53,"line":197},[51,567,568],{"class":61}," parent: Mapped[",[51,570,571],{"class":173},"\"Node | None\"",[51,573,457],{"class":61},[51,575,152],{"class":57},[51,577,532],{"class":61},[51,579,580,582,584,587,589,591,593],{"class":53,"line":203},[51,581,537],{"class":214},[51,583,152],{"class":57},[51,585,586],{"class":173},"\"children\"",[51,588,130],{"class":61},[51,590,547],{"class":214},[51,592,152],{"class":57},[51,594,595],{"class":61},"[parent_id]\n",[51,597,598],{"class":53,"line":235},[51,599,563],{"class":61},[51,601,602],{"class":53,"line":246},[51,603,101],{"emptyLinePlaceholder":100},[51,605,606],{"class":53,"line":251},[51,607,608],{"class":142},"# Anchor query: explicit column selection with static typing\n",[51,610,611,614,616],{"class":53,"line":257},[51,612,613],{"class":61},"anchor ",[51,615,152],{"class":57},[51,617,155],{"class":61},[51,619,620],{"class":53,"line":267},[51,621,622],{"class":61}," Node.id,\n",[51,624,625],{"class":53,"line":272},[51,626,627],{"class":61}," Node.parent_id,\n",[51,629,630],{"class":53,"line":291},[51,631,632],{"class":61}," Node.name,\n",[51,634,635,637,639,641],{"class":53,"line":302},[51,636,167],{"class":120},[51,638,170],{"class":61},[51,640,174],{"class":173},[51,642,177],{"class":61},[51,644,645,648,650,652],{"class":53,"line":307},[51,646,647],{"class":61},").where(Node.id ",[51,649,186],{"class":57},[51,651,281],{"class":120},[51,653,177],{"class":61},[14,655,657],{"id":656},"step-2-building-the-recursive-union-cycle-prevention","Step 2: Building the Recursive Union & Cycle Prevention",[19,659,660,661,664,665,668],{},"Recursive CTEs require strict schema alignment between the anchor and recursive steps. Use ",[23,662,663],{},"cte(recursive=True)"," on the anchor, then alias it to reference previous iterations. Track traversal depth and construct a ",[23,666,667],{},"path"," column to prevent infinite loops and enable cycle detection.",[42,670,672],{"className":44,"code":671,"language":46,"meta":47,"style":47},"from sqlalchemy import select, cast, ARRAY, Integer\n\ntree_cte = anchor.cte(name=\"hierarchy\", recursive=True)\ntree_alias = tree_cte.alias()\n\n# Recursive step with depth increment and path tracking\nrecursive_step = select(\n Node.id,\n Node.parent_id,\n Node.name,\n (tree_alias.c.depth + 1).label(\"depth\"),\n (tree_alias.c.path + cast([Node.id], ARRAY(Integer))).label(\"path\")\n).join(\n tree_alias, Node.parent_id == tree_alias.c.id\n).where(\n Node.id.notin_(tree_alias.c.path) # Cycle prevention guard\n)\n\n# Union anchor and recursive step\nfull_hierarchy = tree_cte.union_all(recursive_step)\n",[23,673,674,691,695,720,729,733,738,747,751,755,759,774,789,794,804,809,817,821,825,830],{"__ignoreMap":47},[51,675,676,678,680,682,685,688],{"class":53,"line":54},[51,677,58],{"class":57},[51,679,62],{"class":61},[51,681,65],{"class":57},[51,683,684],{"class":61}," select, cast, ",[51,686,687],{"class":120},"ARRAY",[51,689,690],{"class":61},", Integer\n",[51,692,693],{"class":53,"line":71},[51,694,101],{"emptyLinePlaceholder":100},[51,696,697,700,702,704,706,708,710,712,714,716,718],{"class":53,"line":84},[51,698,699],{"class":61},"tree_cte ",[51,701,152],{"class":57},[51,703,211],{"class":61},[51,705,215],{"class":214},[51,707,152],{"class":57},[51,709,220],{"class":173},[51,711,130],{"class":61},[51,713,225],{"class":214},[51,715,152],{"class":57},[51,717,230],{"class":120},[51,719,177],{"class":61},[51,721,722,725,727],{"class":53,"line":97},[51,723,724],{"class":61},"tree_alias ",[51,726,152],{"class":57},[51,728,243],{"class":61},[51,730,731],{"class":53,"line":104},[51,732,101],{"emptyLinePlaceholder":100},[51,734,735],{"class":53,"line":139},[51,736,737],{"class":142},"# Recursive step with depth increment and path tracking\n",[51,739,740,743,745],{"class":53,"line":146},[51,741,742],{"class":61},"recursive_step ",[51,744,152],{"class":57},[51,746,155],{"class":61},[51,748,749],{"class":53,"line":158},[51,750,622],{"class":61},[51,752,753],{"class":53,"line":164},[51,754,627],{"class":61},[51,756,757],{"class":53,"line":180},[51,758,632],{"class":61},[51,760,761,763,765,767,769,771],{"class":53,"line":192},[51,762,275],{"class":61},[51,764,278],{"class":57},[51,766,281],{"class":120},[51,768,284],{"class":61},[51,770,174],{"class":173},[51,772,773],{"class":61},"),\n",[51,775,776,779,781,784,787],{"class":53,"line":197},[51,777,778],{"class":61}," (tree_alias.c.path ",[51,780,278],{"class":57},[51,782,783],{"class":61}," cast([Node.id], ARRAY(Integer))).label(",[51,785,786],{"class":173},"\"path\"",[51,788,177],{"class":61},[51,790,791],{"class":53,"line":203},[51,792,793],{"class":61},").join(\n",[51,795,796,799,801],{"class":53,"line":235},[51,797,798],{"class":61}," tree_alias, Node.parent_id ",[51,800,186],{"class":57},[51,802,803],{"class":61}," tree_alias.c.id\n",[51,805,806],{"class":53,"line":246},[51,807,808],{"class":61},").where(\n",[51,810,811,814],{"class":53,"line":251},[51,812,813],{"class":61}," Node.id.notin_(tree_alias.c.path) ",[51,815,816],{"class":142},"# Cycle prevention guard\n",[51,818,819],{"class":53,"line":257},[51,820,177],{"class":61},[51,822,823],{"class":53,"line":267},[51,824,101],{"emptyLinePlaceholder":100},[51,826,827],{"class":53,"line":272},[51,828,829],{"class":142},"# Union anchor and recursive step\n",[51,831,832,835,837],{"class":53,"line":291},[51,833,834],{"class":61},"full_hierarchy ",[51,836,152],{"class":57},[51,838,839],{"class":61}," tree_cte.union_all(recursive_step)\n",[14,841,843],{"id":842},"step-3-async-execution-streaming-optimization","Step 3: Async Execution, Streaming & Optimization",[19,845,846,847,850,851,854],{},"Compile the final statement and execute it asynchronously. For large hierarchies, enable ",[23,848,849],{},"stream_results=True"," to bypass full result buffering and prevent memory exhaustion. Pair this with ",[23,852,853],{},"yield_per()"," for chunked iteration.",[42,856,858],{"className":44,"code":857,"language":46,"meta":47,"style":47},"async def fetch_tree(session: AsyncSession, root_id: int, max_depth: int = 10):\n # Apply depth limit before outer select\n limited_stmt = select(full_hierarchy).where(\n full_hierarchy.c.depth \u003C max_depth\n ).order_by(full_hierarchy.c.depth)\n\n # Async execution with streaming\n result = await session.execute(\n limited_stmt.execution_options(stream_results=True)\n )\n \n # Yield rows in chunks to prevent event loop blocking\n for row in result.yield_per(1000):\n yield row\n",[23,859,860,886,891,901,912,917,921,926,937,950,954,958,963,979],{"__ignoreMap":47},[51,861,862,864,866,869,871,873,876,878,881,884],{"class":53,"line":54},[51,863,107],{"class":57},[51,865,110],{"class":57},[51,867,868],{"class":113}," fetch_tree",[51,870,117],{"class":61},[51,872,121],{"class":120},[51,874,875],{"class":61},", max_depth: ",[51,877,121],{"class":120},[51,879,880],{"class":57}," =",[51,882,883],{"class":120}," 10",[51,885,434],{"class":61},[51,887,888],{"class":53,"line":71},[51,889,890],{"class":142}," # Apply depth limit before outer select\n",[51,892,893,896,898],{"class":53,"line":84},[51,894,895],{"class":61}," limited_stmt ",[51,897,152],{"class":57},[51,899,900],{"class":61}," select(full_hierarchy).where(\n",[51,902,903,906,909],{"class":53,"line":97},[51,904,905],{"class":61}," full_hierarchy.c.depth ",[51,907,908],{"class":57},"\u003C",[51,910,911],{"class":61}," max_depth\n",[51,913,914],{"class":53,"line":104},[51,915,916],{"class":61}," ).order_by(full_hierarchy.c.depth)\n",[51,918,919],{"class":53,"line":139},[51,920,101],{"emptyLinePlaceholder":100},[51,922,923],{"class":53,"line":146},[51,924,925],{"class":142}," # Async execution with streaming\n",[51,927,928,930,932,934],{"class":53,"line":158},[51,929,327],{"class":61},[51,931,152],{"class":57},[51,933,332],{"class":57},[51,935,936],{"class":61}," session.execute(\n",[51,938,939,942,944,946,948],{"class":53,"line":164},[51,940,941],{"class":61}," limited_stmt.execution_options(",[51,943,338],{"class":214},[51,945,152],{"class":57},[51,947,230],{"class":120},[51,949,177],{"class":61},[51,951,952],{"class":53,"line":180},[51,953,563],{"class":61},[51,955,956],{"class":53,"line":192},[51,957,351],{"class":61},[51,959,960],{"class":53,"line":197},[51,961,962],{"class":142}," # Yield rows in chunks to prevent event loop blocking\n",[51,964,965,967,969,971,974,977],{"class":53,"line":203},[51,966,357],{"class":57},[51,968,360],{"class":61},[51,970,363],{"class":57},[51,972,973],{"class":61}," result.yield_per(",[51,975,976],{"class":120},"1000",[51,978,434],{"class":61},[51,980,981,983],{"class":53,"line":235},[51,982,372],{"class":57},[51,984,375],{"class":61},[19,986,987,988,992],{},"This execution pattern aligns with broader architectural strategies for ",[36,989,991],{"href":990},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002F","Advanced Query Patterns and Bulk Data Operations",", ensuring predictable memory footprints and connection pool stability.",[14,994,996],{"id":995},"error-resolution-debugging","Error Resolution & Debugging",[19,998,999],{},"Recursive CTE compilation and execution frequently fail due to strict type enforcement or unbounded traversal. Below are exact resolutions for production environments.",[1001,1002,1004],"h3",{"id":1003},"argumenterror-union-types-must-match",[23,1005,1006],{},"ArgumentError: UNION types must match",[19,1008,1009,1010,1013,1014,1017,1018,1021,1022,1025],{},"SQLAlchemy enforces strict column alignment. If the recursive step infers a different type than the anchor (e.g., ",[23,1011,1012],{},"Integer"," vs ",[23,1015,1016],{},"BigInteger","), compilation fails. Resolve with explicit ",[23,1019,1020],{},"cast()"," or ",[23,1023,1024],{},"type_coerce()",":",[42,1027,1029],{"className":44,"code":1028,"language":46,"meta":47,"style":47},"from sqlalchemy import cast, type_coerce, Integer\n\nrecursive_step = select(\n cast(Node.id, Integer).label(\"id\"),\n type_coerce(Node.parent_id, Integer).label(\"parent_id\"),\n Node.name,\n (tree_alias.c.depth + 1).label(\"depth\")\n).join(tree_alias, Node.parent_id == tree_alias.c.id)\n",[23,1030,1031,1042,1046,1054,1064,1074,1078,1092],{"__ignoreMap":47},[51,1032,1033,1035,1037,1039],{"class":53,"line":54},[51,1034,58],{"class":57},[51,1036,62],{"class":61},[51,1038,65],{"class":57},[51,1040,1041],{"class":61}," cast, type_coerce, Integer\n",[51,1043,1044],{"class":53,"line":71},[51,1045,101],{"emptyLinePlaceholder":100},[51,1047,1048,1050,1052],{"class":53,"line":84},[51,1049,742],{"class":61},[51,1051,152],{"class":57},[51,1053,155],{"class":61},[51,1055,1056,1059,1062],{"class":53,"line":97},[51,1057,1058],{"class":61}," cast(Node.id, Integer).label(",[51,1060,1061],{"class":173},"\"id\"",[51,1063,773],{"class":61},[51,1065,1066,1069,1072],{"class":53,"line":104},[51,1067,1068],{"class":61}," type_coerce(Node.parent_id, Integer).label(",[51,1070,1071],{"class":173},"\"parent_id\"",[51,1073,773],{"class":61},[51,1075,1076],{"class":53,"line":139},[51,1077,632],{"class":61},[51,1079,1080,1082,1084,1086,1088,1090],{"class":53,"line":146},[51,1081,275],{"class":61},[51,1083,278],{"class":57},[51,1085,281],{"class":120},[51,1087,284],{"class":61},[51,1089,174],{"class":173},[51,1091,177],{"class":61},[51,1093,1094,1097,1099],{"class":53,"line":158},[51,1095,1096],{"class":61},").join(tree_alias, Node.parent_id ",[51,1098,186],{"class":57},[51,1100,299],{"class":61},[1001,1102,1104,1107,1108],{"id":1103},"recursionerror-database-max_stack_depth",[23,1105,1106],{},"RecursionError"," & Database ",[23,1109,1110],{},"max_stack_depth",[19,1112,1113,1114,1116,1117,1120,1121,1124,1125,1128],{},"Unbounded recursion exhausts DB memory or hits PostgreSQL's ",[23,1115,1110],{}," (default 2MB). Always implement a ",[23,1118,1119],{},"depth"," guard and ",[23,1122,1123],{},"cycle"," detection. In PostgreSQL, verify execution plans with ",[23,1126,1127],{},"EXPLAIN ANALYZE"," to detect sequential scans on large adjacency lists:",[42,1130,1134],{"className":1131,"code":1132,"language":1133,"meta":47,"style":47},"language-sql shiki shiki-themes github-light github-dark","-- Run in your DB console to validate query plan\nEXPLAIN ANALYZE WITH RECURSIVE hierarchy AS (...) SELECT * FROM hierarchy;\n","sql",[23,1135,1136,1141],{"__ignoreMap":47},[51,1137,1138],{"class":53,"line":54},[51,1139,1140],{"class":142},"-- Run in your DB console to validate query plan\n",[51,1142,1143,1146,1149,1152,1155,1158,1161,1164,1167,1170],{"class":53,"line":71},[51,1144,1145],{"class":61},"EXPLAIN ANALYZE ",[51,1147,1148],{"class":57},"WITH",[51,1150,1151],{"class":57}," RECURSIVE",[51,1153,1154],{"class":61}," hierarchy ",[51,1156,1157],{"class":57},"AS",[51,1159,1160],{"class":61}," (...) ",[51,1162,1163],{"class":57},"SELECT",[51,1165,1166],{"class":57}," *",[51,1168,1169],{"class":57}," FROM",[51,1171,1172],{"class":61}," hierarchy;\n",[1001,1174,1176],{"id":1175},"production-pitfalls-checklist","Production Pitfalls Checklist",[1178,1179,1180,1195,1207,1220,1226],"ul",{},[1181,1182,1183,1187,1188,1191,1192,40],"li",{},[1184,1185,1186],"strong",{},"Omitting explicit column aliases"," in the recursive step triggers ",[23,1189,1190],{},"ArgumentError",". Always use ",[23,1193,1194],{},".label(\"col_name\")",[1181,1196,1197,1202,1203,1206],{},[1184,1198,1199,1200],{},"Using legacy ",[23,1201,385],{}," instead of ",[23,1204,1205],{},"select()"," causes deprecation warnings and async incompatibility.",[1181,1208,1209,1212,1213,1021,1215,1217,1218,40],{},[1184,1210,1211],{},"Unbounded recursion"," without ",[23,1214,1119],{},[23,1216,1123],{}," guards exhausts DB memory or hits ",[23,1219,1110],{},[1181,1221,1222,1225],{},[1184,1223,1224],{},"Failing to cast recursive step columns"," to match anchor types causes silent data truncation or compilation failure.",[1181,1227,1228,1234,1235,40],{},[1184,1229,1230,1231],{},"Neglecting ",[23,1232,1233],{},"await session.commit()"," or async context managers leads to connection pool leaks. Always use ",[23,1236,1237],{},"async with AsyncSession(engine) as session:",[14,1239,1241],{"id":1240},"faq","FAQ",[19,1243,1244,1247,1248,1250,1251,1254,1255,1258,1259,40],{},[1184,1245,1246],{},"How do I limit recursion depth in SQLAlchemy 2.0?","\nAdd a ",[23,1249,1119],{}," integer column to the anchor query (initialized to 0), increment it in the recursive step (",[23,1252,1253],{},"cte_alias.c.depth + 1","), and apply a final ",[23,1256,1257],{},"where(cte_alias.c.depth \u003C max_depth)"," filter before the outer ",[23,1260,1205],{},[19,1262,1263,1270,1271,1021,1274,40],{},[1184,1264,1265,1266,1269],{},"Why does ",[23,1267,1268],{},"union_all()"," throw a column mismatch error during CTE compilation?","\nSQLAlchemy enforces strict schema alignment in recursive unions. Both anchor and recursive steps must return identical column counts and compatible types. Resolve by explicitly casting mismatched columns using ",[23,1272,1273],{},"sqlalchemy.cast()",[23,1275,1024],{},[19,1277,1278,1281,1282,1284,1285,1288,1289,1291],{},[1184,1279,1280],{},"Can recursive CTEs be executed safely in async workflows with asyncpg?","\nYes. CTE construction is dialect-agnostic. Compile the statement with ",[23,1283,1205],{},", then execute via ",[23,1286,1287],{},"await async_session.execute(stmt)",". Ensure your target RDBMS supports recursive syntax (PostgreSQL 8.4+, MySQL 8.0+, SQLite 3.8.3+) and use ",[23,1290,849],{}," to prevent async event loop blocking on large datasets.",[1293,1294,1295],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":47,"searchDepth":71,"depth":71,"links":1297},[1298,1299,1300,1301,1302,1308],{"id":16,"depth":71,"text":17},{"id":378,"depth":71,"text":379},{"id":656,"depth":71,"text":657},{"id":842,"depth":71,"text":843},{"id":995,"depth":71,"text":996,"children":1303},[1304,1305,1307],{"id":1003,"depth":84,"text":1006},{"id":1103,"depth":84,"text":1306},"RecursionError & Database max_stack_depth",{"id":1175,"depth":84,"text":1176},{"id":1240,"depth":71,"text":1241},"md",{},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fcommon-table-expressions-ctes-and-recursive-queries\u002Fimplementing-recursive-ctes-for-hierarchical-data-in-sqlalchemy",{"title":5,"description":47},"advanced-query-patterns-and-bulk-data-operations\u002Fcommon-table-expressions-ctes-and-recursive-queries\u002Fimplementing-recursive-ctes-for-hierarchical-data-in-sqlalchemy\u002Findex","nKOUX3J_KcuVf9iVVNdx2-mSzojzBuB1x9p2VeLM64Q",1778149144397]