[{"data":1,"prerenderedAt":1298},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-window-frames-lag-lead":3},{"id":4,"title":5,"body":6,"description":1284,"difficulty":1285,"extension":1286,"framework":1287,"frameworkSlug":53,"meta":1288,"navigation":160,"order":74,"path":1289,"qaPath":1290,"seo":1291,"stem":1292,"subtopic":1293,"topic":1294,"topicSlug":1295,"updated":1296,"__hash__":1297},"blog\u002Fblog\u002Fsql-window-frames-lag-lead.md","SQL Window Frames, LAG, and LEAD — Moving Averages and Period Comparisons",{"type":7,"value":8,"toc":1275},"minimark",[9,14,28,38,45,49,174,180,184,195,368,371,375,396,657,764,768,786,927,931,1243,1247,1271],[10,11,13],"h2",{"id":12},"what-window-frames-control","What window frames control",[15,16,17,18,22,23,27],"p",{},"When you add ",[19,20,21],"code",{},"ORDER BY"," to a window function, the database needs to know which\nrows within the partition to include in each calculation. That set of rows is\ncalled the ",[24,25,26],"strong",{},"frame",". Without an explicit frame clause, the default is:",[29,30,35],"pre",{"className":31,"code":33,"language":34},[32],"language-text","RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n","text",[19,36,33],{"__ignoreMap":37},"",[15,39,40,41,44],{},"This means \"all rows from the start of the partition up to (and including) all\nrows with the same ORDER BY value as the current row\". For most use cases you\nwant ",[19,42,43],{},"ROWS BETWEEN"," instead, which counts physical rows not value ranges.",[10,46,48],{"id":47},"rows-vs-range","ROWS vs RANGE",[29,50,54],{"className":51,"code":52,"language":53,"meta":37,"style":37},"language-sql shiki shiki-themes github-light github-dark","-- Running total using ROWS (physical position — recommended for running totals)\nSELECT\n    order_date,\n    daily_revenue,\n    SUM(daily_revenue) OVER (\n        ORDER BY order_date\n        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n    ) AS running_total\nFROM daily_revenue_summary;\n\n-- RANGE would include all rows with the same order_date in the \"current\" group\n-- If two dates are equal, they get the same running total (often unexpected)\n","sql",[19,55,56,65,72,79,85,101,110,134,146,155,162,168],{"__ignoreMap":37},[57,58,61],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"sJ8bj","-- Running total using ROWS (physical position — recommended for running totals)\n",[57,66,68],{"class":59,"line":67},2,[57,69,71],{"class":70},"szBVR","SELECT\n",[57,73,75],{"class":59,"line":74},3,[57,76,78],{"class":77},"sVt8B","    order_date,\n",[57,80,82],{"class":59,"line":81},4,[57,83,84],{"class":77},"    daily_revenue,\n",[57,86,88,92,95,98],{"class":59,"line":87},5,[57,89,91],{"class":90},"sj4cs","    SUM",[57,93,94],{"class":77},"(daily_revenue) ",[57,96,97],{"class":70},"OVER",[57,99,100],{"class":77}," (\n",[57,102,104,107],{"class":59,"line":103},6,[57,105,106],{"class":70},"        ORDER BY",[57,108,109],{"class":77}," order_date\n",[57,111,113,116,119,122,125,128,131],{"class":59,"line":112},7,[57,114,115],{"class":70},"        ROWS",[57,117,118],{"class":70}," BETWEEN",[57,120,121],{"class":70}," UNBOUNDED",[57,123,124],{"class":70}," PRECEDING",[57,126,127],{"class":70}," AND",[57,129,130],{"class":77}," CURRENT ",[57,132,133],{"class":70},"ROW\n",[57,135,137,140,143],{"class":59,"line":136},8,[57,138,139],{"class":77},"    ) ",[57,141,142],{"class":70},"AS",[57,144,145],{"class":77}," running_total\n",[57,147,149,152],{"class":59,"line":148},9,[57,150,151],{"class":70},"FROM",[57,153,154],{"class":77}," daily_revenue_summary;\n",[57,156,158],{"class":59,"line":157},10,[57,159,161],{"emptyLinePlaceholder":160},true,"\n",[57,163,165],{"class":59,"line":164},11,[57,166,167],{"class":63},"-- RANGE would include all rows with the same order_date in the \"current\" group\n",[57,169,171],{"class":59,"line":170},12,[57,172,173],{"class":63},"-- If two dates are equal, they get the same running total (often unexpected)\n",[15,175,176,177,179],{},"Use ",[19,178,43],{}," for running totals and moving averages where you want an\nexact count of preceding rows.",[10,181,183],{"id":182},"moving-averages","Moving averages",[15,185,186,187,190,191,194],{},"A ",[24,188,189],{},"7-day moving average"," smooths out daily noise in metrics like revenue or\nsignups. The frame ",[19,192,193],{},"ROWS BETWEEN 6 PRECEDING AND CURRENT ROW"," means \"this row\nplus the 6 rows before it\" — 7 rows total.",[29,196,198],{"className":51,"code":197,"language":53,"meta":37,"style":37},"-- 7-day moving average of daily revenue\nSELECT\n    order_date,\n    daily_revenue,\n    ROUND(\n        AVG(daily_revenue) OVER (\n            ORDER BY order_date\n            ROWS BETWEEN 6 PRECEDING AND CURRENT ROW\n        ), 2\n    ) AS revenue_7d_avg\nFROM (\n    SELECT\n        DATE(created_at) AS order_date,\n        SUM(total_amount) AS daily_revenue\n    FROM orders\n    WHERE status = 'completed'\n    GROUP BY DATE(created_at)\n) daily\nORDER BY order_date;\n",[19,199,200,205,209,213,217,225,236,243,261,269,278,284,289,303,317,326,342,354,360],{"__ignoreMap":37},[57,201,202],{"class":59,"line":60},[57,203,204],{"class":63},"-- 7-day moving average of daily revenue\n",[57,206,207],{"class":59,"line":67},[57,208,71],{"class":70},[57,210,211],{"class":59,"line":74},[57,212,78],{"class":77},[57,214,215],{"class":59,"line":81},[57,216,84],{"class":77},[57,218,219,222],{"class":59,"line":87},[57,220,221],{"class":90},"    ROUND",[57,223,224],{"class":77},"(\n",[57,226,227,230,232,234],{"class":59,"line":103},[57,228,229],{"class":90},"        AVG",[57,231,94],{"class":77},[57,233,97],{"class":70},[57,235,100],{"class":77},[57,237,238,241],{"class":59,"line":112},[57,239,240],{"class":70},"            ORDER BY",[57,242,109],{"class":77},[57,244,245,248,250,253,255,257,259],{"class":59,"line":136},[57,246,247],{"class":70},"            ROWS",[57,249,118],{"class":70},[57,251,252],{"class":90}," 6",[57,254,124],{"class":70},[57,256,127],{"class":70},[57,258,130],{"class":77},[57,260,133],{"class":70},[57,262,263,266],{"class":59,"line":148},[57,264,265],{"class":77},"        ), ",[57,267,268],{"class":90},"2\n",[57,270,271,273,275],{"class":59,"line":157},[57,272,139],{"class":77},[57,274,142],{"class":70},[57,276,277],{"class":77}," revenue_7d_avg\n",[57,279,280,282],{"class":59,"line":164},[57,281,151],{"class":70},[57,283,100],{"class":77},[57,285,286],{"class":59,"line":170},[57,287,288],{"class":70},"    SELECT\n",[57,290,292,295,298,300],{"class":59,"line":291},13,[57,293,294],{"class":70},"        DATE",[57,296,297],{"class":77},"(created_at) ",[57,299,142],{"class":70},[57,301,302],{"class":77}," order_date,\n",[57,304,306,309,312,314],{"class":59,"line":305},14,[57,307,308],{"class":90},"        SUM",[57,310,311],{"class":77},"(total_amount) ",[57,313,142],{"class":70},[57,315,316],{"class":77}," daily_revenue\n",[57,318,320,323],{"class":59,"line":319},15,[57,321,322],{"class":70},"    FROM",[57,324,325],{"class":77}," orders\n",[57,327,329,332,335,338],{"class":59,"line":328},16,[57,330,331],{"class":70},"    WHERE",[57,333,334],{"class":70}," status",[57,336,337],{"class":70}," =",[57,339,341],{"class":340},"sZZnC"," 'completed'\n",[57,343,345,348,351],{"class":59,"line":344},17,[57,346,347],{"class":70},"    GROUP BY",[57,349,350],{"class":70}," DATE",[57,352,353],{"class":77},"(created_at)\n",[57,355,357],{"class":59,"line":356},18,[57,358,359],{"class":77},") daily\n",[57,361,363,365],{"class":59,"line":362},19,[57,364,21],{"class":70},[57,366,367],{"class":77}," order_date;\n",[15,369,370],{},"For the first 6 rows (where fewer than 7 days exist), the average is computed\nover however many rows are available — there is no implicit padding.",[10,372,374],{"id":373},"lag-and-lead-accessing-adjacent-rows","LAG and LEAD — accessing adjacent rows",[15,376,377,380,381,384,385,388,389,392,393,395],{},[19,378,379],{},"LAG(col, n)"," returns the value of ",[19,382,383],{},"col"," from ",[19,386,387],{},"n"," rows before the current row.\n",[19,390,391],{},"LEAD(col, n)"," returns it from ",[19,394,387],{}," rows ahead. Both return NULL if the offset\nrow does not exist (supply a third argument as the default).",[29,397,399],{"className":51,"code":398,"language":53,"meta":37,"style":37},"-- Month-over-month revenue change\nWITH monthly AS (\n    SELECT\n        DATE_TRUNC('month', created_at) AS month,\n        SUM(total_amount)               AS revenue\n    FROM orders\n    WHERE status = 'completed'\n    GROUP BY DATE_TRUNC('month', created_at)\n)\nSELECT\n    month,\n    revenue,\n    LAG(revenue)  OVER (ORDER BY month)  AS prev_month_revenue,\n    revenue - LAG(revenue) OVER (ORDER BY month) AS mom_change,\n    ROUND(\n        100.0 * (revenue - LAG(revenue) OVER (ORDER BY month))\n        \u002F NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 1\n    ) AS mom_pct_change\nFROM monthly\nORDER BY month;\n",[19,400,401,406,418,422,441,453,459,469,481,486,490,497,502,527,557,563,597,631,640,647],{"__ignoreMap":37},[57,402,403],{"class":59,"line":60},[57,404,405],{"class":63},"-- Month-over-month revenue change\n",[57,407,408,411,414,416],{"class":59,"line":67},[57,409,410],{"class":70},"WITH",[57,412,413],{"class":77}," monthly ",[57,415,142],{"class":70},[57,417,100],{"class":77},[57,419,420],{"class":59,"line":74},[57,421,288],{"class":70},[57,423,424,427,430,433,435,438],{"class":59,"line":81},[57,425,426],{"class":77},"        DATE_TRUNC(",[57,428,429],{"class":340},"'month'",[57,431,432],{"class":77},", created_at) ",[57,434,142],{"class":70},[57,436,437],{"class":70}," month",[57,439,440],{"class":77},",\n",[57,442,443,445,448,450],{"class":59,"line":87},[57,444,308],{"class":90},[57,446,447],{"class":77},"(total_amount)               ",[57,449,142],{"class":70},[57,451,452],{"class":77}," revenue\n",[57,454,455,457],{"class":59,"line":103},[57,456,322],{"class":70},[57,458,325],{"class":77},[57,460,461,463,465,467],{"class":59,"line":112},[57,462,331],{"class":70},[57,464,334],{"class":70},[57,466,337],{"class":70},[57,468,341],{"class":340},[57,470,471,473,476,478],{"class":59,"line":136},[57,472,347],{"class":70},[57,474,475],{"class":77}," DATE_TRUNC(",[57,477,429],{"class":340},[57,479,480],{"class":77},", created_at)\n",[57,482,483],{"class":59,"line":148},[57,484,485],{"class":77},")\n",[57,487,488],{"class":59,"line":157},[57,489,71],{"class":70},[57,491,492,495],{"class":59,"line":164},[57,493,494],{"class":70},"    month",[57,496,440],{"class":77},[57,498,499],{"class":59,"line":170},[57,500,501],{"class":77},"    revenue,\n",[57,503,504,507,510,512,515,517,519,522,524],{"class":59,"line":291},[57,505,506],{"class":90},"    LAG",[57,508,509],{"class":77},"(revenue)  ",[57,511,97],{"class":70},[57,513,514],{"class":77}," (",[57,516,21],{"class":70},[57,518,437],{"class":70},[57,520,521],{"class":77},")  ",[57,523,142],{"class":70},[57,525,526],{"class":77}," prev_month_revenue,\n",[57,528,529,532,535,538,541,543,545,547,549,552,554],{"class":59,"line":305},[57,530,531],{"class":77},"    revenue ",[57,533,534],{"class":70},"-",[57,536,537],{"class":90}," LAG",[57,539,540],{"class":77},"(revenue) ",[57,542,97],{"class":70},[57,544,514],{"class":77},[57,546,21],{"class":70},[57,548,437],{"class":70},[57,550,551],{"class":77},") ",[57,553,142],{"class":70},[57,555,556],{"class":77}," mom_change,\n",[57,558,559,561],{"class":59,"line":319},[57,560,221],{"class":90},[57,562,224],{"class":77},[57,564,565,568,571,574,577,580,582,584,586,588,590,592,594],{"class":59,"line":328},[57,566,567],{"class":90},"        100",[57,569,570],{"class":77},".",[57,572,573],{"class":90},"0",[57,575,576],{"class":70}," *",[57,578,579],{"class":77}," (revenue ",[57,581,534],{"class":70},[57,583,537],{"class":90},[57,585,540],{"class":77},[57,587,97],{"class":70},[57,589,514],{"class":77},[57,591,21],{"class":70},[57,593,437],{"class":70},[57,595,596],{"class":77},"))\n",[57,598,599,602,605,608,611,613,615,617,619,621,624,626,628],{"class":59,"line":344},[57,600,601],{"class":70},"        \u002F",[57,603,604],{"class":90}," NULLIF",[57,606,607],{"class":77},"(",[57,609,610],{"class":90},"LAG",[57,612,540],{"class":77},[57,614,97],{"class":70},[57,616,514],{"class":77},[57,618,21],{"class":70},[57,620,437],{"class":70},[57,622,623],{"class":77},"), ",[57,625,573],{"class":90},[57,627,623],{"class":77},[57,629,630],{"class":90},"1\n",[57,632,633,635,637],{"class":59,"line":356},[57,634,139],{"class":77},[57,636,142],{"class":70},[57,638,639],{"class":77}," mom_pct_change\n",[57,641,642,644],{"class":59,"line":362},[57,643,151],{"class":70},[57,645,646],{"class":77}," monthly\n",[57,648,650,652,654],{"class":59,"line":649},20,[57,651,21],{"class":70},[57,653,437],{"class":70},[57,655,656],{"class":77},";\n",[29,658,660],{"className":51,"code":659,"language":53,"meta":37,"style":37},"-- Session gap detection: find sessions with > 30 min gap from previous\nSELECT\n    session_id,\n    user_id,\n    started_at,\n    LAG(started_at) OVER (PARTITION BY user_id ORDER BY started_at) AS prev_start,\n    started_at - LAG(started_at) OVER (PARTITION BY user_id ORDER BY started_at)\n        AS gap\nFROM user_sessions\nORDER BY user_id, started_at;\n",[19,661,662,667,671,676,681,686,716,742,750,757],{"__ignoreMap":37},[57,663,664],{"class":59,"line":60},[57,665,666],{"class":63},"-- Session gap detection: find sessions with > 30 min gap from previous\n",[57,668,669],{"class":59,"line":67},[57,670,71],{"class":70},[57,672,673],{"class":59,"line":74},[57,674,675],{"class":77},"    session_id,\n",[57,677,678],{"class":59,"line":81},[57,679,680],{"class":77},"    user_id,\n",[57,682,683],{"class":59,"line":87},[57,684,685],{"class":77},"    started_at,\n",[57,687,688,690,693,695,697,700,703,706,708,711,713],{"class":59,"line":103},[57,689,506],{"class":90},[57,691,692],{"class":77},"(started_at) ",[57,694,97],{"class":70},[57,696,514],{"class":77},[57,698,699],{"class":70},"PARTITION",[57,701,702],{"class":70}," BY",[57,704,705],{"class":77}," user_id ",[57,707,21],{"class":70},[57,709,710],{"class":77}," started_at) ",[57,712,142],{"class":70},[57,714,715],{"class":77}," prev_start,\n",[57,717,718,721,723,725,727,729,731,733,735,737,739],{"class":59,"line":112},[57,719,720],{"class":77},"    started_at ",[57,722,534],{"class":70},[57,724,537],{"class":90},[57,726,692],{"class":77},[57,728,97],{"class":70},[57,730,514],{"class":77},[57,732,699],{"class":70},[57,734,702],{"class":70},[57,736,705],{"class":77},[57,738,21],{"class":70},[57,740,741],{"class":77}," started_at)\n",[57,743,744,747],{"class":59,"line":136},[57,745,746],{"class":70},"        AS",[57,748,749],{"class":77}," gap\n",[57,751,752,754],{"class":59,"line":148},[57,753,151],{"class":70},[57,755,756],{"class":77}," user_sessions\n",[57,758,759,761],{"class":59,"line":157},[57,760,21],{"class":70},[57,762,763],{"class":77}," user_id, started_at;\n",[10,765,767],{"id":766},"first_value-and-last_value","FIRST_VALUE and LAST_VALUE",[15,769,770,773,774,777,778,781,782,785],{},[19,771,772],{},"FIRST_VALUE(col)"," returns the value from the first row of the window frame.\n",[19,775,776],{},"LAST_VALUE(col)"," returns the value from the last row — but the default frame\n(",[19,779,780],{},"RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW",") means ",[19,783,784],{},"LAST_VALUE"," sees\nonly rows up to the current one. To get the true last value of the partition,\nextend the frame explicitly.",[29,787,789],{"className":51,"code":788,"language":53,"meta":37,"style":37},"-- Each order compared to the customer's first and most recent order amount\nSELECT\n    id,\n    customer_id,\n    total_amount,\n    created_at,\n    FIRST_VALUE(total_amount) OVER (\n        PARTITION BY customer_id ORDER BY created_at\n        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\n    ) AS first_order_amount,\n    LAST_VALUE(total_amount) OVER (\n        PARTITION BY customer_id ORDER BY created_at\n        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\n    ) AS latest_order_amount\nFROM orders;\n",[19,790,791,796,800,805,810,815,820,831,846,863,872,883,895,911,920],{"__ignoreMap":37},[57,792,793],{"class":59,"line":60},[57,794,795],{"class":63},"-- Each order compared to the customer's first and most recent order amount\n",[57,797,798],{"class":59,"line":67},[57,799,71],{"class":70},[57,801,802],{"class":59,"line":74},[57,803,804],{"class":77},"    id,\n",[57,806,807],{"class":59,"line":81},[57,808,809],{"class":77},"    customer_id,\n",[57,811,812],{"class":59,"line":87},[57,813,814],{"class":77},"    total_amount,\n",[57,816,817],{"class":59,"line":103},[57,818,819],{"class":77},"    created_at,\n",[57,821,822,825,827,829],{"class":59,"line":112},[57,823,824],{"class":90},"    FIRST_VALUE",[57,826,311],{"class":77},[57,828,97],{"class":70},[57,830,100],{"class":77},[57,832,833,836,838,841,843],{"class":59,"line":136},[57,834,835],{"class":70},"        PARTITION",[57,837,702],{"class":70},[57,839,840],{"class":77}," customer_id ",[57,842,21],{"class":70},[57,844,845],{"class":77}," created_at\n",[57,847,848,850,852,854,856,858,860],{"class":59,"line":148},[57,849,115],{"class":70},[57,851,118],{"class":70},[57,853,121],{"class":70},[57,855,124],{"class":70},[57,857,127],{"class":70},[57,859,121],{"class":70},[57,861,862],{"class":70}," FOLLOWING\n",[57,864,865,867,869],{"class":59,"line":157},[57,866,139],{"class":77},[57,868,142],{"class":70},[57,870,871],{"class":77}," first_order_amount,\n",[57,873,874,877,879,881],{"class":59,"line":164},[57,875,876],{"class":90},"    LAST_VALUE",[57,878,311],{"class":77},[57,880,97],{"class":70},[57,882,100],{"class":77},[57,884,885,887,889,891,893],{"class":59,"line":170},[57,886,835],{"class":70},[57,888,702],{"class":70},[57,890,840],{"class":77},[57,892,21],{"class":70},[57,894,845],{"class":77},[57,896,897,899,901,903,905,907,909],{"class":59,"line":291},[57,898,115],{"class":70},[57,900,118],{"class":70},[57,902,121],{"class":70},[57,904,124],{"class":70},[57,906,127],{"class":70},[57,908,121],{"class":70},[57,910,862],{"class":70},[57,912,913,915,917],{"class":59,"line":305},[57,914,139],{"class":77},[57,916,142],{"class":70},[57,918,919],{"class":77}," latest_order_amount\n",[57,921,922,924],{"class":59,"line":319},[57,923,151],{"class":70},[57,925,926],{"class":77}," orders;\n",[10,928,930],{"id":929},"year-over-year-comparison-with-lag","Year-over-year comparison with LAG",[29,932,934],{"className":51,"code":933,"language":53,"meta":37,"style":37},"-- YoY revenue comparison per product category\nWITH yearly AS (\n    SELECT\n        EXTRACT(YEAR FROM o.created_at) AS yr,\n        p.category,\n        SUM(i.unit_price * i.quantity)  AS revenue\n    FROM order_items i\n    JOIN products p ON p.id = i.product_id\n    JOIN orders   o ON o.id = i.order_id\n    GROUP BY yr, p.category\n)\nSELECT\n    yr,\n    category,\n    revenue,\n    LAG(revenue) OVER (PARTITION BY category ORDER BY yr) AS prev_year,\n    ROUND(100.0 * (revenue - LAG(revenue) OVER (PARTITION BY category ORDER BY yr))\n          \u002F NULLIF(LAG(revenue) OVER (PARTITION BY category ORDER BY yr), 0), 1)\n        AS yoy_pct\nFROM yearly\nORDER BY category, yr;\n",[19,935,936,941,952,956,982,994,1024,1031,1059,1083,1097,1101,1105,1110,1115,1119,1146,1184,1221,1228,1235],{"__ignoreMap":37},[57,937,938],{"class":59,"line":60},[57,939,940],{"class":63},"-- YoY revenue comparison per product category\n",[57,942,943,945,948,950],{"class":59,"line":67},[57,944,410],{"class":70},[57,946,947],{"class":77}," yearly ",[57,949,142],{"class":70},[57,951,100],{"class":77},[57,953,954],{"class":59,"line":74},[57,955,288],{"class":70},[57,957,958,961,964,967,970,972,975,977,979],{"class":59,"line":81},[57,959,960],{"class":77},"        EXTRACT(",[57,962,963],{"class":70},"YEAR",[57,965,966],{"class":70}," FROM",[57,968,969],{"class":90}," o",[57,971,570],{"class":77},[57,973,974],{"class":90},"created_at",[57,976,551],{"class":77},[57,978,142],{"class":70},[57,980,981],{"class":77}," yr,\n",[57,983,984,987,989,992],{"class":59,"line":87},[57,985,986],{"class":90},"        p",[57,988,570],{"class":77},[57,990,991],{"class":90},"category",[57,993,440],{"class":77},[57,995,996,998,1000,1003,1005,1008,1010,1013,1015,1018,1020,1022],{"class":59,"line":103},[57,997,308],{"class":90},[57,999,607],{"class":77},[57,1001,1002],{"class":90},"i",[57,1004,570],{"class":77},[57,1006,1007],{"class":90},"unit_price",[57,1009,576],{"class":70},[57,1011,1012],{"class":90}," i",[57,1014,570],{"class":77},[57,1016,1017],{"class":90},"quantity",[57,1019,521],{"class":77},[57,1021,142],{"class":70},[57,1023,452],{"class":77},[57,1025,1026,1028],{"class":59,"line":112},[57,1027,322],{"class":70},[57,1029,1030],{"class":77}," order_items i\n",[57,1032,1033,1036,1039,1042,1045,1047,1050,1052,1054,1056],{"class":59,"line":136},[57,1034,1035],{"class":70},"    JOIN",[57,1037,1038],{"class":77}," products p ",[57,1040,1041],{"class":70},"ON",[57,1043,1044],{"class":90}," p",[57,1046,570],{"class":77},[57,1048,1049],{"class":90},"id",[57,1051,337],{"class":70},[57,1053,1012],{"class":90},[57,1055,570],{"class":77},[57,1057,1058],{"class":90},"product_id\n",[57,1060,1061,1063,1066,1068,1070,1072,1074,1076,1078,1080],{"class":59,"line":148},[57,1062,1035],{"class":70},[57,1064,1065],{"class":77}," orders   o ",[57,1067,1041],{"class":70},[57,1069,969],{"class":90},[57,1071,570],{"class":77},[57,1073,1049],{"class":90},[57,1075,337],{"class":70},[57,1077,1012],{"class":90},[57,1079,570],{"class":77},[57,1081,1082],{"class":90},"order_id\n",[57,1084,1085,1087,1090,1092,1094],{"class":59,"line":157},[57,1086,347],{"class":70},[57,1088,1089],{"class":77}," yr, ",[57,1091,15],{"class":90},[57,1093,570],{"class":77},[57,1095,1096],{"class":90},"category\n",[57,1098,1099],{"class":59,"line":164},[57,1100,485],{"class":77},[57,1102,1103],{"class":59,"line":170},[57,1104,71],{"class":70},[57,1106,1107],{"class":59,"line":291},[57,1108,1109],{"class":77},"    yr,\n",[57,1111,1112],{"class":59,"line":305},[57,1113,1114],{"class":77},"    category,\n",[57,1116,1117],{"class":59,"line":319},[57,1118,501],{"class":77},[57,1120,1121,1123,1125,1127,1129,1131,1133,1136,1138,1141,1143],{"class":59,"line":328},[57,1122,506],{"class":90},[57,1124,540],{"class":77},[57,1126,97],{"class":70},[57,1128,514],{"class":77},[57,1130,699],{"class":70},[57,1132,702],{"class":70},[57,1134,1135],{"class":77}," category ",[57,1137,21],{"class":70},[57,1139,1140],{"class":77}," yr) ",[57,1142,142],{"class":70},[57,1144,1145],{"class":77}," prev_year,\n",[57,1147,1148,1150,1152,1155,1157,1159,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181],{"class":59,"line":344},[57,1149,221],{"class":90},[57,1151,607],{"class":77},[57,1153,1154],{"class":90},"100",[57,1156,570],{"class":77},[57,1158,573],{"class":90},[57,1160,576],{"class":70},[57,1162,579],{"class":77},[57,1164,534],{"class":70},[57,1166,537],{"class":90},[57,1168,540],{"class":77},[57,1170,97],{"class":70},[57,1172,514],{"class":77},[57,1174,699],{"class":70},[57,1176,702],{"class":70},[57,1178,1135],{"class":77},[57,1180,21],{"class":70},[57,1182,1183],{"class":77}," yr))\n",[57,1185,1186,1189,1191,1193,1195,1197,1199,1201,1203,1205,1207,1209,1212,1214,1216,1219],{"class":59,"line":356},[57,1187,1188],{"class":70},"          \u002F",[57,1190,604],{"class":90},[57,1192,607],{"class":77},[57,1194,610],{"class":90},[57,1196,540],{"class":77},[57,1198,97],{"class":70},[57,1200,514],{"class":77},[57,1202,699],{"class":70},[57,1204,702],{"class":70},[57,1206,1135],{"class":77},[57,1208,21],{"class":70},[57,1210,1211],{"class":77}," yr), ",[57,1213,573],{"class":90},[57,1215,623],{"class":77},[57,1217,1218],{"class":90},"1",[57,1220,485],{"class":77},[57,1222,1223,1225],{"class":59,"line":362},[57,1224,746],{"class":70},[57,1226,1227],{"class":77}," yoy_pct\n",[57,1229,1230,1232],{"class":59,"line":649},[57,1231,151],{"class":70},[57,1233,1234],{"class":77}," yearly\n",[57,1236,1238,1240],{"class":59,"line":1237},21,[57,1239,21],{"class":70},[57,1241,1242],{"class":77}," category, yr;\n",[10,1244,1246],{"id":1245},"recap","Recap",[15,1248,1249,1250,1252,1253,1255,1256,1259,1260,1263,1264,1266,1267,1270],{},"Window frames control which rows each window calculation sees. Use ",[19,1251,43],{}," for running totals and moving averages where physical row position\nmatters. ",[19,1254,610],{}," and ",[19,1257,1258],{},"LEAD"," access values from adjacent rows — the cleanest\npattern for period-over-period comparisons. ",[19,1261,1262],{},"FIRST_VALUE"," \u002F ",[19,1265,784],{}," need\nan explicit ",[19,1268,1269],{},"ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"," frame to\nsee the whole partition rather than just up to the current row.",[1272,1273,1274],"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 .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);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":37,"searchDepth":67,"depth":67,"links":1276},[1277,1278,1279,1280,1281,1282,1283],{"id":12,"depth":67,"text":13},{"id":47,"depth":67,"text":48},{"id":182,"depth":67,"text":183},{"id":373,"depth":67,"text":374},{"id":766,"depth":67,"text":767},{"id":929,"depth":67,"text":930},{"id":1245,"depth":67,"text":1246},"SQL window frames (ROWS vs RANGE), LAG, LEAD, FIRST_VALUE, LAST_VALUE — period-over-period comparisons, moving averages, and gap detection explained.","hard","md","SQL",{},"\u002Fblog\u002Fsql-window-frames-lag-lead","\u002Fsql\u002Fwindow-functions\u002Fframes-and-offsets",{"title":5,"description":1284},"blog\u002Fsql-window-frames-lag-lead","Frames & Offset Functions","Window Functions","window-functions","2026-06-20","eo1G40mMF6Ip8fCtEbOY0tbr8XFZe1Dn1l4zUGonebE",1782244089243]