[{"data":1,"prerenderedAt":1298},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-aggregation-group-by-having":3},{"id":4,"title":5,"body":6,"description":1284,"difficulty":1285,"extension":1286,"framework":1287,"frameworkSlug":68,"meta":1288,"navigation":118,"order":122,"path":1289,"qaPath":1290,"seo":1291,"stem":1292,"subtopic":1293,"topic":1294,"topicSlug":1295,"updated":1296,"__hash__":1297},"blog\u002Fblog\u002Fsql-aggregation-group-by-having.md","SQL Aggregation — GROUP BY, HAVING, and Aggregate Functions",{"type":7,"value":8,"toc":1273},"minimark",[9,14,18,59,63,255,270,274,388,407,411,416,501,518,522,733,737,745,904,913,917,924,1106,1110,1116,1238,1242,1269],[10,11,13],"h2",{"id":12},"what-aggregation-is-for","What aggregation is for",[15,16,17],"p",{},"Raw tables store one fact per row. Aggregation collapses many rows into summary\nstatistics — total revenue per month, average order value per customer, number\nof open support tickets per team. Every report, dashboard, and analytics query\nyou write will use it.",[15,19,20,21,25,26,25,29,25,32,25,35,38,39,42,43,46,47,50,51,54,55,58],{},"SQL's aggregate functions are: ",[22,23,24],"code",{},"COUNT",", ",[22,27,28],{},"SUM",[22,30,31],{},"AVG",[22,33,34],{},"MIN",[22,36,37],{},"MAX",", and\n",[22,40,41],{},"STRING_AGG"," \u002F ",[22,44,45],{},"GROUP_CONCAT",". They appear in ",[22,48,49],{},"SELECT"," and ",[22,52,53],{},"HAVING",", and they\noperate on groups defined by ",[22,56,57],{},"GROUP BY",".",[10,60,62],{"id":61},"count-the-four-forms","COUNT — the four forms",[64,65,70],"pre",{"className":66,"code":67,"language":68,"meta":69,"style":69},"language-sql shiki shiki-themes github-light github-dark","-- COUNT(*) counts every row including those with NULLs\nSELECT COUNT(*) AS total_orders FROM orders;\n\n-- COUNT(column) counts non-NULL values only\nSELECT COUNT(coupon_code) AS orders_with_coupon FROM orders;\n\n-- COUNT(DISTINCT column) counts unique non-NULL values\nSELECT COUNT(DISTINCT customer_id) AS unique_buyers FROM orders;\n\n-- Conditional count (orders placed this month)\nSELECT COUNT(*) FILTER (WHERE created_at >= DATE_TRUNC('month', NOW()))\nAS orders_this_month\nFROM orders;\n-- MySQL equivalent: COUNT(CASE WHEN created_at >= ... THEN 1 END)\n","sql","",[22,71,72,81,113,120,126,145,150,156,180,185,191,234,242,249],{"__ignoreMap":69},[73,74,77],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sJ8bj","-- COUNT(*) counts every row including those with NULLs\n",[73,82,84,87,91,95,98,101,104,107,110],{"class":75,"line":83},2,[73,85,49],{"class":86},"szBVR",[73,88,90],{"class":89},"sj4cs"," COUNT",[73,92,94],{"class":93},"sVt8B","(",[73,96,97],{"class":86},"*",[73,99,100],{"class":93},") ",[73,102,103],{"class":86},"AS",[73,105,106],{"class":93}," total_orders ",[73,108,109],{"class":86},"FROM",[73,111,112],{"class":93}," orders;\n",[73,114,116],{"class":75,"line":115},3,[73,117,119],{"emptyLinePlaceholder":118},true,"\n",[73,121,123],{"class":75,"line":122},4,[73,124,125],{"class":79},"-- COUNT(column) counts non-NULL values only\n",[73,127,129,131,133,136,138,141,143],{"class":75,"line":128},5,[73,130,49],{"class":86},[73,132,90],{"class":89},[73,134,135],{"class":93},"(coupon_code) ",[73,137,103],{"class":86},[73,139,140],{"class":93}," orders_with_coupon ",[73,142,109],{"class":86},[73,144,112],{"class":93},[73,146,148],{"class":75,"line":147},6,[73,149,119],{"emptyLinePlaceholder":118},[73,151,153],{"class":75,"line":152},7,[73,154,155],{"class":79},"-- COUNT(DISTINCT column) counts unique non-NULL values\n",[73,157,159,161,163,165,168,171,173,176,178],{"class":75,"line":158},8,[73,160,49],{"class":86},[73,162,90],{"class":89},[73,164,94],{"class":93},[73,166,167],{"class":86},"DISTINCT",[73,169,170],{"class":93}," customer_id) ",[73,172,103],{"class":86},[73,174,175],{"class":93}," unique_buyers ",[73,177,109],{"class":86},[73,179,112],{"class":93},[73,181,183],{"class":75,"line":182},9,[73,184,119],{"emptyLinePlaceholder":118},[73,186,188],{"class":75,"line":187},10,[73,189,190],{"class":79},"-- Conditional count (orders placed this month)\n",[73,192,194,196,198,200,202,204,207,210,213,216,219,222,226,228,231],{"class":75,"line":193},11,[73,195,49],{"class":86},[73,197,90],{"class":89},[73,199,94],{"class":93},[73,201,97],{"class":86},[73,203,100],{"class":93},[73,205,206],{"class":86},"FILTER",[73,208,209],{"class":93}," (",[73,211,212],{"class":86},"WHERE",[73,214,215],{"class":93}," created_at ",[73,217,218],{"class":86},">=",[73,220,221],{"class":93}," DATE_TRUNC(",[73,223,225],{"class":224},"sZZnC","'month'",[73,227,25],{"class":93},[73,229,230],{"class":86},"NOW",[73,232,233],{"class":93},"()))\n",[73,235,237,239],{"class":75,"line":236},12,[73,238,103],{"class":86},[73,240,241],{"class":93}," orders_this_month\n",[73,243,245,247],{"class":75,"line":244},13,[73,246,109],{"class":86},[73,248,112],{"class":93},[73,250,252],{"class":75,"line":251},14,[73,253,254],{"class":79},"-- MySQL equivalent: COUNT(CASE WHEN created_at >= ... THEN 1 END)\n",[15,256,257,258,261,262,265,266,269],{},"A common bug: using ",[22,259,260],{},"COUNT(*)"," with a ",[22,263,264],{},"LEFT JOIN"," and expecting zero for\nunmatched rows. It returns 1, because the NULL-padded row still counts. Use\n",[22,267,268],{},"COUNT(right_table.id)"," instead.",[10,271,273],{"id":272},"sum-avg-min-max","SUM, AVG, MIN, MAX",[64,275,277],{"className":66,"code":276,"language":68,"meta":69,"style":69},"-- Revenue summary for last 30 days\nSELECT\n    SUM(total_amount)            AS gross_revenue,\n    AVG(total_amount)            AS avg_order_value,\n    MIN(total_amount)            AS smallest_order,\n    MAX(total_amount)            AS largest_order,\n    COUNT(*)                     AS order_count\nFROM orders\nWHERE created_at >= NOW() - INTERVAL '30 days';\n",[22,278,279,284,289,302,314,326,338,355,362],{"__ignoreMap":69},[73,280,281],{"class":75,"line":76},[73,282,283],{"class":79},"-- Revenue summary for last 30 days\n",[73,285,286],{"class":75,"line":83},[73,287,288],{"class":86},"SELECT\n",[73,290,291,294,297,299],{"class":75,"line":115},[73,292,293],{"class":89},"    SUM",[73,295,296],{"class":93},"(total_amount)            ",[73,298,103],{"class":86},[73,300,301],{"class":93}," gross_revenue,\n",[73,303,304,307,309,311],{"class":75,"line":122},[73,305,306],{"class":89},"    AVG",[73,308,296],{"class":93},[73,310,103],{"class":86},[73,312,313],{"class":93}," avg_order_value,\n",[73,315,316,319,321,323],{"class":75,"line":128},[73,317,318],{"class":89},"    MIN",[73,320,296],{"class":93},[73,322,103],{"class":86},[73,324,325],{"class":93}," smallest_order,\n",[73,327,328,331,333,335],{"class":75,"line":147},[73,329,330],{"class":89},"    MAX",[73,332,296],{"class":93},[73,334,103],{"class":86},[73,336,337],{"class":93}," largest_order,\n",[73,339,340,343,345,347,350,352],{"class":75,"line":152},[73,341,342],{"class":89},"    COUNT",[73,344,94],{"class":93},[73,346,97],{"class":86},[73,348,349],{"class":93},")                     ",[73,351,103],{"class":86},[73,353,354],{"class":93}," order_count\n",[73,356,357,359],{"class":75,"line":158},[73,358,109],{"class":86},[73,360,361],{"class":93}," orders\n",[73,363,364,366,368,370,373,376,379,382,385],{"class":75,"line":182},[73,365,212],{"class":86},[73,367,215],{"class":93},[73,369,218],{"class":86},[73,371,372],{"class":86}," NOW",[73,374,375],{"class":93},"() ",[73,377,378],{"class":86},"-",[73,380,381],{"class":93}," INTERVAL ",[73,383,384],{"class":224},"'30 days'",[73,386,387],{"class":93},";\n",[15,389,390,391,393,394,398,399,402,403,406],{},"All aggregate functions except ",[22,392,260],{}," ",[395,396,397],"strong",{},"ignore NULLs",". This means\n",[22,400,401],{},"AVG(discount)"," divides by the count of non-NULL rows — if half your orders\nhave no discount (NULL), the average overstates the real per-order discount.\nUse ",[22,404,405],{},"AVG(COALESCE(discount, 0))"," when you want NULLs counted as zero.",[10,408,410],{"id":409},"group-by-summaries-per-group","GROUP BY — summaries per group",[15,412,413,415],{},[22,414,57],{}," splits rows into buckets by one or more columns, then applies the\naggregate function within each bucket.",[64,417,419],{"className":66,"code":418,"language":68,"meta":69,"style":69},"-- Revenue and order count per customer\nSELECT\n    customer_id,\n    COUNT(*)                   AS order_count,\n    SUM(total_amount)          AS lifetime_value,\n    MAX(created_at)            AS last_order_date\nFROM orders\nGROUP BY customer_id\nORDER BY lifetime_value DESC;\n",[22,420,421,426,430,435,451,463,475,481,488],{"__ignoreMap":69},[73,422,423],{"class":75,"line":76},[73,424,425],{"class":79},"-- Revenue and order count per customer\n",[73,427,428],{"class":75,"line":83},[73,429,288],{"class":86},[73,431,432],{"class":75,"line":115},[73,433,434],{"class":93},"    customer_id,\n",[73,436,437,439,441,443,446,448],{"class":75,"line":122},[73,438,342],{"class":89},[73,440,94],{"class":93},[73,442,97],{"class":86},[73,444,445],{"class":93},")                   ",[73,447,103],{"class":86},[73,449,450],{"class":93}," order_count,\n",[73,452,453,455,458,460],{"class":75,"line":128},[73,454,293],{"class":89},[73,456,457],{"class":93},"(total_amount)          ",[73,459,103],{"class":86},[73,461,462],{"class":93}," lifetime_value,\n",[73,464,465,467,470,472],{"class":75,"line":147},[73,466,330],{"class":89},[73,468,469],{"class":93},"(created_at)            ",[73,471,103],{"class":86},[73,473,474],{"class":93}," last_order_date\n",[73,476,477,479],{"class":75,"line":152},[73,478,109],{"class":86},[73,480,361],{"class":93},[73,482,483,485],{"class":75,"line":158},[73,484,57],{"class":86},[73,486,487],{"class":93}," customer_id\n",[73,489,490,493,496,499],{"class":75,"line":182},[73,491,492],{"class":86},"ORDER BY",[73,494,495],{"class":93}," lifetime_value ",[73,497,498],{"class":86},"DESC",[73,500,387],{"class":93},[15,502,503,504,506,507,510,511,513,514,517],{},"Every column in ",[22,505,49],{}," that is ",[395,508,509],{},"not"," inside an aggregate function must\nappear in ",[22,512,57],{},". The database raises an error otherwise (except in MySQL\nwith ",[22,515,516],{},"ONLY_FULL_GROUP_BY"," disabled, where the behaviour is undefined).",[10,519,521],{"id":520},"multi-column-group-by","Multi-column GROUP BY",[64,523,525],{"className":66,"code":524,"language":68,"meta":69,"style":69},"-- Monthly revenue by product category\nSELECT\n    DATE_TRUNC('month', o.created_at) AS month,\n    p.category,\n    SUM(i.unit_price * i.quantity)    AS revenue,\n    COUNT(DISTINCT o.id)              AS orders\nFROM orders o\nJOIN order_items i ON i.order_id = o.id\nJOIN products    p ON p.id = i.product_id\nGROUP BY DATE_TRUNC('month', o.created_at), p.category\nORDER BY month DESC, revenue DESC;\n",[22,526,527,532,536,563,575,608,631,638,666,691,717],{"__ignoreMap":69},[73,528,529],{"class":75,"line":76},[73,530,531],{"class":79},"-- Monthly revenue by product category\n",[73,533,534],{"class":75,"line":83},[73,535,288],{"class":86},[73,537,538,541,543,545,548,550,553,555,557,560],{"class":75,"line":115},[73,539,540],{"class":93},"    DATE_TRUNC(",[73,542,225],{"class":224},[73,544,25],{"class":93},[73,546,547],{"class":89},"o",[73,549,58],{"class":93},[73,551,552],{"class":89},"created_at",[73,554,100],{"class":93},[73,556,103],{"class":86},[73,558,559],{"class":86}," month",[73,561,562],{"class":93},",\n",[73,564,565,568,570,573],{"class":75,"line":122},[73,566,567],{"class":89},"    p",[73,569,58],{"class":93},[73,571,572],{"class":89},"category",[73,574,562],{"class":93},[73,576,577,579,581,584,586,589,592,595,597,600,603,605],{"class":75,"line":128},[73,578,293],{"class":89},[73,580,94],{"class":93},[73,582,583],{"class":89},"i",[73,585,58],{"class":93},[73,587,588],{"class":89},"unit_price",[73,590,591],{"class":86}," *",[73,593,594],{"class":89}," i",[73,596,58],{"class":93},[73,598,599],{"class":89},"quantity",[73,601,602],{"class":93},")    ",[73,604,103],{"class":86},[73,606,607],{"class":93}," revenue,\n",[73,609,610,612,614,616,619,621,624,627,629],{"class":75,"line":147},[73,611,342],{"class":89},[73,613,94],{"class":93},[73,615,167],{"class":86},[73,617,618],{"class":89}," o",[73,620,58],{"class":93},[73,622,623],{"class":89},"id",[73,625,626],{"class":93},")              ",[73,628,103],{"class":86},[73,630,361],{"class":93},[73,632,633,635],{"class":75,"line":152},[73,634,109],{"class":86},[73,636,637],{"class":93}," orders o\n",[73,639,640,643,646,649,651,653,656,659,661,663],{"class":75,"line":158},[73,641,642],{"class":86},"JOIN",[73,644,645],{"class":93}," order_items i ",[73,647,648],{"class":86},"ON",[73,650,594],{"class":89},[73,652,58],{"class":93},[73,654,655],{"class":89},"order_id",[73,657,658],{"class":86}," =",[73,660,618],{"class":89},[73,662,58],{"class":93},[73,664,665],{"class":89},"id\n",[73,667,668,670,673,675,678,680,682,684,686,688],{"class":75,"line":182},[73,669,642],{"class":86},[73,671,672],{"class":93}," products    p ",[73,674,648],{"class":86},[73,676,677],{"class":89}," p",[73,679,58],{"class":93},[73,681,623],{"class":89},[73,683,658],{"class":86},[73,685,594],{"class":89},[73,687,58],{"class":93},[73,689,690],{"class":89},"product_id\n",[73,692,693,695,697,699,701,703,705,707,710,712,714],{"class":75,"line":187},[73,694,57],{"class":86},[73,696,221],{"class":93},[73,698,225],{"class":224},[73,700,25],{"class":93},[73,702,547],{"class":89},[73,704,58],{"class":93},[73,706,552],{"class":89},[73,708,709],{"class":93},"), ",[73,711,15],{"class":89},[73,713,58],{"class":93},[73,715,716],{"class":89},"category\n",[73,718,719,721,723,726,729,731],{"class":75,"line":193},[73,720,492],{"class":86},[73,722,559],{"class":86},[73,724,725],{"class":86}," DESC",[73,727,728],{"class":93},", revenue ",[73,730,498],{"class":86},[73,732,387],{"class":93},[10,734,736],{"id":735},"having-filtering-on-aggregate-values","HAVING — filtering on aggregate values",[15,738,739,741,742,744],{},[22,740,212],{}," runs before aggregation and cannot reference aggregate results. ",[22,743,53],{},"\nruns after aggregation and filters the groups.",[64,746,748],{"className":66,"code":747,"language":68,"meta":69,"style":69},"-- Customers who have placed more than 5 orders AND spent over £500\nSELECT\n    customer_id,\n    COUNT(*)          AS order_count,\n    SUM(total_amount) AS total_spent\nFROM orders\nGROUP BY customer_id\nHAVING COUNT(*) > 5\n   AND SUM(total_amount) > 500\nORDER BY total_spent DESC;\n\n-- Product categories with average price above £50\nSELECT category, AVG(unit_price) AS avg_price\nFROM products\nGROUP BY category\nHAVING AVG(unit_price) > 50;\n",[22,749,750,755,759,763,778,790,796,802,820,835,846,850,855,872,879,887],{"__ignoreMap":69},[73,751,752],{"class":75,"line":76},[73,753,754],{"class":79},"-- Customers who have placed more than 5 orders AND spent over £500\n",[73,756,757],{"class":75,"line":83},[73,758,288],{"class":86},[73,760,761],{"class":75,"line":115},[73,762,434],{"class":93},[73,764,765,767,769,771,774,776],{"class":75,"line":122},[73,766,342],{"class":89},[73,768,94],{"class":93},[73,770,97],{"class":86},[73,772,773],{"class":93},")          ",[73,775,103],{"class":86},[73,777,450],{"class":93},[73,779,780,782,785,787],{"class":75,"line":128},[73,781,293],{"class":89},[73,783,784],{"class":93},"(total_amount) ",[73,786,103],{"class":86},[73,788,789],{"class":93}," total_spent\n",[73,791,792,794],{"class":75,"line":147},[73,793,109],{"class":86},[73,795,361],{"class":93},[73,797,798,800],{"class":75,"line":152},[73,799,57],{"class":86},[73,801,487],{"class":93},[73,803,804,806,808,810,812,814,817],{"class":75,"line":158},[73,805,53],{"class":86},[73,807,90],{"class":89},[73,809,94],{"class":93},[73,811,97],{"class":86},[73,813,100],{"class":93},[73,815,816],{"class":86},">",[73,818,819],{"class":89}," 5\n",[73,821,822,825,828,830,832],{"class":75,"line":182},[73,823,824],{"class":86},"   AND",[73,826,827],{"class":89}," SUM",[73,829,784],{"class":93},[73,831,816],{"class":86},[73,833,834],{"class":89}," 500\n",[73,836,837,839,842,844],{"class":75,"line":187},[73,838,492],{"class":86},[73,840,841],{"class":93}," total_spent ",[73,843,498],{"class":86},[73,845,387],{"class":93},[73,847,848],{"class":75,"line":193},[73,849,119],{"emptyLinePlaceholder":118},[73,851,852],{"class":75,"line":236},[73,853,854],{"class":79},"-- Product categories with average price above £50\n",[73,856,857,859,862,864,867,869],{"class":75,"line":244},[73,858,49],{"class":86},[73,860,861],{"class":93}," category, ",[73,863,31],{"class":89},[73,865,866],{"class":93},"(unit_price) ",[73,868,103],{"class":86},[73,870,871],{"class":93}," avg_price\n",[73,873,874,876],{"class":75,"line":251},[73,875,109],{"class":86},[73,877,878],{"class":93}," products\n",[73,880,882,884],{"class":75,"line":881},15,[73,883,57],{"class":86},[73,885,886],{"class":93}," category\n",[73,888,890,892,895,897,899,902],{"class":75,"line":889},16,[73,891,53],{"class":86},[73,893,894],{"class":89}," AVG",[73,896,866],{"class":93},[73,898,816],{"class":86},[73,900,901],{"class":89}," 50",[73,903,387],{"class":93},[15,905,906,907,909,910,912],{},"Think of ",[22,908,212],{}," as \"which rows go into the calculation\" and ",[22,911,53],{}," as\n\"which calculation results do I keep\".",[10,914,916],{"id":915},"conditional-aggregation-pivoting-without-a-join","Conditional aggregation — pivoting without a join",[15,918,919,920,923],{},"You can combine ",[22,921,922],{},"CASE"," with an aggregate to compute multiple summaries in a\nsingle pass:",[64,925,927],{"className":66,"code":926,"language":68,"meta":69,"style":69},"-- Order counts broken down by status in one query\nSELECT\n    COUNT(*)                                              AS total,\n    COUNT(CASE WHEN status = 'pending'   THEN 1 END)     AS pending,\n    COUNT(CASE WHEN status = 'shipped'   THEN 1 END)     AS shipped,\n    COUNT(CASE WHEN status = 'delivered' THEN 1 END)     AS delivered,\n    SUM(CASE WHEN status = 'pending' THEN total_amount ELSE 0 END) AS pending_revenue\nFROM orders\nWHERE created_at >= '2026-01-01';\n",[22,928,929,934,938,954,990,1020,1051,1087,1093],{"__ignoreMap":69},[73,930,931],{"class":75,"line":76},[73,932,933],{"class":79},"-- Order counts broken down by status in one query\n",[73,935,936],{"class":75,"line":83},[73,937,288],{"class":86},[73,939,940,942,944,946,949,951],{"class":75,"line":115},[73,941,342],{"class":89},[73,943,94],{"class":93},[73,945,97],{"class":86},[73,947,948],{"class":93},")                                              ",[73,950,103],{"class":86},[73,952,953],{"class":93}," total,\n",[73,955,956,958,960,962,965,968,970,973,976,979,982,985,987],{"class":75,"line":122},[73,957,342],{"class":89},[73,959,94],{"class":93},[73,961,922],{"class":86},[73,963,964],{"class":86}," WHEN",[73,966,967],{"class":86}," status",[73,969,658],{"class":86},[73,971,972],{"class":224}," 'pending'",[73,974,975],{"class":86},"   THEN",[73,977,978],{"class":89}," 1",[73,980,981],{"class":86}," END",[73,983,984],{"class":93},")     ",[73,986,103],{"class":86},[73,988,989],{"class":93}," pending,\n",[73,991,992,994,996,998,1000,1002,1004,1007,1009,1011,1013,1015,1017],{"class":75,"line":128},[73,993,342],{"class":89},[73,995,94],{"class":93},[73,997,922],{"class":86},[73,999,964],{"class":86},[73,1001,967],{"class":86},[73,1003,658],{"class":86},[73,1005,1006],{"class":224}," 'shipped'",[73,1008,975],{"class":86},[73,1010,978],{"class":89},[73,1012,981],{"class":86},[73,1014,984],{"class":93},[73,1016,103],{"class":86},[73,1018,1019],{"class":93}," shipped,\n",[73,1021,1022,1024,1026,1028,1030,1032,1034,1037,1040,1042,1044,1046,1048],{"class":75,"line":147},[73,1023,342],{"class":89},[73,1025,94],{"class":93},[73,1027,922],{"class":86},[73,1029,964],{"class":86},[73,1031,967],{"class":86},[73,1033,658],{"class":86},[73,1035,1036],{"class":224}," 'delivered'",[73,1038,1039],{"class":86}," THEN",[73,1041,978],{"class":89},[73,1043,981],{"class":86},[73,1045,984],{"class":93},[73,1047,103],{"class":86},[73,1049,1050],{"class":93}," delivered,\n",[73,1052,1053,1055,1057,1059,1061,1063,1065,1067,1069,1072,1075,1078,1080,1082,1084],{"class":75,"line":152},[73,1054,293],{"class":89},[73,1056,94],{"class":93},[73,1058,922],{"class":86},[73,1060,964],{"class":86},[73,1062,967],{"class":86},[73,1064,658],{"class":86},[73,1066,972],{"class":224},[73,1068,1039],{"class":86},[73,1070,1071],{"class":93}," total_amount ",[73,1073,1074],{"class":86},"ELSE",[73,1076,1077],{"class":89}," 0",[73,1079,981],{"class":86},[73,1081,100],{"class":93},[73,1083,103],{"class":86},[73,1085,1086],{"class":93}," pending_revenue\n",[73,1088,1089,1091],{"class":75,"line":158},[73,1090,109],{"class":86},[73,1092,361],{"class":93},[73,1094,1095,1097,1099,1101,1104],{"class":75,"line":182},[73,1096,212],{"class":86},[73,1098,215],{"class":93},[73,1100,218],{"class":86},[73,1102,1103],{"class":224}," '2026-01-01'",[73,1105,387],{"class":93},[10,1107,1109],{"id":1108},"group-by-with-rollup","GROUP BY with ROLLUP",[15,1111,1112,1115],{},[22,1113,1114],{},"ROLLUP"," adds subtotal rows automatically — useful for reporting hierarchies:",[64,1117,1119],{"className":66,"code":1118,"language":68,"meta":69,"style":69},"-- Revenue by year and month, with yearly subtotals\nSELECT\n    EXTRACT(YEAR  FROM created_at) AS yr,\n    EXTRACT(MONTH FROM created_at) AS mo,\n    SUM(total_amount)              AS revenue\nFROM orders\nGROUP BY ROLLUP (\n    EXTRACT(YEAR FROM created_at),\n    EXTRACT(MONTH FROM created_at)\n)\nORDER BY yr, mo;\n-- Rows where mo IS NULL are year-level subtotals\n-- The row where both yr and mo are NULL is the grand total\n",[22,1120,1121,1126,1130,1149,1166,1178,1184,1194,1205,1216,1221,1228,1233],{"__ignoreMap":69},[73,1122,1123],{"class":75,"line":76},[73,1124,1125],{"class":79},"-- Revenue by year and month, with yearly subtotals\n",[73,1127,1128],{"class":75,"line":83},[73,1129,288],{"class":86},[73,1131,1132,1135,1138,1141,1144,1146],{"class":75,"line":115},[73,1133,1134],{"class":93},"    EXTRACT(",[73,1136,1137],{"class":86},"YEAR",[73,1139,1140],{"class":86},"  FROM",[73,1142,1143],{"class":93}," created_at) ",[73,1145,103],{"class":86},[73,1147,1148],{"class":93}," yr,\n",[73,1150,1151,1153,1156,1159,1161,1163],{"class":75,"line":122},[73,1152,1134],{"class":93},[73,1154,1155],{"class":86},"MONTH",[73,1157,1158],{"class":86}," FROM",[73,1160,1143],{"class":93},[73,1162,103],{"class":86},[73,1164,1165],{"class":93}," mo,\n",[73,1167,1168,1170,1173,1175],{"class":75,"line":128},[73,1169,293],{"class":89},[73,1171,1172],{"class":93},"(total_amount)              ",[73,1174,103],{"class":86},[73,1176,1177],{"class":93}," revenue\n",[73,1179,1180,1182],{"class":75,"line":147},[73,1181,109],{"class":86},[73,1183,361],{"class":93},[73,1185,1186,1188,1191],{"class":75,"line":152},[73,1187,57],{"class":86},[73,1189,1190],{"class":86}," ROLLUP",[73,1192,1193],{"class":93}," (\n",[73,1195,1196,1198,1200,1202],{"class":75,"line":158},[73,1197,1134],{"class":93},[73,1199,1137],{"class":86},[73,1201,1158],{"class":86},[73,1203,1204],{"class":93}," created_at),\n",[73,1206,1207,1209,1211,1213],{"class":75,"line":182},[73,1208,1134],{"class":93},[73,1210,1155],{"class":86},[73,1212,1158],{"class":86},[73,1214,1215],{"class":93}," created_at)\n",[73,1217,1218],{"class":75,"line":187},[73,1219,1220],{"class":93},")\n",[73,1222,1223,1225],{"class":75,"line":193},[73,1224,492],{"class":86},[73,1226,1227],{"class":93}," yr, mo;\n",[73,1229,1230],{"class":75,"line":236},[73,1231,1232],{"class":79},"-- Rows where mo IS NULL are year-level subtotals\n",[73,1234,1235],{"class":75,"line":244},[73,1236,1237],{"class":79},"-- The row where both yr and mo are NULL is the grand total\n",[10,1239,1241],{"id":1240},"recap","Recap",[15,1243,1244,1245,1247,1248,1250,1251,1253,1254,1256,1257,1259,1260,1262,1263,1265,1266,1268],{},"Aggregate functions collapse many rows into one value per group. ",[22,1246,57],{},"\ndefines the groups; every non-aggregated ",[22,1249,49],{}," column must be in ",[22,1252,57],{},".\nFilter raw rows with ",[22,1255,212],{}," before aggregation; filter groups with ",[22,1258,53],{},"\nafter. Use conditional aggregation (",[22,1261,922],{}," inside ",[22,1264,24],{},"\u002F",[22,1267,28],{},") to produce\nmulti-column summaries in a single pass instead of running multiple queries.",[1270,1271,1272],"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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":69,"searchDepth":83,"depth":83,"links":1274},[1275,1276,1277,1278,1279,1280,1281,1282,1283],{"id":12,"depth":83,"text":13},{"id":61,"depth":83,"text":62},{"id":272,"depth":83,"text":273},{"id":409,"depth":83,"text":410},{"id":520,"depth":83,"text":521},{"id":735,"depth":83,"text":736},{"id":915,"depth":83,"text":916},{"id":1108,"depth":83,"text":1109},{"id":1240,"depth":83,"text":1241},"SQL aggregation explained — COUNT, SUM, AVG, MIN, MAX, GROUP BY, HAVING, and conditional aggregation with real e-commerce examples.","medium","md","SQL",{},"\u002Fblog\u002Fsql-aggregation-group-by-having","\u002Fsql\u002Fbasics\u002Faggregation",{"title":5,"description":1284},"blog\u002Fsql-aggregation-group-by-having","Aggregation & GROUP BY","Query Basics","basics","2026-06-20","8vk7RHn76W2iG0E2btjhbCvxJuA2B2Rx7cPOO33ptwM",1782244089274]