[{"data":1,"prerenderedAt":887},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-window-functions-over-partition":3},{"id":4,"title":5,"body":6,"description":873,"difficulty":874,"extension":875,"framework":876,"frameworkSlug":53,"meta":877,"navigation":498,"order":61,"path":878,"qaPath":879,"seo":880,"stem":881,"subtopic":882,"topic":883,"topicSlug":884,"updated":885,"__hash__":886},"blog\u002Fblog\u002Fsql-window-functions-over-partition.md","SQL Window Functions — OVER, PARTITION BY, and When to Use Them",{"type":7,"value":8,"toc":863},"minimark",[9,14,27,34,38,48,172,175,179,185,300,310,314,329,431,447,451,590,594,603,722,726,732,840,844,859],[10,11,13],"h2",{"id":12},"the-problem-window-functions-solve","The problem window functions solve",[15,16,17,21,22,26],"p",{},[18,19,20],"code",{},"GROUP BY"," collapses rows into one summary row per group. That is great for\ntotals but useless when you want the total ",[23,24,25],"strong",{},"alongside"," the original row — for\nexample, showing each order's amount and the running total so far, or each\nemployee's salary and their department average for comparison.",[15,28,29,30,33],{},"Window functions compute aggregate values ",[23,31,32],{},"without collapsing rows",". Every\ninput row stays in the result, and the window function adds a new computed\ncolumn based on a related set of rows (the \"window\").",[10,35,37],{"id":36},"the-over-clause","The OVER clause",[15,39,40,41,44,45,47],{},"The ",[18,42,43],{},"OVER ()"," clause is what makes a function a window function instead of a\nregular aggregate. An empty ",[18,46,43],{}," means \"compute over all rows\".",[49,50,55],"pre",{"className":51,"code":52,"language":53,"meta":54,"style":54},"language-sql shiki shiki-themes github-light github-dark","-- Each order with the overall average order value alongside it\nSELECT\n    id,\n    customer_id,\n    total_amount,\n    AVG(total_amount) OVER ()  AS company_avg,\n    total_amount - AVG(total_amount) OVER () AS vs_avg\nFROM orders\nWHERE created_at >= '2026-01-01';\n-- Every row is preserved; no GROUP BY needed\n","sql","",[18,56,57,66,73,80,86,92,114,138,147,166],{"__ignoreMap":54},[58,59,62],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"sJ8bj","-- Each order with the overall average order value alongside it\n",[58,67,69],{"class":60,"line":68},2,[58,70,72],{"class":71},"szBVR","SELECT\n",[58,74,76],{"class":60,"line":75},3,[58,77,79],{"class":78},"sVt8B","    id,\n",[58,81,83],{"class":60,"line":82},4,[58,84,85],{"class":78},"    customer_id,\n",[58,87,89],{"class":60,"line":88},5,[58,90,91],{"class":78},"    total_amount,\n",[58,93,95,99,102,105,108,111],{"class":60,"line":94},6,[58,96,98],{"class":97},"sj4cs","    AVG",[58,100,101],{"class":78},"(total_amount) ",[58,103,104],{"class":71},"OVER",[58,106,107],{"class":78}," ()  ",[58,109,110],{"class":71},"AS",[58,112,113],{"class":78}," company_avg,\n",[58,115,117,120,123,126,128,130,133,135],{"class":60,"line":116},7,[58,118,119],{"class":78},"    total_amount ",[58,121,122],{"class":71},"-",[58,124,125],{"class":97}," AVG",[58,127,101],{"class":78},[58,129,104],{"class":71},[58,131,132],{"class":78}," () ",[58,134,110],{"class":71},[58,136,137],{"class":78}," vs_avg\n",[58,139,141,144],{"class":60,"line":140},8,[58,142,143],{"class":71},"FROM",[58,145,146],{"class":78}," orders\n",[58,148,150,153,156,159,163],{"class":60,"line":149},9,[58,151,152],{"class":71},"WHERE",[58,154,155],{"class":78}," created_at ",[58,157,158],{"class":71},">=",[58,160,162],{"class":161},"sZZnC"," '2026-01-01'",[58,164,165],{"class":78},";\n",[58,167,169],{"class":60,"line":168},10,[58,170,171],{"class":64},"-- Every row is preserved; no GROUP BY needed\n",[15,173,174],{},"Compare this to the GROUP BY alternative, which would require a self-join or\nsubquery to bring the average back to the row level.",[10,176,178],{"id":177},"partition-by-separate-windows-per-group","PARTITION BY — separate windows per group",[15,180,181,184],{},[18,182,183],{},"PARTITION BY"," divides rows into independent windows, like a GROUP BY that\ndoes not collapse rows.",[49,186,188],{"className":51,"code":187,"language":53,"meta":54,"style":54},"-- Each order with its customer's total spend and order rank\nSELECT\n    id,\n    customer_id,\n    total_amount,\n    created_at,\n    SUM(total_amount)  OVER (PARTITION BY customer_id) AS customer_lifetime_value,\n    COUNT(*)           OVER (PARTITION BY customer_id) AS customer_order_count,\n    AVG(total_amount)  OVER (PARTITION BY customer_id) AS customer_avg_order\nFROM orders;\n",[18,189,190,195,199,203,207,211,216,243,272,293],{"__ignoreMap":54},[58,191,192],{"class":60,"line":61},[58,193,194],{"class":64},"-- Each order with its customer's total spend and order rank\n",[58,196,197],{"class":60,"line":68},[58,198,72],{"class":71},[58,200,201],{"class":60,"line":75},[58,202,79],{"class":78},[58,204,205],{"class":60,"line":82},[58,206,85],{"class":78},[58,208,209],{"class":60,"line":88},[58,210,91],{"class":78},[58,212,213],{"class":60,"line":94},[58,214,215],{"class":78},"    created_at,\n",[58,217,218,221,224,226,229,232,235,238,240],{"class":60,"line":116},[58,219,220],{"class":97},"    SUM",[58,222,223],{"class":78},"(total_amount)  ",[58,225,104],{"class":71},[58,227,228],{"class":78}," (",[58,230,231],{"class":71},"PARTITION",[58,233,234],{"class":71}," BY",[58,236,237],{"class":78}," customer_id) ",[58,239,110],{"class":71},[58,241,242],{"class":78}," customer_lifetime_value,\n",[58,244,245,248,251,254,257,259,261,263,265,267,269],{"class":60,"line":140},[58,246,247],{"class":97},"    COUNT",[58,249,250],{"class":78},"(",[58,252,253],{"class":71},"*",[58,255,256],{"class":78},")           ",[58,258,104],{"class":71},[58,260,228],{"class":78},[58,262,231],{"class":71},[58,264,234],{"class":71},[58,266,237],{"class":78},[58,268,110],{"class":71},[58,270,271],{"class":78}," customer_order_count,\n",[58,273,274,276,278,280,282,284,286,288,290],{"class":60,"line":149},[58,275,98],{"class":97},[58,277,223],{"class":78},[58,279,104],{"class":71},[58,281,228],{"class":78},[58,283,231],{"class":71},[58,285,234],{"class":71},[58,287,237],{"class":78},[58,289,110],{"class":71},[58,291,292],{"class":78}," customer_avg_order\n",[58,294,295,297],{"class":60,"line":168},[58,296,143],{"class":71},[58,298,299],{"class":78}," orders;\n",[15,301,302,303,306,307,309],{},"Each customer_id is a separate partition. The ",[18,304,305],{},"SUM"," for order 1001 (customer 7)\ncounts only customer 7's orders; the ",[18,308,305],{}," for order 1002 (customer 12) counts\nonly customer 12's orders.",[10,311,313],{"id":312},"order-by-inside-over-running-totals-and-ranks","ORDER BY inside OVER — running totals and ranks",[15,315,316,317,320,321,324,325,328],{},"Adding ",[18,318,319],{},"ORDER BY"," to the window makes the window function ",[23,322,323],{},"position-aware",".\nWithout it, aggregate functions look at all rows in the partition at once.\nWith it, they typically see rows up to and including the current row (the\ndefault frame is ",[18,326,327],{},"ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW",").",[49,330,332],{"className":51,"code":331,"language":53,"meta":54,"style":54},"-- Running total of revenue per day (cumulative daily revenue)\nSELECT\n    DATE(created_at)                                     AS order_date,\n    SUM(total_amount)                                    AS daily_revenue,\n    SUM(SUM(total_amount)) OVER (ORDER BY DATE(created_at)) AS running_total\nFROM orders\nWHERE status = 'completed'\nGROUP BY DATE(created_at)\nORDER BY order_date;\n",[18,333,334,339,343,356,368,396,402,415,424],{"__ignoreMap":54},[58,335,336],{"class":60,"line":61},[58,337,338],{"class":64},"-- Running total of revenue per day (cumulative daily revenue)\n",[58,340,341],{"class":60,"line":68},[58,342,72],{"class":71},[58,344,345,348,351,353],{"class":60,"line":75},[58,346,347],{"class":71},"    DATE",[58,349,350],{"class":78},"(created_at)                                     ",[58,352,110],{"class":71},[58,354,355],{"class":78}," order_date,\n",[58,357,358,360,363,365],{"class":60,"line":82},[58,359,220],{"class":97},[58,361,362],{"class":78},"(total_amount)                                    ",[58,364,110],{"class":71},[58,366,367],{"class":78}," daily_revenue,\n",[58,369,370,372,374,376,379,381,383,385,388,391,393],{"class":60,"line":88},[58,371,220],{"class":97},[58,373,250],{"class":78},[58,375,305],{"class":97},[58,377,378],{"class":78},"(total_amount)) ",[58,380,104],{"class":71},[58,382,228],{"class":78},[58,384,319],{"class":71},[58,386,387],{"class":71}," DATE",[58,389,390],{"class":78},"(created_at)) ",[58,392,110],{"class":71},[58,394,395],{"class":78}," running_total\n",[58,397,398,400],{"class":60,"line":94},[58,399,143],{"class":71},[58,401,146],{"class":78},[58,403,404,406,409,412],{"class":60,"line":116},[58,405,152],{"class":71},[58,407,408],{"class":71}," status",[58,410,411],{"class":71}," =",[58,413,414],{"class":161}," 'completed'\n",[58,416,417,419,421],{"class":60,"line":140},[58,418,20],{"class":71},[58,420,387],{"class":71},[58,422,423],{"class":78},"(created_at)\n",[58,425,426,428],{"class":60,"line":149},[58,427,319],{"class":71},[58,429,430],{"class":78}," order_date;\n",[15,432,433,434,436,437,439,440,442,443,446],{},"Note the double ",[18,435,305],{},": the inner ",[18,438,305],{}," aggregates the ",[18,441,20],{},", producing one\nrow per day. The outer ",[18,444,445],{},"SUM ... OVER"," then computes the running total across\nthose daily subtotals.",[10,448,450],{"id":449},"aggregate-window-functions-vs-group-by","Aggregate window functions vs GROUP BY",[49,452,454],{"className":51,"code":453,"language":53,"meta":54,"style":54},"-- GROUP BY: one row per department, lose individual employee rows\nSELECT department_id, AVG(salary) AS dept_avg\nFROM employees\nGROUP BY department_id;\n\n-- Window function: all rows preserved, dept avg in a new column\nSELECT\n    id,\n    name,\n    department_id,\n    salary,\n    AVG(salary) OVER (PARTITION BY department_id) AS dept_avg,\n    salary - AVG(salary) OVER (PARTITION BY department_id) AS vs_dept_avg\nFROM employees;\n",[18,455,456,461,480,487,494,500,505,509,513,521,526,532,555,582],{"__ignoreMap":54},[58,457,458],{"class":60,"line":61},[58,459,460],{"class":64},"-- GROUP BY: one row per department, lose individual employee rows\n",[58,462,463,466,469,472,475,477],{"class":60,"line":68},[58,464,465],{"class":71},"SELECT",[58,467,468],{"class":78}," department_id, ",[58,470,471],{"class":97},"AVG",[58,473,474],{"class":78},"(salary) ",[58,476,110],{"class":71},[58,478,479],{"class":78}," dept_avg\n",[58,481,482,484],{"class":60,"line":75},[58,483,143],{"class":71},[58,485,486],{"class":78}," employees\n",[58,488,489,491],{"class":60,"line":82},[58,490,20],{"class":71},[58,492,493],{"class":78}," department_id;\n",[58,495,496],{"class":60,"line":88},[58,497,499],{"emptyLinePlaceholder":498},true,"\n",[58,501,502],{"class":60,"line":94},[58,503,504],{"class":64},"-- Window function: all rows preserved, dept avg in a new column\n",[58,506,507],{"class":60,"line":116},[58,508,72],{"class":71},[58,510,511],{"class":60,"line":140},[58,512,79],{"class":78},[58,514,515,518],{"class":60,"line":149},[58,516,517],{"class":71},"    name",[58,519,520],{"class":78},",\n",[58,522,523],{"class":60,"line":168},[58,524,525],{"class":78},"    department_id,\n",[58,527,529],{"class":60,"line":528},11,[58,530,531],{"class":78},"    salary,\n",[58,533,535,537,539,541,543,545,547,550,552],{"class":60,"line":534},12,[58,536,98],{"class":97},[58,538,474],{"class":78},[58,540,104],{"class":71},[58,542,228],{"class":78},[58,544,231],{"class":71},[58,546,234],{"class":71},[58,548,549],{"class":78}," department_id) ",[58,551,110],{"class":71},[58,553,554],{"class":78}," dept_avg,\n",[58,556,558,561,563,565,567,569,571,573,575,577,579],{"class":60,"line":557},13,[58,559,560],{"class":78},"    salary ",[58,562,122],{"class":71},[58,564,125],{"class":97},[58,566,474],{"class":78},[58,568,104],{"class":71},[58,570,228],{"class":78},[58,572,231],{"class":71},[58,574,234],{"class":71},[58,576,549],{"class":78},[58,578,110],{"class":71},[58,580,581],{"class":78}," vs_dept_avg\n",[58,583,585,587],{"class":60,"line":584},14,[58,586,143],{"class":71},[58,588,589],{"class":78}," employees;\n",[10,591,593],{"id":592},"multiple-window-functions-in-one-query","Multiple window functions in one query",[15,595,596,597,599,600,602],{},"You can use multiple ",[18,598,104],{}," clauses with different partitions in the same\n",[18,601,465],{},":",[49,604,606],{"className":51,"code":605,"language":53,"meta":54,"style":54},"SELECT\n    id,\n    customer_id,\n    product_category,\n    total_amount,\n    -- Customer-level metrics\n    SUM(total_amount) OVER (PARTITION BY customer_id)          AS cust_total,\n    -- Category-level metrics\n    AVG(total_amount) OVER (PARTITION BY product_category)     AS cat_avg,\n    -- Company-wide rank by amount\n    RANK()            OVER (ORDER BY total_amount DESC)        AS company_rank\nFROM orders;\n",[18,607,608,612,616,620,625,629,634,656,661,683,688,716],{"__ignoreMap":54},[58,609,610],{"class":60,"line":61},[58,611,72],{"class":71},[58,613,614],{"class":60,"line":68},[58,615,79],{"class":78},[58,617,618],{"class":60,"line":75},[58,619,85],{"class":78},[58,621,622],{"class":60,"line":82},[58,623,624],{"class":78},"    product_category,\n",[58,626,627],{"class":60,"line":88},[58,628,91],{"class":78},[58,630,631],{"class":60,"line":94},[58,632,633],{"class":64},"    -- Customer-level metrics\n",[58,635,636,638,640,642,644,646,648,651,653],{"class":60,"line":116},[58,637,220],{"class":97},[58,639,101],{"class":78},[58,641,104],{"class":71},[58,643,228],{"class":78},[58,645,231],{"class":71},[58,647,234],{"class":71},[58,649,650],{"class":78}," customer_id)          ",[58,652,110],{"class":71},[58,654,655],{"class":78}," cust_total,\n",[58,657,658],{"class":60,"line":140},[58,659,660],{"class":64},"    -- Category-level metrics\n",[58,662,663,665,667,669,671,673,675,678,680],{"class":60,"line":149},[58,664,98],{"class":97},[58,666,101],{"class":78},[58,668,104],{"class":71},[58,670,228],{"class":78},[58,672,231],{"class":71},[58,674,234],{"class":71},[58,676,677],{"class":78}," product_category)     ",[58,679,110],{"class":71},[58,681,682],{"class":78}," cat_avg,\n",[58,684,685],{"class":60,"line":168},[58,686,687],{"class":64},"    -- Company-wide rank by amount\n",[58,689,690,693,696,698,700,702,705,708,711,713],{"class":60,"line":528},[58,691,692],{"class":97},"    RANK",[58,694,695],{"class":78},"()            ",[58,697,104],{"class":71},[58,699,228],{"class":78},[58,701,319],{"class":71},[58,703,704],{"class":78}," total_amount ",[58,706,707],{"class":71},"DESC",[58,709,710],{"class":78},")        ",[58,712,110],{"class":71},[58,714,715],{"class":78}," company_rank\n",[58,717,718,720],{"class":60,"line":534},[58,719,143],{"class":71},[58,721,299],{"class":78},[10,723,725],{"id":724},"named-windows-avoiding-repetition","Named windows — avoiding repetition",[15,727,728,729,602],{},"When the same window spec appears multiple times, define it once with ",[18,730,731],{},"WINDOW",[49,733,735],{"className":51,"code":734,"language":53,"meta":54,"style":54},"SELECT\n    id,\n    customer_id,\n    total_amount,\n    SUM(total_amount)   OVER w AS running_total,\n    AVG(total_amount)   OVER w AS running_avg,\n    COUNT(*)            OVER w AS running_count\nFROM orders\nWINDOW w AS (PARTITION BY customer_id ORDER BY created_at)\nORDER BY customer_id, created_at;\n",[18,736,737,741,745,749,753,770,785,805,811,833],{"__ignoreMap":54},[58,738,739],{"class":60,"line":61},[58,740,72],{"class":71},[58,742,743],{"class":60,"line":68},[58,744,79],{"class":78},[58,746,747],{"class":60,"line":75},[58,748,85],{"class":78},[58,750,751],{"class":60,"line":82},[58,752,91],{"class":78},[58,754,755,757,760,762,765,767],{"class":60,"line":88},[58,756,220],{"class":97},[58,758,759],{"class":78},"(total_amount)   ",[58,761,104],{"class":71},[58,763,764],{"class":78}," w ",[58,766,110],{"class":71},[58,768,769],{"class":78}," running_total,\n",[58,771,772,774,776,778,780,782],{"class":60,"line":94},[58,773,98],{"class":97},[58,775,759],{"class":78},[58,777,104],{"class":71},[58,779,764],{"class":78},[58,781,110],{"class":71},[58,783,784],{"class":78}," running_avg,\n",[58,786,787,789,791,793,796,798,800,802],{"class":60,"line":116},[58,788,247],{"class":97},[58,790,250],{"class":78},[58,792,253],{"class":71},[58,794,795],{"class":78},")            ",[58,797,104],{"class":71},[58,799,764],{"class":78},[58,801,110],{"class":71},[58,803,804],{"class":78}," running_count\n",[58,806,807,809],{"class":60,"line":140},[58,808,143],{"class":71},[58,810,146],{"class":78},[58,812,813,815,817,819,821,823,825,828,830],{"class":60,"line":149},[58,814,731],{"class":71},[58,816,764],{"class":78},[58,818,110],{"class":71},[58,820,228],{"class":78},[58,822,231],{"class":71},[58,824,234],{"class":71},[58,826,827],{"class":78}," customer_id ",[58,829,319],{"class":71},[58,831,832],{"class":78}," created_at)\n",[58,834,835,837],{"class":60,"line":168},[58,836,319],{"class":71},[58,838,839],{"class":78}," customer_id, created_at;\n",[10,841,843],{"id":842},"recap","Recap",[15,845,846,847,849,850,852,853,855,856,858],{},"Window functions add computed columns that reference a related set of rows\nwithout collapsing the result — the key difference from ",[18,848,20],{},". ",[18,851,183],{}," divides rows into independent windows; ",[18,854,319],{}," inside ",[18,857,104],{}," makes the\nwindow position-aware (running totals, rankings). Use them when you need both\nthe detail row and an aggregate or rank in the same query — a pattern that would\notherwise require a self-join or subquery.",[860,861,862],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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":54,"searchDepth":68,"depth":68,"links":864},[865,866,867,868,869,870,871,872],{"id":12,"depth":68,"text":13},{"id":36,"depth":68,"text":37},{"id":177,"depth":68,"text":178},{"id":312,"depth":68,"text":313},{"id":449,"depth":68,"text":450},{"id":592,"depth":68,"text":593},{"id":724,"depth":68,"text":725},{"id":842,"depth":68,"text":843},"SQL window functions explained — OVER, PARTITION BY, ORDER BY inside windows, aggregate vs ranking window functions, and when to use them over GROUP BY.","medium","md","SQL",{},"\u002Fblog\u002Fsql-window-functions-over-partition","\u002Fsql\u002Fwindow-functions\u002Fwindow-basics",{"title":5,"description":873},"blog\u002Fsql-window-functions-over-partition","Window Function Basics","Window Functions","window-functions","2026-06-20","kUYTbARmlgfVwySTwWFgOMeCuMOMglcVQh6m3b0Aj3Q",1782244088366]