[{"data":1,"prerenderedAt":2796},["ShallowReactive",2],{"page-\u002Fsql\u002F09-window-functions":3},{"id":4,"title":5,"body":6,"description":2789,"extension":2790,"meta":2791,"navigation":886,"path":2792,"seo":2793,"stem":2794,"__hash__":2795},"content\u002Fsql\u002F09-window-functions.md","09 — Window Functions",{"type":7,"value":8,"toc":2764},"minimark",[9,13,27,32,35,73,177,181,191,229,233,329,488,493,625,629,651,746,750,753,761,765,815,819,1041,1045,1112,1132,1136,1216,1370,1374,1601,1605,1894,1898,1902,1977,1981,2078,2082,2093,2196,2200,2317,2321,2505,2509,2512,2575,2730,2733,2760],[10,11,5],"h1",{"id":12},"_09-window-functions",[14,15,16,17,21,22,26],"p",{},"Window functions perform calculations across a set of rows ",[18,19,20],"em",{},"related to the current row",", without collapsing them like ",[23,24,25],"code",{},"GROUP BY"," does. They are the most powerful analytical feature in SQL — ranking, running totals, moving averages, lead\u002Flag comparisons, and \"top-N per group\" all become one-liners.",[28,29,31],"h2",{"id":30},"the-core-idea","The Core Idea",[14,33,34],{},"A window function:",[36,37,38,50,59,66],"ol",{},[39,40,41,45,46,49],"li",{},[42,43,44],"strong",{},"Partitions"," the result set into groups (",[23,47,48],{},"PARTITION BY",").",[39,51,52,55,56,49],{},[42,53,54],{},"Orders"," each partition (",[23,57,58],{},"ORDER BY",[39,60,61,62,65],{},"Defines a ",[42,63,64],{},"frame"," (the subset of rows used for the current calculation).",[39,67,68,69,72],{},"Computes a value per row, ",[42,70,71],{},"keeping all rows"," in the output (no collapse).",[74,75,77],"code-wrapper",{"language":76},"sql",[78,79,83],"pre",{"className":80,"code":81,"language":76,"meta":82,"style":82},"language-sql shiki shiki-themes github-light github-dark","-- Running total per customer, ordered by date.\n-- GROUP BY would collapse each customer into one row; the window function keeps them all.\n-- Default frame (with ORDER BY): RANGE UNBOUNDED PRECEDING TO CURRENT ROW → running accumulation.\nSELECT\n  customer_id,\n  ordered_on,\n  amount,\n  SUM(amount) OVER (PARTITION BY customer_id ORDER BY ordered_on) AS running_total\nFROM orders;\n","",[23,84,85,94,100,106,113,120,126,132,168],{"__ignoreMap":82},[86,87,90],"span",{"class":88,"line":89},"line",1,[86,91,93],{"class":92},"sdCPZ","-- Running total per customer, ordered by date.\n",[86,95,97],{"class":88,"line":96},2,[86,98,99],{"class":92},"-- GROUP BY would collapse each customer into one row; the window function keeps them all.\n",[86,101,103],{"class":88,"line":102},3,[86,104,105],{"class":92},"-- Default frame (with ORDER BY): RANGE UNBOUNDED PRECEDING TO CURRENT ROW → running accumulation.\n",[86,107,109],{"class":88,"line":108},4,[86,110,112],{"class":111},"svdQ7","SELECT\n",[86,114,116],{"class":88,"line":115},5,[86,117,119],{"class":118},"ssxIu","  customer_id,\n",[86,121,123],{"class":88,"line":122},6,[86,124,125],{"class":118},"  ordered_on,\n",[86,127,129],{"class":88,"line":128},7,[86,130,131],{"class":118},"  amount,\n",[86,133,135,139,142,145,148,151,154,157,159,162,165],{"class":88,"line":134},8,[86,136,138],{"class":137},"snvgF","  SUM",[86,140,141],{"class":118},"(amount) ",[86,143,144],{"class":111},"OVER",[86,146,147],{"class":118}," (",[86,149,150],{"class":111},"PARTITION",[86,152,153],{"class":111}," BY",[86,155,156],{"class":118}," customer_id ",[86,158,58],{"class":111},[86,160,161],{"class":118}," ordered_on) ",[86,163,164],{"class":111},"AS",[86,166,167],{"class":118}," running_total\n",[86,169,171,174],{"class":88,"line":170},9,[86,172,173],{"class":111},"FROM",[86,175,176],{"class":118}," orders;\n",[28,178,180],{"id":179},"anatomy-of-a-window-function","Anatomy of a Window Function",[74,182,184],{"language":183},"text",[78,185,189],{"className":186,"code":188,"language":183,"meta":82},[187],"language-text","function(args) OVER (\n  [PARTITION BY ...]     -- groups; default = whole result set is one partition\n  [ORDER BY ...]         -- order within each partition; default = unspecified (no order guarantee)\n  [frame_clause]         -- which rows to include; default depends on function + presence of ORDER BY\n)\n",[23,190,188],{"__ignoreMap":82},[192,193,194,201,223],"ul",{},[39,195,196,200],{},[42,197,198],{},[23,199,48],{}," — divides rows into independent groups. Omit it and the whole result set is one partition.",[39,202,203,207,208,211,212,215,216,219,220,222],{},[42,204,205],{},[23,206,58],{}," — orders rows ",[18,209,210],{},"within"," each partition. For ranking functions (",[23,213,214],{},"ROW_NUMBER",", ",[23,217,218],{},"RANK","), it's essential. For pure aggregates without ",[23,221,58],{},", the frame is the whole partition.",[39,224,225,228],{},[42,226,227],{},"Frame"," — defines the window's row range. Defaults differ by function (see below).",[28,230,232],{"id":231},"ranking-functions","Ranking Functions",[234,235,236,249],"table",{},[237,238,239],"thead",{},[240,241,242,246],"tr",{},[243,244,245],"th",{},"Function",[243,247,248],{},"Behavior on ties",[250,251,252,263,277,291,305,315],"tbody",{},[240,253,254,260],{},[255,256,257],"td",{},[23,258,259],{},"ROW_NUMBER()",[255,261,262],{},"Unique sequential number — ties get arbitrary but distinct numbers.",[240,264,265,270],{},[255,266,267],{},[23,268,269],{},"RANK()",[255,271,272,273,276],{},"Same rank for ties, then ",[42,274,275],{},"skips"," (1, 1, 3).",[240,278,279,284],{},[255,280,281],{},[23,282,283],{},"DENSE_RANK()",[255,285,286,287,290],{},"Same rank for ties, ",[42,288,289],{},"no skip"," (1, 1, 2).",[240,292,293,298],{},[255,294,295],{},[23,296,297],{},"PERCENT_RANK()",[255,299,300,301,304],{},"Relative rank as a fraction: ",[23,302,303],{},"(rank - 1) \u002F (total - 1)",".",[240,306,307,312],{},[255,308,309],{},[23,310,311],{},"CUME_DIST()",[255,313,314],{},"Cumulative distribution: rows with value ≤ current \u002F total.",[240,316,317,322],{},[255,318,319],{},[23,320,321],{},"NTILE(n)",[255,323,324,325,328],{},"Divides partition into ",[23,326,327],{},"n"," equal-ish buckets; returns bucket number.",[74,330,331],{"language":76},[78,332,334],{"className":80,"code":333,"language":76,"meta":82,"style":82},"-- Rank orders by amount within each customer.\n-- For amounts [450, 100, 100, 50] within a customer:\n--   ROW_NUMBER: 1, 2, 3, 4  (ties get distinct arbitrary numbers — non-deterministic without tie-breaker)\n--   RANK:       1, 2, 2, 4  (skips 3 after the tie)\n--   DENSE_RANK: 1, 2, 2, 3  (no skip — next distinct value gets next rank)\nSELECT\n  customer_id,\n  id AS order_id,\n  amount,\n  ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rn,\n  RANK()       OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rnk,\n  DENSE_RANK() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS drnk\nFROM orders;\n",[23,335,336,341,346,351,356,361,365,369,379,383,418,450,481],{"__ignoreMap":82},[86,337,338],{"class":88,"line":89},[86,339,340],{"class":92},"-- Rank orders by amount within each customer.\n",[86,342,343],{"class":88,"line":96},[86,344,345],{"class":92},"-- For amounts [450, 100, 100, 50] within a customer:\n",[86,347,348],{"class":88,"line":102},[86,349,350],{"class":92},"--   ROW_NUMBER: 1, 2, 3, 4  (ties get distinct arbitrary numbers — non-deterministic without tie-breaker)\n",[86,352,353],{"class":88,"line":108},[86,354,355],{"class":92},"--   RANK:       1, 2, 2, 4  (skips 3 after the tie)\n",[86,357,358],{"class":88,"line":115},[86,359,360],{"class":92},"--   DENSE_RANK: 1, 2, 2, 3  (no skip — next distinct value gets next rank)\n",[86,362,363],{"class":88,"line":122},[86,364,112],{"class":111},[86,366,367],{"class":88,"line":128},[86,368,119],{"class":118},[86,370,371,374,376],{"class":88,"line":134},[86,372,373],{"class":118},"  id ",[86,375,164],{"class":111},[86,377,378],{"class":118}," order_id,\n",[86,380,381],{"class":88,"line":170},[86,382,131],{"class":118},[86,384,386,389,392,394,396,398,400,402,404,407,410,413,415],{"class":88,"line":385},10,[86,387,388],{"class":137},"  ROW_NUMBER",[86,390,391],{"class":118},"() ",[86,393,144],{"class":111},[86,395,147],{"class":118},[86,397,150],{"class":111},[86,399,153],{"class":111},[86,401,156],{"class":118},[86,403,58],{"class":111},[86,405,406],{"class":118}," amount ",[86,408,409],{"class":111},"DESC",[86,411,412],{"class":118},") ",[86,414,164],{"class":111},[86,416,417],{"class":118}," rn,\n",[86,419,421,424,427,429,431,433,435,437,439,441,443,445,447],{"class":88,"line":420},11,[86,422,423],{"class":137},"  RANK",[86,425,426],{"class":118},"()       ",[86,428,144],{"class":111},[86,430,147],{"class":118},[86,432,150],{"class":111},[86,434,153],{"class":111},[86,436,156],{"class":118},[86,438,58],{"class":111},[86,440,406],{"class":118},[86,442,409],{"class":111},[86,444,412],{"class":118},[86,446,164],{"class":111},[86,448,449],{"class":118}," rnk,\n",[86,451,453,456,458,460,462,464,466,468,470,472,474,476,478],{"class":88,"line":452},12,[86,454,455],{"class":137},"  DENSE_RANK",[86,457,391],{"class":118},[86,459,144],{"class":111},[86,461,147],{"class":118},[86,463,150],{"class":111},[86,465,153],{"class":111},[86,467,156],{"class":118},[86,469,58],{"class":111},[86,471,406],{"class":118},[86,473,409],{"class":111},[86,475,412],{"class":118},[86,477,164],{"class":111},[86,479,480],{"class":118}," drnk\n",[86,482,484,486],{"class":88,"line":483},13,[86,485,173],{"class":111},[86,487,176],{"class":118},[489,490,492],"h3",{"id":491},"top-n-per-group-canonical-pattern","Top-N per Group — Canonical Pattern",[74,494,495],{"language":76},[78,496,498],{"className":80,"code":497,"language":76,"meta":82,"style":82},"-- Find the top 3 orders per customer.\n-- ROW_NUMBER (not RANK\u002FDENSE_RANK) is the right choice for \"exactly N\":\n-- it never produces ties, so rn \u003C= 3 returns exactly 3 rows per partition\n-- (or fewer if the partition is smaller).\n-- Wrap in a CTE because window functions can't appear in WHERE (computed after WHERE).\nWITH ranked AS (\n  SELECT *,\n    ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC, id) AS rn\n    -- ↑ added 'id' as tie-breaker for deterministic ordering on equal amounts\n  FROM orders\n)\nSELECT * FROM ranked WHERE rn \u003C= 3;\n",[23,499,500,505,510,515,520,525,538,549,580,585,593,598],{"__ignoreMap":82},[86,501,502],{"class":88,"line":89},[86,503,504],{"class":92},"-- Find the top 3 orders per customer.\n",[86,506,507],{"class":88,"line":96},[86,508,509],{"class":92},"-- ROW_NUMBER (not RANK\u002FDENSE_RANK) is the right choice for \"exactly N\":\n",[86,511,512],{"class":88,"line":102},[86,513,514],{"class":92},"-- it never produces ties, so rn \u003C= 3 returns exactly 3 rows per partition\n",[86,516,517],{"class":88,"line":108},[86,518,519],{"class":92},"-- (or fewer if the partition is smaller).\n",[86,521,522],{"class":88,"line":115},[86,523,524],{"class":92},"-- Wrap in a CTE because window functions can't appear in WHERE (computed after WHERE).\n",[86,526,527,530,533,535],{"class":88,"line":122},[86,528,529],{"class":111},"WITH",[86,531,532],{"class":118}," ranked ",[86,534,164],{"class":111},[86,536,537],{"class":118}," (\n",[86,539,540,543,546],{"class":88,"line":128},[86,541,542],{"class":111},"  SELECT",[86,544,545],{"class":111}," *",[86,547,548],{"class":118},",\n",[86,550,551,554,556,558,560,562,564,566,568,570,572,575,577],{"class":88,"line":134},[86,552,553],{"class":137},"    ROW_NUMBER",[86,555,391],{"class":118},[86,557,144],{"class":111},[86,559,147],{"class":118},[86,561,150],{"class":111},[86,563,153],{"class":111},[86,565,156],{"class":118},[86,567,58],{"class":111},[86,569,406],{"class":118},[86,571,409],{"class":111},[86,573,574],{"class":118},", id) ",[86,576,164],{"class":111},[86,578,579],{"class":118}," rn\n",[86,581,582],{"class":88,"line":170},[86,583,584],{"class":92},"    -- ↑ added 'id' as tie-breaker for deterministic ordering on equal amounts\n",[86,586,587,590],{"class":88,"line":385},[86,588,589],{"class":111},"  FROM",[86,591,592],{"class":118}," orders\n",[86,594,595],{"class":88,"line":420},[86,596,597],{"class":118},")\n",[86,599,600,603,605,608,610,613,616,619,622],{"class":88,"line":452},[86,601,602],{"class":111},"SELECT",[86,604,545],{"class":111},[86,606,607],{"class":111}," FROM",[86,609,532],{"class":118},[86,611,612],{"class":111},"WHERE",[86,614,615],{"class":118}," rn ",[86,617,618],{"class":111},"\u003C=",[86,620,621],{"class":137}," 3",[86,623,624],{"class":118},";\n",[28,626,628],{"id":627},"aggregate-functions-as-windows","Aggregate Functions as Windows",[14,630,631,632,215,635,215,638,215,641,215,644,647,648,650],{},"Any aggregate (",[23,633,634],{},"SUM",[23,636,637],{},"AVG",[23,639,640],{},"MIN",[23,642,643],{},"MAX",[23,645,646],{},"COUNT",") can be a window function with ",[23,649,144],{},":",[74,652,653],{"language":76},[78,654,656],{"className":80,"code":655,"language":76,"meta":82,"style":82},"-- Each order alongside its customer's total and the overall average.\n-- Without ORDER BY in OVER, the frame is the ENTIRE partition → every row gets the same value.\n-- SUM OVER (PARTITION BY customer_id) → customer's total (same for all their orders).\n-- AVG OVER () → grand average across all rows (empty OVER = whole result set is one partition).\nSELECT\n  id,\n  customer_id,\n  amount,\n  SUM(amount) OVER (PARTITION BY customer_id) AS customer_total,  -- same per customer\n  AVG(amount) OVER ()                       AS overall_avg          -- same for all rows\nFROM orders;\n",[23,657,658,663,668,673,678,682,687,691,695,720,740],{"__ignoreMap":82},[86,659,660],{"class":88,"line":89},[86,661,662],{"class":92},"-- Each order alongside its customer's total and the overall average.\n",[86,664,665],{"class":88,"line":96},[86,666,667],{"class":92},"-- Without ORDER BY in OVER, the frame is the ENTIRE partition → every row gets the same value.\n",[86,669,670],{"class":88,"line":102},[86,671,672],{"class":92},"-- SUM OVER (PARTITION BY customer_id) → customer's total (same for all their orders).\n",[86,674,675],{"class":88,"line":108},[86,676,677],{"class":92},"-- AVG OVER () → grand average across all rows (empty OVER = whole result set is one partition).\n",[86,679,680],{"class":88,"line":115},[86,681,112],{"class":111},[86,683,684],{"class":88,"line":122},[86,685,686],{"class":118},"  id,\n",[86,688,689],{"class":88,"line":128},[86,690,119],{"class":118},[86,692,693],{"class":88,"line":134},[86,694,131],{"class":118},[86,696,697,699,701,703,705,707,709,712,714,717],{"class":88,"line":170},[86,698,138],{"class":137},[86,700,141],{"class":118},[86,702,144],{"class":111},[86,704,147],{"class":118},[86,706,150],{"class":111},[86,708,153],{"class":111},[86,710,711],{"class":118}," customer_id) ",[86,713,164],{"class":111},[86,715,716],{"class":118}," customer_total,  ",[86,718,719],{"class":92},"-- same per customer\n",[86,721,722,725,727,729,732,734,737],{"class":88,"line":385},[86,723,724],{"class":137},"  AVG",[86,726,141],{"class":118},[86,728,144],{"class":111},[86,730,731],{"class":118}," ()                       ",[86,733,164],{"class":111},[86,735,736],{"class":118}," overall_avg          ",[86,738,739],{"class":92},"-- same for all rows\n",[86,741,742,744],{"class":88,"line":420},[86,743,173],{"class":111},[86,745,176],{"class":118},[28,747,749],{"id":748},"frames-rows-vs-range-vs-groups","Frames — ROWS vs RANGE vs GROUPS",[14,751,752],{},"The frame specifies which rows the function sees, relative to the current row:",[74,754,755],{"language":183},[78,756,759],{"className":757,"code":758,"language":183,"meta":82},[187],"{ROWS | RANGE | GROUPS} BETWEEN \u003Cstart> AND \u003Cend>\n  \u003Cstart>\u002F\u003Cend> := UNBOUNDED PRECEDING | \u003Cn> PRECEDING | CURRENT ROW | \u003Cn> FOLLOWING | UNBOUNDED FOLLOWING\n",[23,760,758],{"__ignoreMap":82},[489,762,764],{"id":763},"default-frames-when-order-by-is-present","Default Frames (when ORDER BY is present)",[192,766,767,791,799],{},[39,768,769,770,215,772,215,774,215,776,215,778,780,781,786,787,790],{},"For ",[23,771,634],{},[23,773,637],{},[23,775,640],{},[23,777,643],{},[23,779,646],{}," — ",[42,782,783],{},[23,784,785],{},"RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"," (running aggregate, including all ",[42,788,789],{},"peers"," with the same order key as the current row).",[39,792,793,794,215,796,798],{},"For ranking functions (",[23,795,214],{},[23,797,218],{},", etc.) — the whole partition (frame is irrelevant; they use the order, not a frame).",[39,800,769,801,804,805,804,808,804,811,814],{},[23,802,803],{},"LAG","\u002F",[23,806,807],{},"LEAD",[23,809,810],{},"FIRST_VALUE",[23,812,813],{},"LAST_VALUE"," — the whole partition.",[489,816,818],{"id":817},"common-frame-patterns","Common Frame Patterns",[74,820,821],{"language":76},[78,822,824],{"className":80,"code":823,"language":76,"meta":82,"style":82},"-- Running total (sum from partition start to current row, strict row-count).\n-- ROWS: exactly one row per step. Use this for row-by-row accumulation.\nSUM(amount) OVER (PARTITION BY customer_id ORDER BY ordered_on\n  ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\n\n-- Running total EXCLUDING the current row (sum of all rows before this one).\nSUM(amount) OVER (PARTITION BY customer_id ORDER BY ordered_on\n  ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)\n\n-- Moving average over a 3-row window (1 before + current + 1 after).\nAVG(amount) OVER (ORDER BY ordered_on\n  ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)\n\n-- Sum of the whole partition (same for every row — no ORDER BY, frame = full partition).\nSUM(amount) OVER (PARTITION BY customer_id)\n\n-- Cumulative sum up to and including all peers (default RANGE behavior).\n-- RANGE CURRENT ROW includes all rows with the same ORDER BY value as the current row.\nSUM(amount) OVER (PARTITION BY customer_id ORDER BY ordered_on)\n",[23,825,826,831,836,857,882,888,893,913,932,936,941,955,974,978,984,1002,1007,1013,1019],{"__ignoreMap":82},[86,827,828],{"class":88,"line":89},[86,829,830],{"class":92},"-- Running total (sum from partition start to current row, strict row-count).\n",[86,832,833],{"class":88,"line":96},[86,834,835],{"class":92},"-- ROWS: exactly one row per step. Use this for row-by-row accumulation.\n",[86,837,838,840,842,844,846,848,850,852,854],{"class":88,"line":102},[86,839,634],{"class":137},[86,841,141],{"class":118},[86,843,144],{"class":111},[86,845,147],{"class":118},[86,847,150],{"class":111},[86,849,153],{"class":111},[86,851,156],{"class":118},[86,853,58],{"class":111},[86,855,856],{"class":118}," ordered_on\n",[86,858,859,862,865,868,871,874,877,880],{"class":88,"line":108},[86,860,861],{"class":111},"  ROWS",[86,863,864],{"class":111}," BETWEEN",[86,866,867],{"class":111}," UNBOUNDED",[86,869,870],{"class":111}," PRECEDING",[86,872,873],{"class":111}," AND",[86,875,876],{"class":118}," CURRENT ",[86,878,879],{"class":111},"ROW",[86,881,597],{"class":118},[86,883,884],{"class":88,"line":115},[86,885,887],{"emptyLinePlaceholder":886},true,"\n",[86,889,890],{"class":88,"line":122},[86,891,892],{"class":92},"-- Running total EXCLUDING the current row (sum of all rows before this one).\n",[86,894,895,897,899,901,903,905,907,909,911],{"class":88,"line":128},[86,896,634],{"class":137},[86,898,141],{"class":118},[86,900,144],{"class":111},[86,902,147],{"class":118},[86,904,150],{"class":111},[86,906,153],{"class":111},[86,908,156],{"class":118},[86,910,58],{"class":111},[86,912,856],{"class":118},[86,914,915,917,919,921,923,925,928,930],{"class":88,"line":134},[86,916,861],{"class":111},[86,918,864],{"class":111},[86,920,867],{"class":111},[86,922,870],{"class":111},[86,924,873],{"class":111},[86,926,927],{"class":137}," 1",[86,929,870],{"class":111},[86,931,597],{"class":118},[86,933,934],{"class":88,"line":170},[86,935,887],{"emptyLinePlaceholder":886},[86,937,938],{"class":88,"line":385},[86,939,940],{"class":92},"-- Moving average over a 3-row window (1 before + current + 1 after).\n",[86,942,943,945,947,949,951,953],{"class":88,"line":420},[86,944,637],{"class":137},[86,946,141],{"class":118},[86,948,144],{"class":111},[86,950,147],{"class":118},[86,952,58],{"class":111},[86,954,856],{"class":118},[86,956,957,959,961,963,965,967,969,972],{"class":88,"line":452},[86,958,861],{"class":111},[86,960,864],{"class":111},[86,962,927],{"class":137},[86,964,870],{"class":111},[86,966,873],{"class":111},[86,968,927],{"class":137},[86,970,971],{"class":111}," FOLLOWING",[86,973,597],{"class":118},[86,975,976],{"class":88,"line":483},[86,977,887],{"emptyLinePlaceholder":886},[86,979,981],{"class":88,"line":980},14,[86,982,983],{"class":92},"-- Sum of the whole partition (same for every row — no ORDER BY, frame = full partition).\n",[86,985,987,989,991,993,995,997,999],{"class":88,"line":986},15,[86,988,634],{"class":137},[86,990,141],{"class":118},[86,992,144],{"class":111},[86,994,147],{"class":118},[86,996,150],{"class":111},[86,998,153],{"class":111},[86,1000,1001],{"class":118}," customer_id)\n",[86,1003,1005],{"class":88,"line":1004},16,[86,1006,887],{"emptyLinePlaceholder":886},[86,1008,1010],{"class":88,"line":1009},17,[86,1011,1012],{"class":92},"-- Cumulative sum up to and including all peers (default RANGE behavior).\n",[86,1014,1016],{"class":88,"line":1015},18,[86,1017,1018],{"class":92},"-- RANGE CURRENT ROW includes all rows with the same ORDER BY value as the current row.\n",[86,1020,1022,1024,1026,1028,1030,1032,1034,1036,1038],{"class":88,"line":1021},19,[86,1023,634],{"class":137},[86,1025,141],{"class":118},[86,1027,144],{"class":111},[86,1029,147],{"class":118},[86,1031,150],{"class":111},[86,1033,153],{"class":111},[86,1035,156],{"class":118},[86,1037,58],{"class":111},[86,1039,1040],{"class":118}," ordered_on)\n",[489,1042,1044],{"id":1043},"rows-vs-range-vs-groups","ROWS vs RANGE vs GROUPS",[234,1046,1047,1063],{},[237,1048,1049],{},[240,1050,1051,1054],{},[243,1052,1053],{},"Mode",[243,1055,1056,1057,804,1060],{},"Meaning of ",[23,1058,1059],{},"n PRECEDING",[23,1061,1062],{},"FOLLOWING",[250,1064,1065,1078,1096],{},[240,1066,1067,1072],{},[255,1068,1069],{},[23,1070,1071],{},"ROWS",[255,1073,1074,1075,1077],{},"Exactly ",[23,1076,327],{}," rows before\u002Fafter, counting physical rows.",[240,1079,1080,1085],{},[255,1081,1082],{},[23,1083,1084],{},"RANGE",[255,1086,1087,1088,1090,1091,1093,1094,304],{},"All rows whose ",[23,1089,58],{}," value is within ",[23,1092,327],{}," of the current row's value. Needs a numeric ",[23,1095,58],{},[240,1097,1098,1103],{},[255,1099,1100],{},[23,1101,1102],{},"GROUPS",[255,1104,1105,1106,1108,1109,1111],{},"All peer groups (rows with the same ",[23,1107,58],{}," value) within ",[23,1110,327],{}," groups.",[14,1113,1114,1116,1117,1119,1120,1122,1123,1125,1126,1128,1129,1131],{},[23,1115,1084],{}," and ",[23,1118,1102],{}," deal with ties — ",[23,1121,785],{}," (the default) includes all ",[42,1124,789],{}," (rows with the same ",[23,1127,58],{}," value), which is why a running total over ties gives the same value for all tied rows. Use ",[23,1130,1071],{}," for strict row-count semantics.",[28,1133,1135],{"id":1134},"value-functions","Value Functions",[234,1137,1138,1147],{},[237,1139,1140],{},[240,1141,1142,1144],{},[243,1143,245],{},[243,1145,1146],{},"Returns",[250,1148,1149,1170,1186,1196,1206],{},[240,1150,1151,1156],{},[255,1152,1153],{},[23,1154,1155],{},"LAG(col, n, default)",[255,1157,1158,1159,1162,1163,1165,1166,1169],{},"Value of ",[23,1160,1161],{},"col"," from ",[23,1164,327],{}," rows ",[42,1167,1168],{},"before"," the current row.",[240,1171,1172,1177],{},[255,1173,1174],{},[23,1175,1176],{},"LEAD(col, n, default)",[255,1178,1158,1179,1162,1181,1165,1183,304],{},[23,1180,1161],{},[23,1182,327],{},[42,1184,1185],{},"after",[240,1187,1188,1193],{},[255,1189,1190],{},[23,1191,1192],{},"FIRST_VALUE(col)",[255,1194,1195],{},"First value in the frame.",[240,1197,1198,1203],{},[255,1199,1200],{},[23,1201,1202],{},"LAST_VALUE(col)",[255,1204,1205],{},"Last value in the frame (⚠️ see below).",[240,1207,1208,1213],{},[255,1209,1210],{},[23,1211,1212],{},"NTH_VALUE(col, n)",[255,1214,1215],{},"Nth value in the frame.",[74,1217,1218],{"language":76},[78,1219,1221],{"className":80,"code":1220,"language":76,"meta":82,"style":82},"-- Day-over-day change in amount.\n-- LAG(amount, 1, 0): previous row's amount; default 0 if no previous row (first row in partition).\nSELECT\n  ordered_on,\n  amount,\n  amount - LAG(amount) OVER (ORDER BY ordered_on) AS daily_delta,\n  amount - LAG(amount, 1, 0) OVER (ORDER BY ordered_on) AS delta_with_default\nFROM orders;\n\n-- Compare each order to the customer's first order.\n-- FIRST_VALUE with ASC order → the earliest order's amount.\nSELECT\n  customer_id, ordered_on, amount,\n  amount - FIRST_VALUE(amount) OVER (PARTITION BY customer_id ORDER BY ordered_on) AS diff_from_first\nFROM orders;\n",[23,1222,1223,1228,1233,1237,1241,1245,1271,1305,1311,1315,1320,1325,1329,1334,1364],{"__ignoreMap":82},[86,1224,1225],{"class":88,"line":89},[86,1226,1227],{"class":92},"-- Day-over-day change in amount.\n",[86,1229,1230],{"class":88,"line":96},[86,1231,1232],{"class":92},"-- LAG(amount, 1, 0): previous row's amount; default 0 if no previous row (first row in partition).\n",[86,1234,1235],{"class":88,"line":102},[86,1236,112],{"class":111},[86,1238,1239],{"class":88,"line":108},[86,1240,125],{"class":118},[86,1242,1243],{"class":88,"line":115},[86,1244,131],{"class":118},[86,1246,1247,1250,1253,1256,1258,1260,1262,1264,1266,1268],{"class":88,"line":122},[86,1248,1249],{"class":118},"  amount ",[86,1251,1252],{"class":111},"-",[86,1254,1255],{"class":137}," LAG",[86,1257,141],{"class":118},[86,1259,144],{"class":111},[86,1261,147],{"class":118},[86,1263,58],{"class":111},[86,1265,161],{"class":118},[86,1267,164],{"class":111},[86,1269,1270],{"class":118}," daily_delta,\n",[86,1272,1273,1275,1277,1279,1282,1285,1287,1290,1292,1294,1296,1298,1300,1302],{"class":88,"line":128},[86,1274,1249],{"class":118},[86,1276,1252],{"class":111},[86,1278,1255],{"class":137},[86,1280,1281],{"class":118},"(amount, ",[86,1283,1284],{"class":137},"1",[86,1286,215],{"class":118},[86,1288,1289],{"class":137},"0",[86,1291,412],{"class":118},[86,1293,144],{"class":111},[86,1295,147],{"class":118},[86,1297,58],{"class":111},[86,1299,161],{"class":118},[86,1301,164],{"class":111},[86,1303,1304],{"class":118}," delta_with_default\n",[86,1306,1307,1309],{"class":88,"line":134},[86,1308,173],{"class":111},[86,1310,176],{"class":118},[86,1312,1313],{"class":88,"line":170},[86,1314,887],{"emptyLinePlaceholder":886},[86,1316,1317],{"class":88,"line":385},[86,1318,1319],{"class":92},"-- Compare each order to the customer's first order.\n",[86,1321,1322],{"class":88,"line":420},[86,1323,1324],{"class":92},"-- FIRST_VALUE with ASC order → the earliest order's amount.\n",[86,1326,1327],{"class":88,"line":452},[86,1328,112],{"class":111},[86,1330,1331],{"class":88,"line":483},[86,1332,1333],{"class":118},"  customer_id, ordered_on, amount,\n",[86,1335,1336,1338,1340,1343,1345,1347,1349,1351,1353,1355,1357,1359,1361],{"class":88,"line":980},[86,1337,1249],{"class":118},[86,1339,1252],{"class":111},[86,1341,1342],{"class":137}," FIRST_VALUE",[86,1344,141],{"class":118},[86,1346,144],{"class":111},[86,1348,147],{"class":118},[86,1350,150],{"class":111},[86,1352,153],{"class":111},[86,1354,156],{"class":118},[86,1356,58],{"class":111},[86,1358,161],{"class":118},[86,1360,164],{"class":111},[86,1362,1363],{"class":118}," diff_from_first\n",[86,1365,1366,1368],{"class":88,"line":986},[86,1367,173],{"class":111},[86,1369,176],{"class":118},[28,1371,1373],{"id":1372},"complex-implementation-7-day-moving-average-with-proper-frame","Complex Implementation: 7-Day Moving Average with Proper Frame",[74,1375,1376],{"language":76},[78,1377,1379],{"className":80,"code":1378,"language":76,"meta":82,"style":82},"-- 7-day moving average of daily revenue.\n-- The frame is defined by ROWS BETWEEN 6 PRECEDING AND CURRENT ROW:\n--   current row + 6 preceding rows = 7 rows total (the 7-day window).\n-- ROWS (not RANGE) ensures exactly 7 physical rows, regardless of date gaps.\n--   Note: if dates are missing (no orders on some days), ROWS counts rows, not calendar days.\n--   For true calendar-day windows, use RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW.\nSELECT\n  ordered_on,\n  daily_revenue,\n  AVG(daily_revenue) OVER (\n    ORDER BY ordered_on\n    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW    -- 7-row sliding window\n  ) AS moving_avg_7d,\n  -- True calendar 7-day window using RANGE with interval (PostgreSQL).\n  -- This handles gaps: if a date is missing, RANGE still looks back 6 calendar days.\n  AVG(daily_revenue) OVER (\n    ORDER BY ordered_on\n    RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW\n  ) AS moving_avg_7d_calendar\nFROM (\n  -- Stage 1: aggregate to daily revenue (window functions operate on rows, not pre-aggregated).\n  SELECT DATE(ordered_on) AS ordered_on, SUM(amount) AS daily_revenue\n  FROM orders\n  GROUP BY DATE(ordered_on)\n) daily\nORDER BY ordered_on;\n",[23,1380,1381,1386,1391,1396,1401,1406,1411,1415,1419,1424,1435,1442,1463,1473,1478,1483,1493,1499,1522,1531,1538,1544,1569,1576,1587,1593],{"__ignoreMap":82},[86,1382,1383],{"class":88,"line":89},[86,1384,1385],{"class":92},"-- 7-day moving average of daily revenue.\n",[86,1387,1388],{"class":88,"line":96},[86,1389,1390],{"class":92},"-- The frame is defined by ROWS BETWEEN 6 PRECEDING AND CURRENT ROW:\n",[86,1392,1393],{"class":88,"line":102},[86,1394,1395],{"class":92},"--   current row + 6 preceding rows = 7 rows total (the 7-day window).\n",[86,1397,1398],{"class":88,"line":108},[86,1399,1400],{"class":92},"-- ROWS (not RANGE) ensures exactly 7 physical rows, regardless of date gaps.\n",[86,1402,1403],{"class":88,"line":115},[86,1404,1405],{"class":92},"--   Note: if dates are missing (no orders on some days), ROWS counts rows, not calendar days.\n",[86,1407,1408],{"class":88,"line":122},[86,1409,1410],{"class":92},"--   For true calendar-day windows, use RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW.\n",[86,1412,1413],{"class":88,"line":128},[86,1414,112],{"class":111},[86,1416,1417],{"class":88,"line":134},[86,1418,125],{"class":118},[86,1420,1421],{"class":88,"line":170},[86,1422,1423],{"class":118},"  daily_revenue,\n",[86,1425,1426,1428,1431,1433],{"class":88,"line":385},[86,1427,724],{"class":137},[86,1429,1430],{"class":118},"(daily_revenue) ",[86,1432,144],{"class":111},[86,1434,537],{"class":118},[86,1436,1437,1440],{"class":88,"line":420},[86,1438,1439],{"class":111},"    ORDER BY",[86,1441,856],{"class":118},[86,1443,1444,1447,1449,1452,1454,1456,1458,1460],{"class":88,"line":452},[86,1445,1446],{"class":111},"    ROWS",[86,1448,864],{"class":111},[86,1450,1451],{"class":137}," 6",[86,1453,870],{"class":111},[86,1455,873],{"class":111},[86,1457,876],{"class":118},[86,1459,879],{"class":111},[86,1461,1462],{"class":92},"    -- 7-row sliding window\n",[86,1464,1465,1468,1470],{"class":88,"line":483},[86,1466,1467],{"class":118},"  ) ",[86,1469,164],{"class":111},[86,1471,1472],{"class":118}," moving_avg_7d,\n",[86,1474,1475],{"class":88,"line":980},[86,1476,1477],{"class":92},"  -- True calendar 7-day window using RANGE with interval (PostgreSQL).\n",[86,1479,1480],{"class":88,"line":986},[86,1481,1482],{"class":92},"  -- This handles gaps: if a date is missing, RANGE still looks back 6 calendar days.\n",[86,1484,1485,1487,1489,1491],{"class":88,"line":1004},[86,1486,724],{"class":137},[86,1488,1430],{"class":118},[86,1490,144],{"class":111},[86,1492,537],{"class":118},[86,1494,1495,1497],{"class":88,"line":1009},[86,1496,1439],{"class":111},[86,1498,856],{"class":118},[86,1500,1501,1504,1506,1509,1513,1515,1517,1519],{"class":88,"line":1015},[86,1502,1503],{"class":111},"    RANGE",[86,1505,864],{"class":111},[86,1507,1508],{"class":118}," INTERVAL ",[86,1510,1512],{"class":1511},"sJ6F3","'6 days'",[86,1514,870],{"class":111},[86,1516,873],{"class":111},[86,1518,876],{"class":118},[86,1520,1521],{"class":111},"ROW\n",[86,1523,1524,1526,1528],{"class":88,"line":1021},[86,1525,1467],{"class":118},[86,1527,164],{"class":111},[86,1529,1530],{"class":118}," moving_avg_7d_calendar\n",[86,1532,1534,1536],{"class":88,"line":1533},20,[86,1535,173],{"class":111},[86,1537,537],{"class":118},[86,1539,1541],{"class":88,"line":1540},21,[86,1542,1543],{"class":92},"  -- Stage 1: aggregate to daily revenue (window functions operate on rows, not pre-aggregated).\n",[86,1545,1547,1549,1552,1555,1557,1560,1562,1564,1566],{"class":88,"line":1546},22,[86,1548,542],{"class":111},[86,1550,1551],{"class":111}," DATE",[86,1553,1554],{"class":118},"(ordered_on) ",[86,1556,164],{"class":111},[86,1558,1559],{"class":118}," ordered_on, ",[86,1561,634],{"class":137},[86,1563,141],{"class":118},[86,1565,164],{"class":111},[86,1567,1568],{"class":118}," daily_revenue\n",[86,1570,1572,1574],{"class":88,"line":1571},23,[86,1573,589],{"class":111},[86,1575,592],{"class":118},[86,1577,1579,1582,1584],{"class":88,"line":1578},24,[86,1580,1581],{"class":111},"  GROUP BY",[86,1583,1551],{"class":111},[86,1585,1586],{"class":118},"(ordered_on)\n",[86,1588,1590],{"class":88,"line":1589},25,[86,1591,1592],{"class":118},") daily\n",[86,1594,1596,1598],{"class":88,"line":1595},26,[86,1597,58],{"class":111},[86,1599,1600],{"class":118}," ordered_on;\n",[28,1602,1604],{"id":1603},"complex-implementation-top-n-per-group-with-row_number","Complex Implementation: Top-N per Group with ROW_NUMBER",[74,1606,1607],{"language":76},[78,1608,1610],{"className":80,"code":1609,"language":76,"meta":82,"style":82},"-- Top 3 highest-value orders per customer, with rank and customer's total for context.\n-- ROW_NUMBER ensures exactly 3 per customer (no ties, no extra rows).\n-- The outer WHERE filters on rn (window functions computed after WHERE, so need a CTE\u002Fsubquery).\nSELECT\n  customer_id,\n  customer_name,\n  order_id,\n  amount,\n  rn AS rank_in_customer,\n  customer_total,\n  customer_total - amount AS rest_of_orders_total\nFROM (\n  SELECT\n    o.customer_id,\n    c.name AS customer_name,\n    o.id AS order_id,\n    o.amount,\n    ROW_NUMBER() OVER (PARTITION BY o.customer_id ORDER BY o.amount DESC, o.id) AS rn,\n    -- Tie-breaker: o.id ensures deterministic ranking when amounts are equal.\n    SUM(o.amount) OVER (PARTITION BY o.customer_id) AS customer_total\n    -- ↑ customer's lifetime total (no ORDER BY → frame = full partition → same for all their rows)\n  FROM orders o\n  JOIN customers c ON c.id = o.customer_id\n) ranked\nWHERE rn \u003C= 3\nORDER BY customer_id, rn;\n",[23,1611,1612,1617,1622,1627,1631,1635,1640,1645,1649,1659,1664,1678,1684,1689,1701,1717,1730,1741,1789,1794,1831,1836,1843,1871,1876,1887],{"__ignoreMap":82},[86,1613,1614],{"class":88,"line":89},[86,1615,1616],{"class":92},"-- Top 3 highest-value orders per customer, with rank and customer's total for context.\n",[86,1618,1619],{"class":88,"line":96},[86,1620,1621],{"class":92},"-- ROW_NUMBER ensures exactly 3 per customer (no ties, no extra rows).\n",[86,1623,1624],{"class":88,"line":102},[86,1625,1626],{"class":92},"-- The outer WHERE filters on rn (window functions computed after WHERE, so need a CTE\u002Fsubquery).\n",[86,1628,1629],{"class":88,"line":108},[86,1630,112],{"class":111},[86,1632,1633],{"class":88,"line":115},[86,1634,119],{"class":118},[86,1636,1637],{"class":88,"line":122},[86,1638,1639],{"class":118},"  customer_name,\n",[86,1641,1642],{"class":88,"line":128},[86,1643,1644],{"class":118},"  order_id,\n",[86,1646,1647],{"class":88,"line":134},[86,1648,131],{"class":118},[86,1650,1651,1654,1656],{"class":88,"line":170},[86,1652,1653],{"class":118},"  rn ",[86,1655,164],{"class":111},[86,1657,1658],{"class":118}," rank_in_customer,\n",[86,1660,1661],{"class":88,"line":385},[86,1662,1663],{"class":118},"  customer_total,\n",[86,1665,1666,1669,1671,1673,1675],{"class":88,"line":420},[86,1667,1668],{"class":118},"  customer_total ",[86,1670,1252],{"class":111},[86,1672,406],{"class":118},[86,1674,164],{"class":111},[86,1676,1677],{"class":118}," rest_of_orders_total\n",[86,1679,1680,1682],{"class":88,"line":452},[86,1681,173],{"class":111},[86,1683,537],{"class":118},[86,1685,1686],{"class":88,"line":483},[86,1687,1688],{"class":111},"  SELECT\n",[86,1690,1691,1694,1696,1699],{"class":88,"line":980},[86,1692,1693],{"class":137},"    o",[86,1695,304],{"class":118},[86,1697,1698],{"class":137},"customer_id",[86,1700,548],{"class":118},[86,1702,1703,1706,1708,1711,1714],{"class":88,"line":986},[86,1704,1705],{"class":137},"    c",[86,1707,304],{"class":118},[86,1709,1710],{"class":137},"name",[86,1712,1713],{"class":111}," AS",[86,1715,1716],{"class":118}," customer_name,\n",[86,1718,1719,1721,1723,1726,1728],{"class":88,"line":1004},[86,1720,1693],{"class":137},[86,1722,304],{"class":118},[86,1724,1725],{"class":137},"id",[86,1727,1713],{"class":111},[86,1729,378],{"class":118},[86,1731,1732,1734,1736,1739],{"class":88,"line":1009},[86,1733,1693],{"class":137},[86,1735,304],{"class":118},[86,1737,1738],{"class":137},"amount",[86,1740,548],{"class":118},[86,1742,1743,1745,1747,1749,1751,1753,1755,1758,1760,1762,1765,1767,1769,1771,1774,1776,1779,1781,1783,1785,1787],{"class":88,"line":1015},[86,1744,553],{"class":137},[86,1746,391],{"class":118},[86,1748,144],{"class":111},[86,1750,147],{"class":118},[86,1752,150],{"class":111},[86,1754,153],{"class":111},[86,1756,1757],{"class":137}," o",[86,1759,304],{"class":118},[86,1761,1698],{"class":137},[86,1763,1764],{"class":111}," ORDER BY",[86,1766,1757],{"class":137},[86,1768,304],{"class":118},[86,1770,1738],{"class":137},[86,1772,1773],{"class":111}," DESC",[86,1775,215],{"class":118},[86,1777,1778],{"class":137},"o",[86,1780,304],{"class":118},[86,1782,1725],{"class":137},[86,1784,412],{"class":118},[86,1786,164],{"class":111},[86,1788,417],{"class":118},[86,1790,1791],{"class":88,"line":1021},[86,1792,1793],{"class":92},"    -- Tie-breaker: o.id ensures deterministic ranking when amounts are equal.\n",[86,1795,1796,1799,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828],{"class":88,"line":1533},[86,1797,1798],{"class":137},"    SUM",[86,1800,1801],{"class":118},"(",[86,1803,1778],{"class":137},[86,1805,304],{"class":118},[86,1807,1738],{"class":137},[86,1809,412],{"class":118},[86,1811,144],{"class":111},[86,1813,147],{"class":118},[86,1815,150],{"class":111},[86,1817,153],{"class":111},[86,1819,1757],{"class":137},[86,1821,304],{"class":118},[86,1823,1698],{"class":137},[86,1825,412],{"class":118},[86,1827,164],{"class":111},[86,1829,1830],{"class":118}," customer_total\n",[86,1832,1833],{"class":88,"line":1540},[86,1834,1835],{"class":92},"    -- ↑ customer's lifetime total (no ORDER BY → frame = full partition → same for all their rows)\n",[86,1837,1838,1840],{"class":88,"line":1546},[86,1839,589],{"class":111},[86,1841,1842],{"class":118}," orders o\n",[86,1844,1845,1848,1851,1854,1857,1859,1861,1864,1866,1868],{"class":88,"line":1571},[86,1846,1847],{"class":111},"  JOIN",[86,1849,1850],{"class":118}," customers c ",[86,1852,1853],{"class":111},"ON",[86,1855,1856],{"class":137}," c",[86,1858,304],{"class":118},[86,1860,1725],{"class":137},[86,1862,1863],{"class":111}," =",[86,1865,1757],{"class":137},[86,1867,304],{"class":118},[86,1869,1870],{"class":137},"customer_id\n",[86,1872,1873],{"class":88,"line":1578},[86,1874,1875],{"class":118},") ranked\n",[86,1877,1878,1880,1882,1884],{"class":88,"line":1589},[86,1879,612],{"class":111},[86,1881,615],{"class":118},[86,1883,618],{"class":111},[86,1885,1886],{"class":137}," 3\n",[86,1888,1889,1891],{"class":88,"line":1595},[86,1890,58],{"class":111},[86,1892,1893],{"class":118}," customer_id, rn;\n",[28,1895,1897],{"id":1896},"anti-pattern-default-frame-with-first_valuelast_value","Anti-Pattern: Default Frame with FIRST_VALUE\u002FLAST_VALUE",[489,1899,1901],{"id":1900},"wrong-way-last_value-with-default-frame","❌ Wrong Way — LAST_VALUE with Default Frame",[74,1903,1904],{"language":76},[78,1905,1907],{"className":80,"code":1906,"language":76,"meta":82,"style":82},"-- Intent: get each customer's most recent order amount.\n-- Bug: the default frame (with ORDER BY) is RANGE UNBOUNDED PRECEDING TO CURRENT ROW.\n-- The frame ENDS at the current row → LAST_VALUE = the current row's value, not the partition's last.\nSELECT\n  customer_id,\n  ordered_on,\n  amount,\n  LAST_VALUE(amount) OVER (PARTITION BY customer_id ORDER BY ordered_on) AS last_amount\nFROM orders;\n-- 'last_amount' equals 'amount' for every row — NOT what was intended.\n",[23,1908,1909,1914,1919,1924,1928,1932,1936,1940,1966,1972],{"__ignoreMap":82},[86,1910,1911],{"class":88,"line":89},[86,1912,1913],{"class":92},"-- Intent: get each customer's most recent order amount.\n",[86,1915,1916],{"class":88,"line":96},[86,1917,1918],{"class":92},"-- Bug: the default frame (with ORDER BY) is RANGE UNBOUNDED PRECEDING TO CURRENT ROW.\n",[86,1920,1921],{"class":88,"line":102},[86,1922,1923],{"class":92},"-- The frame ENDS at the current row → LAST_VALUE = the current row's value, not the partition's last.\n",[86,1925,1926],{"class":88,"line":108},[86,1927,112],{"class":111},[86,1929,1930],{"class":88,"line":115},[86,1931,119],{"class":118},[86,1933,1934],{"class":88,"line":122},[86,1935,125],{"class":118},[86,1937,1938],{"class":88,"line":128},[86,1939,131],{"class":118},[86,1941,1942,1945,1947,1949,1951,1953,1955,1957,1959,1961,1963],{"class":88,"line":134},[86,1943,1944],{"class":137},"  LAST_VALUE",[86,1946,141],{"class":118},[86,1948,144],{"class":111},[86,1950,147],{"class":118},[86,1952,150],{"class":111},[86,1954,153],{"class":111},[86,1956,156],{"class":118},[86,1958,58],{"class":111},[86,1960,161],{"class":118},[86,1962,164],{"class":111},[86,1964,1965],{"class":118}," last_amount\n",[86,1967,1968,1970],{"class":88,"line":170},[86,1969,173],{"class":111},[86,1971,176],{"class":118},[86,1973,1974],{"class":88,"line":385},[86,1975,1976],{"class":92},"-- 'last_amount' equals 'amount' for every row — NOT what was intended.\n",[489,1978,1980],{"id":1979},"right-way-explicit-frame-or-first_value-with-desc","✅ Right Way — Explicit Frame or FIRST_VALUE with DESC",[74,1982,1983],{"language":76},[78,1984,1986],{"className":80,"code":1985,"language":76,"meta":82,"style":82},"-- Option 1: extend the frame to the partition end.\nLAST_VALUE(amount) OVER (\n  PARTITION BY customer_id ORDER BY ordered_on\n  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING  -- frame = whole partition\n)\n\n-- Option 2 (cleaner): FIRST_VALUE with descending order.\n-- The first row in DESC order is the most recent → no frame trap.\nFIRST_VALUE(amount) OVER (PARTITION BY customer_id ORDER BY ordered_on DESC)\n",[23,1987,1988,1993,2003,2016,2035,2039,2043,2048,2053],{"__ignoreMap":82},[86,1989,1990],{"class":88,"line":89},[86,1991,1992],{"class":92},"-- Option 1: extend the frame to the partition end.\n",[86,1994,1995,1997,1999,2001],{"class":88,"line":96},[86,1996,813],{"class":137},[86,1998,141],{"class":118},[86,2000,144],{"class":111},[86,2002,537],{"class":118},[86,2004,2005,2008,2010,2012,2014],{"class":88,"line":102},[86,2006,2007],{"class":111},"  PARTITION",[86,2009,153],{"class":111},[86,2011,156],{"class":118},[86,2013,58],{"class":111},[86,2015,856],{"class":118},[86,2017,2018,2020,2022,2024,2026,2028,2030,2032],{"class":88,"line":108},[86,2019,861],{"class":111},[86,2021,864],{"class":111},[86,2023,867],{"class":111},[86,2025,870],{"class":111},[86,2027,873],{"class":111},[86,2029,867],{"class":111},[86,2031,971],{"class":111},[86,2033,2034],{"class":92},"  -- frame = whole partition\n",[86,2036,2037],{"class":88,"line":115},[86,2038,597],{"class":118},[86,2040,2041],{"class":88,"line":122},[86,2042,887],{"emptyLinePlaceholder":886},[86,2044,2045],{"class":88,"line":128},[86,2046,2047],{"class":92},"-- Option 2 (cleaner): FIRST_VALUE with descending order.\n",[86,2049,2050],{"class":88,"line":134},[86,2051,2052],{"class":92},"-- The first row in DESC order is the most recent → no frame trap.\n",[86,2054,2055,2057,2059,2061,2063,2065,2067,2069,2071,2074,2076],{"class":88,"line":170},[86,2056,810],{"class":137},[86,2058,141],{"class":118},[86,2060,144],{"class":111},[86,2062,147],{"class":118},[86,2064,150],{"class":111},[86,2066,153],{"class":111},[86,2068,156],{"class":118},[86,2070,58],{"class":111},[86,2072,2073],{"class":118}," ordered_on ",[86,2075,409],{"class":111},[86,2077,597],{"class":118},[28,2079,2081],{"id":2080},"window-naming-reusable-over-clauses","Window Naming — Reusable OVER Clauses",[14,2083,2084,2085,2088,2089,2092],{},"If you repeat the same ",[23,2086,2087],{},"OVER (...)"," clause, name it with ",[23,2090,2091],{},"WINDOW"," and reference it:",[74,2094,2095],{"language":76},[78,2096,2098],{"className":80,"code":2097,"language":76,"meta":82,"style":82},"-- WINDOW clause: define the partition\u002Forder once, reference by name.\n-- Reduces repetition and makes the partitioning strategy explicit.\nSELECT\n  customer_id, ordered_on, amount,\n  ROW_NUMBER() OVER cust_window AS rn,\n  SUM(amount)   OVER cust_window AS running_total,    -- same OVER as rn\n  AVG(amount)   OVER cust_window AS running_avg        -- same OVER as rn\nFROM orders\nWINDOW cust_window AS (PARTITION BY customer_id ORDER BY ordered_on);\n",[23,2099,2100,2105,2110,2114,2118,2133,2152,2169,2175],{"__ignoreMap":82},[86,2101,2102],{"class":88,"line":89},[86,2103,2104],{"class":92},"-- WINDOW clause: define the partition\u002Forder once, reference by name.\n",[86,2106,2107],{"class":88,"line":96},[86,2108,2109],{"class":92},"-- Reduces repetition and makes the partitioning strategy explicit.\n",[86,2111,2112],{"class":88,"line":102},[86,2113,112],{"class":111},[86,2115,2116],{"class":88,"line":108},[86,2117,1333],{"class":118},[86,2119,2120,2122,2124,2126,2129,2131],{"class":88,"line":115},[86,2121,388],{"class":137},[86,2123,391],{"class":118},[86,2125,144],{"class":111},[86,2127,2128],{"class":118}," cust_window ",[86,2130,164],{"class":111},[86,2132,417],{"class":118},[86,2134,2135,2137,2140,2142,2144,2146,2149],{"class":88,"line":122},[86,2136,138],{"class":137},[86,2138,2139],{"class":118},"(amount)   ",[86,2141,144],{"class":111},[86,2143,2128],{"class":118},[86,2145,164],{"class":111},[86,2147,2148],{"class":118}," running_total,    ",[86,2150,2151],{"class":92},"-- same OVER as rn\n",[86,2153,2154,2156,2158,2160,2162,2164,2167],{"class":88,"line":128},[86,2155,724],{"class":137},[86,2157,2139],{"class":118},[86,2159,144],{"class":111},[86,2161,2128],{"class":118},[86,2163,164],{"class":111},[86,2165,2166],{"class":118}," running_avg        ",[86,2168,2151],{"class":92},[86,2170,2171,2173],{"class":88,"line":134},[86,2172,173],{"class":111},[86,2174,592],{"class":118},[86,2176,2177,2179,2181,2183,2185,2187,2189,2191,2193],{"class":88,"line":170},[86,2178,2091],{"class":111},[86,2180,2128],{"class":118},[86,2182,164],{"class":111},[86,2184,147],{"class":118},[86,2186,150],{"class":111},[86,2188,153],{"class":111},[86,2190,156],{"class":118},[86,2192,58],{"class":111},[86,2194,2195],{"class":118}," ordered_on);\n",[28,2197,2199],{"id":2198},"tips-tricks","💡 Tips & Tricks",[192,2201,2202,2224,2241,2263,2282,2304],{},[39,2203,2204,2207,2208,2211,2212,2215,2216,2219,2220,2223],{},[42,2205,2206],{},"Idiom"," — use ",[23,2209,2210],{},"ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) AS rn"," + ",[23,2213,2214],{},"WHERE rn \u003C= N"," for \"top-N per group.\" It's the most portable pattern (works in every modern database), unlike ",[23,2217,2218],{},"DISTINCT ON"," (PostgreSQL-only) or ",[23,2221,2222],{},"LATERAL"," (engine-specific).",[39,2225,2226,2229,2230,2233,2234,2236,2237,2240],{},[42,2227,2228],{},"Performance"," — window functions require ",[42,2231,2232],{},"sorting"," the partition by the ",[23,2235,58],{}," key. If that key isn't indexed, the planner does an explicit sort (memory permitting, else disk spill). For large partitions, an index on ",[23,2238,2239],{},"(partition_cols..., order_col)"," lets the planner use an index scan and avoid the sort.",[39,2242,2243,2245,2246,2249,2250,2253,2254,2256,2257,2259,2260,2262],{},[42,2244,2206],{}," — prefer ",[23,2247,2248],{},"FIRST_VALUE(x) OVER (... ORDER BY ... DESC)"," over ",[23,2251,2252],{},"LAST_VALUE(x) OVER (... ROWS BETWEEN ... UNBOUNDED FOLLOWING)"," for \"last value in partition.\" The ",[23,2255,810],{},"-with-",[23,2258,409],{}," form avoids the ",[23,2261,813],{}," default-frame trap and is one less clause to get wrong.",[39,2264,2265,2207,2267,2270,2271,2273,2274,2277,2278,2281],{},[42,2266,2206],{},[23,2268,2269],{},"COUNT(*) OVER ()"," (empty ",[23,2272,144],{},") to attach a total row count to every row — handy for paginated APIs that need ",[23,2275,2276],{},"total_count"," alongside each page's rows, without a separate ",[23,2279,2280],{},"count(*)"," query.",[39,2283,2284,780,2286,2288,2289,2291,2292,2294,2295,2297,2298,2300,2301,2303],{},[42,2285,2228],{},[23,2287,1071],{}," vs ",[23,2290,1084],{},": ",[23,2293,1071],{}," is O(1) per step (sliding window via addition\u002Fsubtraction). ",[23,2296,1084],{}," with intervals may require re-scanning rows within the range — more expensive. Use ",[23,2299,1071],{}," when you mean \"N physical rows\"; use ",[23,2302,1084],{}," only when you need value-based windows (e.g., calendar-day windows with gaps).",[39,2305,2306,2309,2310,2312,2313,2316],{},[42,2307,2308],{},"Debug"," — when a running total looks wrong (jumps, repeats, or excludes the current row), check the frame. The default ",[23,2311,1084],{}," frame includes peers (ties), which can make the total \"jump\" at ties. Switch to ",[23,2314,2315],{},"ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"," for strict row-by-row accumulation.",[28,2318,2320],{"id":2319},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[192,2322,2323,2348,2370,2391,2412,2436,2448,2465,2480],{},[39,2324,2325,2330,2331,2333,2334,804,2336,804,2338,2341,2342,2344,2345,304],{},[42,2326,2327,2328],{},"Window functions can't appear in ",[23,2329,612],{}," — they're computed ",[18,2332,1185],{}," ",[23,2335,612],{},[23,2337,25],{},[23,2339,2340],{},"HAVING"," (logically, in the ",[23,2343,602],{}," phase). Filter on window values in an outer query or CTE: ",[23,2346,2347],{},"WITH t AS (SELECT ..., ROW_NUMBER() OVER (...) AS rn FROM ...) SELECT * FROM t WHERE rn \u003C= 3",[39,2349,2350,2356,2357,2360,2361,804,2364,2366,2367,2369],{},[42,2351,2352,2353,2355],{},"NULLs in ",[23,2354,58],{}," for window functions"," — NULLs sort first or last depending on the engine (",[23,2358,2359],{},"NULLS LAST"," is PostgreSQL default for ASC). Use explicit ",[23,2362,2363],{},"NULLS FIRST",[23,2365,2359],{}," in the window ",[23,2368,58],{}," if NULL position matters.",[39,2371,2372,780,2379,2381,2382,2384,2385,2387,2388,304],{},[42,2373,2374,2375,2288,2377],{},"Peer groups in ",[23,2376,218],{},[23,2378,214],{},[23,2380,218],{}," gives the same rank to ties and skips the next rank (1, 1, 3). ",[23,2383,214],{}," gives distinct arbitrary numbers to ties (1, 2, 3 or 1, 3, 2 — non-deterministic). For stable ",[23,2386,214],{},", add a unique tie-breaker: ",[23,2389,2390],{},"ORDER BY amount DESC, id",[39,2392,2393,2399,2400,2291,2402,2405,2406,2408,2409,2411],{},[42,2394,2395,2396,2398],{},"Default frame for aggregates with ",[23,2397,58],{}," vs without"," — with ",[23,2401,58],{},[23,2403,2404],{},"RANGE UNBOUNDED PRECEDING TO CURRENT ROW"," (running aggregate, includes peers). Without ",[23,2407,58],{},": whole partition (same value for all rows). The difference is dramatic — forgetting ",[23,2410,58],{}," when you want a running total gives you the partition total instead.",[39,2413,2414,2419,2420,2423,2424,2426,2427,2430,2431,2211,2433,304],{},[42,2415,2416,2418],{},[23,2417,813],{}," with default frame"," — the default frame ends at ",[23,2421,2422],{},"CURRENT ROW",", so ",[23,2425,813],{}," returns the current row's value. Always specify ",[23,2428,2429],{},"ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING",", or use ",[23,2432,810],{},[23,2434,2435],{},"ORDER BY ... DESC",[39,2437,2438,2444,2445,2447],{},[42,2439,2440,2443],{},[23,2441,2442],{},"NTH_VALUE"," out of range"," — if the frame has fewer than N rows, ",[23,2446,2442],{}," returns NULL (not an error). Check the frame size if you expect a value.",[39,2449,2450,780,2457,2459,2460,804,2462,2464],{},[42,2451,2452,804,2454,2456],{},[23,2453,803],{},[23,2455,807],{}," at partition boundaries",[23,2458,803],{}," at the first row of a partition returns NULL (no preceding row), not a value from the previous partition. Partitions are independent — ",[23,2461,803],{},[23,2463,807],{}," don't cross them.",[39,2466,2467,2472,2473,2475,2476,2479],{},[42,2468,2469,2470],{},"Window functions and ",[23,2471,25],{}," — you can combine them. ",[23,2474,25],{}," collapses rows, then window functions operate over the grouped rows. ",[23,2477,2478],{},"SELECT customer_id, SUM(amount), RANK() OVER (ORDER BY SUM(amount) DESC) FROM orders GROUP BY customer_id"," ranks customers by total — the window sees grouped output, not raw rows.",[39,2481,2482,780,2489,2491,2492,2494,2495,2497,2498,2500,2501,2504],{},[42,2483,2484,2288,2486],{},[23,2485,2269],{},[23,2487,2488],{},"COUNT(*)",[23,2490,2269],{}," returns the total row count as a column on every row (window function, no collapse). Bare ",[23,2493,2488],{}," without ",[23,2496,144],{}," is an aggregate that collapses rows (illegal in a non-grouped ",[23,2499,602],{}," with other columns). The ",[23,2502,2503],{},"OVER ()"," is what makes it a window function.",[28,2506,2508],{"id":2507},"spot-the-bug","🧠 Spot the Bug",[14,2510,2511],{},"A developer computes a running total per customer, but for orders placed on the same day, the running total \"jumps ahead\" — the first of two same-day orders already shows the sum of both. Why?",[74,2513,2514],{"language":76},[78,2515,2517],{"className":80,"code":2516,"language":76,"meta":82,"style":82},"SELECT\n  customer_id,\n  ordered_on,\n  amount,\n  SUM(amount) OVER (\n    PARTITION BY customer_id\n    ORDER BY ordered_on\n  ) AS running_total\nFROM orders;\n",[23,2518,2519,2523,2527,2531,2535,2545,2555,2561,2569],{"__ignoreMap":82},[86,2520,2521],{"class":88,"line":89},[86,2522,112],{"class":111},[86,2524,2525],{"class":88,"line":96},[86,2526,119],{"class":118},[86,2528,2529],{"class":88,"line":102},[86,2530,125],{"class":118},[86,2532,2533],{"class":88,"line":108},[86,2534,131],{"class":118},[86,2536,2537,2539,2541,2543],{"class":88,"line":115},[86,2538,138],{"class":137},[86,2540,141],{"class":118},[86,2542,144],{"class":111},[86,2544,537],{"class":118},[86,2546,2547,2550,2552],{"class":88,"line":122},[86,2548,2549],{"class":111},"    PARTITION",[86,2551,153],{"class":111},[86,2553,2554],{"class":118}," customer_id\n",[86,2556,2557,2559],{"class":88,"line":128},[86,2558,1439],{"class":111},[86,2560,856],{"class":118},[86,2562,2563,2565,2567],{"class":88,"line":134},[86,2564,1467],{"class":118},[86,2566,164],{"class":111},[86,2568,167],{"class":118},[86,2570,2571,2573],{"class":88,"line":170},[86,2572,173],{"class":111},[86,2574,176],{"class":118},[2576,2577,2578,2582,2621,2627,2704,2713],"details",{},[2579,2580,2581],"summary",{},"Answer",[14,2583,2584,2585,2587,2588,2592,2593,2595,2596,2598,2599,2601,2602,2605,2606,2608,2609,2612,2613,2616,2617,2620],{},"The default frame when ",[23,2586,58],{}," is present is ",[42,2589,2590],{},[23,2591,785],{},". ",[23,2594,1084],{}," (not ",[23,2597,1071],{},") treats ",[23,2600,2422],{}," as including all ",[42,2603,2604],{},"peer rows"," — rows with the same ",[23,2607,58],{}," value as the current row. So when two orders share the same ",[23,2610,2611],{},"ordered_on",", they're peers, and both include each other in their frame: the running total for the ",[18,2614,2615],{},"first"," same-day order already includes the ",[18,2618,2619],{},"second"," same-day order's amount (and vice versa). The running total \"jumps ahead\" within the tied group.",[14,2622,2623,2624,2626],{},"The fix: use ",[23,2625,1071],{}," (strict row-count semantics) so the frame is exactly \"from the partition start to this physical row\":",[74,2628,2629],{"language":76},[78,2630,2632],{"className":80,"code":2631,"language":76,"meta":82,"style":82},"SELECT\n  customer_id,\n  ordered_on,\n  amount,\n  SUM(amount) OVER (\n    PARTITION BY customer_id\n    ORDER BY ordered_on\n    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n  ) AS running_total\nFROM orders;\n",[23,2633,2634,2638,2642,2646,2650,2660,2668,2674,2690,2698],{"__ignoreMap":82},[86,2635,2636],{"class":88,"line":89},[86,2637,112],{"class":111},[86,2639,2640],{"class":88,"line":96},[86,2641,119],{"class":118},[86,2643,2644],{"class":88,"line":102},[86,2645,125],{"class":118},[86,2647,2648],{"class":88,"line":108},[86,2649,131],{"class":118},[86,2651,2652,2654,2656,2658],{"class":88,"line":115},[86,2653,138],{"class":137},[86,2655,141],{"class":118},[86,2657,144],{"class":111},[86,2659,537],{"class":118},[86,2661,2662,2664,2666],{"class":88,"line":122},[86,2663,2549],{"class":111},[86,2665,153],{"class":111},[86,2667,2554],{"class":118},[86,2669,2670,2672],{"class":88,"line":128},[86,2671,1439],{"class":111},[86,2673,856],{"class":118},[86,2675,2676,2678,2680,2682,2684,2686,2688],{"class":88,"line":134},[86,2677,1446],{"class":111},[86,2679,864],{"class":111},[86,2681,867],{"class":111},[86,2683,870],{"class":111},[86,2685,873],{"class":111},[86,2687,876],{"class":118},[86,2689,1521],{"class":111},[86,2691,2692,2694,2696],{"class":88,"line":170},[86,2693,1467],{"class":118},[86,2695,164],{"class":111},[86,2697,167],{"class":118},[86,2699,2700,2702],{"class":88,"line":385},[86,2701,173],{"class":111},[86,2703,176],{"class":118},[14,2705,2706,2707,2709,2710,49],{},"With ",[23,2708,1071],{},", each row's frame ends at that specific row, so the running total accumulates one row at a time, even across same-day orders. To make the row order deterministic within ties, add a tie-breaker (",[23,2711,2712],{},"ORDER BY ordered_on, id",[14,2714,2715,2291,2718,2720,2721,2723,2724,2726,2727,2729],{},[42,2716,2717],{},"The lesson",[23,2719,1084],{}," (the default) includes peers; ",[23,2722,1071],{}," doesn't. For running totals that should accumulate row-by-row, always specify ",[23,2725,2315],{}," — the default ",[23,2728,1084],{}," silently sums all tied rows together.",[28,2731,2732],{"id":2579},"Summary",[14,2734,2735,2736,804,2738,804,2740,2743,2744,804,2746,2748,2749,2288,2751,2753,2754,2756,2757,2759],{},"You can now rank rows (",[23,2737,214],{},[23,2739,218],{},[23,2741,2742],{},"DENSE_RANK","), compute running totals and moving averages with explicit frames, compare rows with ",[23,2745,803],{},[23,2747,807],{},", and solve \"top-N per group\" — all without collapsing rows. You understand the ",[23,2750,1071],{},[23,2752,1084],{}," distinction, the ",[23,2755,813],{}," default-frame trap, and why ",[23,2758,214],{}," is the right tool for \"exactly N.\" Next: data types and the three-valued logic of NULL.",[2761,2762,2763],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}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 .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 .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}",{"title":82,"searchDepth":96,"depth":96,"links":2765},[2766,2767,2768,2771,2772,2777,2778,2779,2780,2784,2785,2786,2787,2788],{"id":30,"depth":96,"text":31},{"id":179,"depth":96,"text":180},{"id":231,"depth":96,"text":232,"children":2769},[2770],{"id":491,"depth":102,"text":492},{"id":627,"depth":96,"text":628},{"id":748,"depth":96,"text":749,"children":2773},[2774,2775,2776],{"id":763,"depth":102,"text":764},{"id":817,"depth":102,"text":818},{"id":1043,"depth":102,"text":1044},{"id":1134,"depth":96,"text":1135},{"id":1372,"depth":96,"text":1373},{"id":1603,"depth":96,"text":1604},{"id":1896,"depth":96,"text":1897,"children":2781},[2782,2783],{"id":1900,"depth":102,"text":1901},{"id":1979,"depth":102,"text":1980},{"id":2080,"depth":96,"text":2081},{"id":2198,"depth":96,"text":2199},{"id":2319,"depth":96,"text":2320},{"id":2507,"depth":96,"text":2508},{"id":2579,"depth":96,"text":2732},"Window functions perform calculations across a set of rows related to the current row, without collapsing them like GROUP BY does. They are the most powerful analytical feature in SQL — ranking, running totals, moving averages, lead\u002Flag comparisons, and \"top-N per group\" all become one-liners.","md",{},"\u002Fsql\u002F09-window-functions",{"title":5,"description":2789},"sql\u002F09-window-functions","twE59_0STgR_FV-_mOgCX6Tq99PE6iv2eACn-i6-1Ok",1789924654394]