[{"data":1,"prerenderedAt":981},["ShallowReactive",2],{"page-\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fwindow-functions-and-analytical-queries\u002Fwriting-window-functions-for-running-totals-in-python\u002F":3},{"id":4,"title":5,"body":6,"description":974,"extension":975,"meta":976,"navigation":117,"path":977,"seo":978,"stem":979,"__hash__":980},"content\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fwindow-functions-and-analytical-queries\u002Fwriting-window-functions-for-running-totals-in-python\u002Findex.md","Writing Window Functions for Running Totals in Python",{"type":7,"value":8,"toc":962},"minimark",[9,13,40,45,234,239,266,281,285,502,506,517,528,532,698,702,705,767,771,900,904,916,937,958],[10,11,5],"h1",{"id":12},"writing-window-functions-for-running-totals-in-python",[14,15,16,17,21,22,25,26,29,30,33,34,39],"p",{},"To generate cumulative sums in SQLAlchemy 2.0, use ",[18,19,20],"code",{},"func.sum().over(order_by=...)"," within a ",[18,23,24],{},"select()"," construct. This compiles directly to the native SQL ",[18,27,28],{},"SUM() OVER (ORDER BY ...)"," clause. The ",[18,31,32],{},"order_by"," parameter is strictly mandatory for deterministic cumulative aggregation; omitting it returns a flat partition total instead of a running series. When integrated with ",[35,36,38],"a",{"href":37},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fwindow-functions-and-analytical-queries\u002F","Window Functions and Analytical Queries",", this pattern guarantees precise, database-side evaluation without Python-level iteration overhead.",[41,42,44],"h3",{"id":43},"basic-async-running-total-query","Basic Async Running Total Query",[46,47,52],"pre",{"className":48,"code":49,"language":50,"meta":51,"style":51},"language-python shiki shiki-themes github-light github-dark","from sqlalchemy import select, func\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom typing import List, Tuple\nfrom your_models import Transaction\n\nasync def get_running_totals(session: AsyncSession) -> List[Tuple[int, float]]:\n stmt = select(\n Transaction.id,\n Transaction.amount,\n func.sum(Transaction.amount).over(order_by=Transaction.created_at)\n .label(\"running_total\")\n ).order_by(Transaction.created_at)\n \n result = await session.execute(stmt)\n return result.all()\n","python","",[18,53,54,73,86,99,112,119,148,160,166,172,186,199,205,211,225],{"__ignoreMap":51},[55,56,59,63,67,70],"span",{"class":57,"line":58},"line",1,[55,60,62],{"class":61},"szBVR","from",[55,64,66],{"class":65},"sVt8B"," sqlalchemy ",[55,68,69],{"class":61},"import",[55,71,72],{"class":65}," select, func\n",[55,74,76,78,81,83],{"class":57,"line":75},2,[55,77,62],{"class":61},[55,79,80],{"class":65}," sqlalchemy.ext.asyncio ",[55,82,69],{"class":61},[55,84,85],{"class":65}," AsyncSession\n",[55,87,89,91,94,96],{"class":57,"line":88},3,[55,90,62],{"class":61},[55,92,93],{"class":65}," typing ",[55,95,69],{"class":61},[55,97,98],{"class":65}," List, Tuple\n",[55,100,102,104,107,109],{"class":57,"line":101},4,[55,103,62],{"class":61},[55,105,106],{"class":65}," your_models ",[55,108,69],{"class":61},[55,110,111],{"class":65}," Transaction\n",[55,113,115],{"class":57,"line":114},5,[55,116,118],{"emptyLinePlaceholder":117},true,"\n",[55,120,122,125,128,132,135,139,142,145],{"class":57,"line":121},6,[55,123,124],{"class":61},"async",[55,126,127],{"class":61}," def",[55,129,131],{"class":130},"sScJk"," get_running_totals",[55,133,134],{"class":65},"(session: AsyncSession) -> List[Tuple[",[55,136,138],{"class":137},"sj4cs","int",[55,140,141],{"class":65},", ",[55,143,144],{"class":137},"float",[55,146,147],{"class":65},"]]:\n",[55,149,151,154,157],{"class":57,"line":150},7,[55,152,153],{"class":65}," stmt ",[55,155,156],{"class":61},"=",[55,158,159],{"class":65}," select(\n",[55,161,163],{"class":57,"line":162},8,[55,164,165],{"class":65}," Transaction.id,\n",[55,167,169],{"class":57,"line":168},9,[55,170,171],{"class":65}," Transaction.amount,\n",[55,173,175,178,181,183],{"class":57,"line":174},10,[55,176,177],{"class":65}," func.sum(Transaction.amount).over(",[55,179,32],{"class":180},"s4XuR",[55,182,156],{"class":61},[55,184,185],{"class":65},"Transaction.created_at)\n",[55,187,189,192,196],{"class":57,"line":188},11,[55,190,191],{"class":65}," .label(",[55,193,195],{"class":194},"sZZnC","\"running_total\"",[55,197,198],{"class":65},")\n",[55,200,202],{"class":57,"line":201},12,[55,203,204],{"class":65}," ).order_by(Transaction.created_at)\n",[55,206,208],{"class":57,"line":207},13,[55,209,210],{"class":65}," \n",[55,212,214,217,219,222],{"class":57,"line":213},14,[55,215,216],{"class":65}," result ",[55,218,156],{"class":61},[55,220,221],{"class":61}," await",[55,223,224],{"class":65}," session.execute(stmt)\n",[55,226,228,231],{"class":57,"line":227},15,[55,229,230],{"class":61}," return",[55,232,233],{"class":65}," result.all()\n",[235,236,238],"h2",{"id":237},"async-workflow-integration","Async Workflow Integration",[14,240,241,242,245,246,249,250,253,254,257,258,261,262,265],{},"Executing window functions in an async environment requires strict adherence to the ",[18,243,244],{},"await session.execute()"," pattern. Calling synchronous result methods like ",[18,247,248],{},".all()"," directly on an ",[18,251,252],{},"AsyncSession"," proxy raises a ",[18,255,256],{},"RuntimeError",". Always resolve results using ",[18,259,260],{},"scalars().all()"," or ",[18,263,264],{},"mappings().all()"," after awaiting execution.",[14,267,268,269,141,272,275,276,280],{},"Proper transaction scoping and connection pool lifecycle management are critical. Async drivers (e.g., ",[18,270,271],{},"asyncpg",[18,273,274],{},"asyncmy",") maintain a non-blocking cursor until the result set is fully consumed or explicitly closed. Contextualizing this within ",[35,277,279],{"href":278},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002F","Advanced Query Patterns and Bulk Data Operations"," ensures high-throughput architectures avoid connection starvation during heavy analytical workloads.",[41,282,284],{"id":283},"error-handling-async-transaction-rollback","Error Handling & Async Transaction Rollback",[46,286,288],{"className":48,"code":287,"language":50,"meta":51,"style":51},"from sqlalchemy.exc import DatabaseError, SQLAlchemyError\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom contextlib import asynccontextmanager\nimport logging\n\n@asynccontextmanager\nasync def safe_running_total_query(session: AsyncSession):\n try:\n stmt = select(\n func.sum(Transaction.amount).over(order_by=Transaction.created_at)\n )\n result = await session.execute(stmt)\n yield result.scalars().all()\n await session.commit()\n except DatabaseError as e:\n logging.error(f\"Window function execution failed: {e}\")\n await session.rollback()\n raise\n except SQLAlchemyError as e:\n await session.rollback()\n raise\n finally:\n await session.close()\n",[18,289,290,302,312,324,331,335,340,352,360,368,378,383,393,401,408,422,448,456,462,474,481,486,494],{"__ignoreMap":51},[55,291,292,294,297,299],{"class":57,"line":58},[55,293,62],{"class":61},[55,295,296],{"class":65}," sqlalchemy.exc ",[55,298,69],{"class":61},[55,300,301],{"class":65}," DatabaseError, SQLAlchemyError\n",[55,303,304,306,308,310],{"class":57,"line":75},[55,305,62],{"class":61},[55,307,80],{"class":65},[55,309,69],{"class":61},[55,311,85],{"class":65},[55,313,314,316,319,321],{"class":57,"line":88},[55,315,62],{"class":61},[55,317,318],{"class":65}," contextlib ",[55,320,69],{"class":61},[55,322,323],{"class":65}," asynccontextmanager\n",[55,325,326,328],{"class":57,"line":101},[55,327,69],{"class":61},[55,329,330],{"class":65}," logging\n",[55,332,333],{"class":57,"line":114},[55,334,118],{"emptyLinePlaceholder":117},[55,336,337],{"class":57,"line":121},[55,338,339],{"class":130},"@asynccontextmanager\n",[55,341,342,344,346,349],{"class":57,"line":150},[55,343,124],{"class":61},[55,345,127],{"class":61},[55,347,348],{"class":130}," safe_running_total_query",[55,350,351],{"class":65},"(session: AsyncSession):\n",[55,353,354,357],{"class":57,"line":162},[55,355,356],{"class":61}," try",[55,358,359],{"class":65},":\n",[55,361,362,364,366],{"class":57,"line":168},[55,363,153],{"class":65},[55,365,156],{"class":61},[55,367,159],{"class":65},[55,369,370,372,374,376],{"class":57,"line":174},[55,371,177],{"class":65},[55,373,32],{"class":180},[55,375,156],{"class":61},[55,377,185],{"class":65},[55,379,380],{"class":57,"line":188},[55,381,382],{"class":65}," )\n",[55,384,385,387,389,391],{"class":57,"line":201},[55,386,216],{"class":65},[55,388,156],{"class":61},[55,390,221],{"class":61},[55,392,224],{"class":65},[55,394,395,398],{"class":57,"line":207},[55,396,397],{"class":61}," yield",[55,399,400],{"class":65}," result.scalars().all()\n",[55,402,403,405],{"class":57,"line":213},[55,404,221],{"class":61},[55,406,407],{"class":65}," session.commit()\n",[55,409,410,413,416,419],{"class":57,"line":227},[55,411,412],{"class":61}," except",[55,414,415],{"class":65}," DatabaseError ",[55,417,418],{"class":61},"as",[55,420,421],{"class":65}," e:\n",[55,423,425,428,431,434,437,440,443,446],{"class":57,"line":424},16,[55,426,427],{"class":65}," logging.error(",[55,429,430],{"class":61},"f",[55,432,433],{"class":194},"\"Window function execution failed: ",[55,435,436],{"class":137},"{",[55,438,439],{"class":65},"e",[55,441,442],{"class":137},"}",[55,444,445],{"class":194},"\"",[55,447,198],{"class":65},[55,449,451,453],{"class":57,"line":450},17,[55,452,221],{"class":61},[55,454,455],{"class":65}," session.rollback()\n",[55,457,459],{"class":57,"line":458},18,[55,460,461],{"class":61}," raise\n",[55,463,465,467,470,472],{"class":57,"line":464},19,[55,466,412],{"class":61},[55,468,469],{"class":65}," SQLAlchemyError ",[55,471,418],{"class":61},[55,473,421],{"class":65},[55,475,477,479],{"class":57,"line":476},20,[55,478,221],{"class":61},[55,480,455],{"class":65},[55,482,484],{"class":57,"line":483},21,[55,485,461],{"class":61},[55,487,489,492],{"class":57,"line":488},22,[55,490,491],{"class":61}," finally",[55,493,359],{"class":65},[55,495,497,499],{"class":57,"line":496},23,[55,498,221],{"class":61},[55,500,501],{"class":65}," session.close()\n",[235,503,505],{"id":504},"partitioning-and-frame-specification","Partitioning and Frame Specification",[14,507,508,509,512,513,516],{},"Multi-tenant or categorical running totals require ",[18,510,511],{},"partition_by",". By default, SQLAlchemy uses ",[18,514,515],{},"RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW",", which evaluates logical value ranges. This triggers severe performance degradation on time-series data with duplicate timestamps or non-sequential values.",[14,518,519,520,523,524,527],{},"Enforce physical row framing with ",[18,521,522],{},"rows_between(UNBOUNDED, CURRENT_ROW)"," to guarantee index-friendly sequential scans. This eliminates the sort overhead associated with ",[18,525,526],{},"range_between"," when dealing with dense datasets.",[41,529,531],{"id":530},"partitioned-running-total-with-frame-control","Partitioned Running Total with Frame Control",[46,533,535],{"className":48,"code":534,"language":50,"meta":51,"style":51},"from sqlalchemy import select, func, rows_between, UNBOUNDED, CURRENT_ROW\nfrom your_models import SalesRecord\n\nasync def get_partitioned_totals(session: AsyncSession):\n window = func.sum(SalesRecord.revenue).over(\n partition_by=SalesRecord.region,\n order_by=SalesRecord.sale_date,\n rows_between=(UNBOUNDED, CURRENT_ROW)\n )\n \n stmt = select(\n SalesRecord.region,\n SalesRecord.sale_date,\n SalesRecord.revenue,\n window.label(\"regional_running_total\")\n ).order_by(SalesRecord.region, SalesRecord.sale_date)\n \n result = await session.execute(stmt)\n return result.mappings().all()\n",[18,536,537,556,567,571,582,592,602,612,631,635,639,647,652,657,662,672,677,681,691],{"__ignoreMap":51},[55,538,539,541,543,545,548,551,553],{"class":57,"line":58},[55,540,62],{"class":61},[55,542,66],{"class":65},[55,544,69],{"class":61},[55,546,547],{"class":65}," select, func, rows_between, ",[55,549,550],{"class":137},"UNBOUNDED",[55,552,141],{"class":65},[55,554,555],{"class":137},"CURRENT_ROW\n",[55,557,558,560,562,564],{"class":57,"line":75},[55,559,62],{"class":61},[55,561,106],{"class":65},[55,563,69],{"class":61},[55,565,566],{"class":65}," SalesRecord\n",[55,568,569],{"class":57,"line":88},[55,570,118],{"emptyLinePlaceholder":117},[55,572,573,575,577,580],{"class":57,"line":101},[55,574,124],{"class":61},[55,576,127],{"class":61},[55,578,579],{"class":130}," get_partitioned_totals",[55,581,351],{"class":65},[55,583,584,587,589],{"class":57,"line":114},[55,585,586],{"class":65}," window ",[55,588,156],{"class":61},[55,590,591],{"class":65}," func.sum(SalesRecord.revenue).over(\n",[55,593,594,597,599],{"class":57,"line":121},[55,595,596],{"class":180}," partition_by",[55,598,156],{"class":61},[55,600,601],{"class":65},"SalesRecord.region,\n",[55,603,604,607,609],{"class":57,"line":150},[55,605,606],{"class":180}," order_by",[55,608,156],{"class":61},[55,610,611],{"class":65},"SalesRecord.sale_date,\n",[55,613,614,617,619,622,624,626,629],{"class":57,"line":162},[55,615,616],{"class":180}," rows_between",[55,618,156],{"class":61},[55,620,621],{"class":65},"(",[55,623,550],{"class":137},[55,625,141],{"class":65},[55,627,628],{"class":137},"CURRENT_ROW",[55,630,198],{"class":65},[55,632,633],{"class":57,"line":168},[55,634,382],{"class":65},[55,636,637],{"class":57,"line":174},[55,638,210],{"class":65},[55,640,641,643,645],{"class":57,"line":188},[55,642,153],{"class":65},[55,644,156],{"class":61},[55,646,159],{"class":65},[55,648,649],{"class":57,"line":201},[55,650,651],{"class":65}," SalesRecord.region,\n",[55,653,654],{"class":57,"line":207},[55,655,656],{"class":65}," SalesRecord.sale_date,\n",[55,658,659],{"class":57,"line":213},[55,660,661],{"class":65}," SalesRecord.revenue,\n",[55,663,664,667,670],{"class":57,"line":227},[55,665,666],{"class":65}," window.label(",[55,668,669],{"class":194},"\"regional_running_total\"",[55,671,198],{"class":65},[55,673,674],{"class":57,"line":424},[55,675,676],{"class":65}," ).order_by(SalesRecord.region, SalesRecord.sale_date)\n",[55,678,679],{"class":57,"line":450},[55,680,210],{"class":65},[55,682,683,685,687,689],{"class":57,"line":458},[55,684,216],{"class":65},[55,686,156],{"class":61},[55,688,221],{"class":61},[55,690,224],{"class":65},[55,692,693,695],{"class":57,"line":464},[55,694,230],{"class":61},[55,696,697],{"class":65}," result.mappings().all()\n",[235,699,701],{"id":700},"performance-optimization-for-large-datasets","Performance Optimization for Large Datasets",[14,703,704],{},"Window functions execute entirely on the database server, but improper schema design forces full table scans and temporary disk spills.",[706,707,708,728,753],"ol",{},[709,710,711,715,716,719,720,723,724,727],"li",{},[712,713,714],"strong",{},"Composite Indexing:"," Create a B-tree index matching the exact ",[18,717,718],{},"PARTITION BY"," and ",[18,721,722],{},"ORDER BY"," sequence: ",[18,725,726],{},"CREATE INDEX idx_running_total ON sales (region, sale_date)",". This allows the query planner to eliminate explicit sorting.",[709,729,730,733,734,737,738,741,742,745,746,748,749,752],{},[712,731,732],{},"Execution Plan Analysis:"," Run ",[18,735,736],{},"EXPLAIN ANALYZE"," on the compiled SQL. Look for ",[18,739,740],{},"WindowAgg"," nodes followed by ",[18,743,744],{},"Sort",". If ",[18,747,744],{}," appears, verify index alignment or explicitly enforce ",[18,750,751],{},"rows_between",".",[709,754,755,758,759,762,763,766],{},[712,756,757],{},"Memory Management:"," Large window evaluations can exhaust async driver memory buffers. Implement server-side cursors via ",[18,760,761],{},"execution_options(stream_results=True)"," or chunk results using ",[18,764,765],{},"yield_per(1000)",". This caps memory consumption by streaming rows incrementally rather than materializing the entire result set in RAM.",[235,768,770],{"id":769},"critical-pitfalls-resolutions","Critical Pitfalls & Resolutions",[772,773,774,790],"table",{},[775,776,777],"thead",{},[778,779,780,784,787],"tr",{},[781,782,783],"th",{},"Issue",[781,785,786],{},"Exact Error Context",[781,788,789],{},"Resolution",[791,792,793,822,846,876],"tbody",{},[778,794,795,808,811],{},[796,797,798],"td",{},[712,799,800,801,803,804,807],{},"Missing ",[18,802,722],{}," in ",[18,805,806],{},"OVER"," Clause",[796,809,810],{},"Returns a flat aggregate instead of a cumulative series. No Python exception is raised, producing silent logical errors.",[796,812,813,814,817,818,821],{},"Explicitly pass ",[18,815,816],{},"order_by=..."," to ",[18,819,820],{},".over()",". Window functions require deterministic ordering to compute running totals.",[778,823,824,829,835],{},[796,825,826],{},[712,827,828],{},"Async Session Not Awaiting Execution",[796,830,831,834],{},[18,832,833],{},"RuntimeError: This result object does not return rows. It has been closed automatically."," or synchronous method invocation failures.",[796,836,837,838,841,842,845],{},"Always use ",[18,839,840],{},"await session.execute(stmt)"," followed by ",[18,843,844],{},".scalars().all()",". Never call synchronous result methods on async proxies.",[778,847,848,853,866],{},[796,849,850],{},[712,851,852],{},"Incorrect Frame Boundaries",[796,854,855,856,859,860,719,862,865],{},"Query execution time spikes exponentially on large tables. ",[18,857,858],{},"EXPLAIN"," shows ",[18,861,744],{},[18,863,864],{},"HashAggregate"," nodes.",[796,867,868,869,872,873,875],{},"Replace default ",[18,870,871],{},"RANGE"," framing with ",[18,874,522],{}," to leverage physical row offsets and index scans.",[778,877,878,883,889],{},[796,879,880],{},[712,881,882],{},"Driver-Level Memory Exhaustion",[796,884,885,888],{},[18,886,887],{},"asyncpg.exceptions.ConnectionDoesNotExistError"," or driver OOM during bulk evaluation.",[796,890,891,892,895,896,899],{},"Enable ",[18,893,894],{},"stream_results=True"," or use ",[18,897,898],{},".yield_per(chunk_size)"," to prevent the async driver from buffering the entire window output in memory.",[235,901,903],{"id":902},"frequently-asked-questions","Frequently Asked Questions",[14,905,906,909,910,912,913,915],{},[712,907,908],{},"Does SQLAlchemy 2.0 support async window functions natively?","\nYes. Window functions compile to standard SQL and execute identically via ",[18,911,252],{},". The ORM layer handles async execution without altering the generated ",[18,914,806],{}," clause syntax or evaluation logic.",[14,917,918,921,922,925,926,929,930,933,934,936],{},[712,919,920],{},"How do I handle NULL values in running total calculations?","\nUse ",[18,923,924],{},"func.coalesce(column, 0)"," inside the window function to treat ",[18,927,928],{},"NULL"," as zero, preventing cumulative propagation breaks. Alternatively, apply ",[18,931,932],{},".filter(column.isnot(None))"," before the window definition to exclude ",[18,935,928],{}," rows from the aggregation entirely.",[14,938,939,942,943,946,947,950,951,954,955,957],{},[712,940,941],{},"Can I combine running totals with bulk inserts in a single transaction?","\nYes, but window functions require a ",[18,944,945],{},"SELECT"," phase. Execute the analytical query first, materialize the results, then perform bulk inserts using ",[18,948,949],{},"session.execute(insert(Model).values(...))"," within the same ",[18,952,953],{},"async with session.begin():"," transaction block. Avoid mixing DML and analytical ",[18,956,945],{}," statements in a single cursor to prevent lock contention.",[959,960,961],"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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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":51,"searchDepth":75,"depth":75,"links":963},[964,965,968,971,972,973],{"id":43,"depth":88,"text":44},{"id":237,"depth":75,"text":238,"children":966},[967],{"id":283,"depth":88,"text":284},{"id":504,"depth":75,"text":505,"children":969},[970],{"id":530,"depth":88,"text":531},{"id":700,"depth":75,"text":701},{"id":769,"depth":75,"text":770},{"id":902,"depth":75,"text":903},"To generate cumulative sums in SQLAlchemy 2.0, use func.sum().over(order_by=...) within a select() construct. This compiles directly to the native SQL SUM() OVER (ORDER BY ...) clause. The order_by parameter is strictly mandatory for deterministic cumulative aggregation; omitting it returns a flat partition total instead of a running series. When integrated with Window Functions and Analytical Queries, this pattern guarantees precise, database-side evaluation without Python-level iteration overhead.","md",{},"\u002Fadvanced-query-patterns-and-bulk-data-operations\u002Fwindow-functions-and-analytical-queries\u002Fwriting-window-functions-for-running-totals-in-python",{"title":5,"description":974},"advanced-query-patterns-and-bulk-data-operations\u002Fwindow-functions-and-analytical-queries\u002Fwriting-window-functions-for-running-totals-in-python\u002Findex","sGYGG89VZKwD1VmRpUKW6nv9oP5Xf9BGrH3djkK93N8",1778149144399]