[{"data":1,"prerenderedAt":3339},["ShallowReactive",2],{"page-\u002Fsql\u002F05-aggregation-and-group-by":3},{"id":4,"title":5,"body":6,"description":16,"extension":3333,"meta":3334,"navigation":519,"path":3335,"seo":3336,"stem":3337,"__hash__":3338},"content\u002Fsql\u002F05-aggregation-and-group-by.md","05 — Aggregation & GROUP BY",{"type":7,"value":8,"toc":3304},"minimark",[9,13,17,22,173,325,337,443,447,457,623,627,633,650,735,739,840,843,847,870,1006,1010,1073,1090,1094,1175,1181,1185,1192,1199,1324,1331,1479,1486,1535,1542,1588,1597,1601,1981,1985,2082,2086,2329,2336,2340,2382,2435,2450,2694,2733,2737,2927,2931,2934,3009,3270,3273,3300],[10,11,5],"h1",{"id":12},"_05-aggregation-group-by",[14,15,16],"p",{},"Aggregation collapses multiple rows into summary values — counts, sums, averages, minima, maxima. It's how you answer \"how many,\" \"how much,\" \"what's the average per group.\"",[18,19,21],"h2",{"id":20},"aggregate-functions","Aggregate Functions",[23,24,25,41],"table",{},[26,27,28],"thead",{},[29,30,31,35,38],"tr",{},[32,33,34],"th",{},"Function",[32,36,37],{},"Returns",[32,39,40],{},"NULL handling",[42,43,44,59,78,91,104,117,134,149,161],"tbody",{},[29,45,46,53,56],{},[47,48,49],"td",{},[50,51,52],"code",{},"COUNT(*)",[47,54,55],{},"Number of rows (including NULLs and duplicates).",[47,57,58],{},"Counts all rows.",[29,60,61,66,73],{},[47,62,63],{},[50,64,65],{},"COUNT(col)",[47,67,68,69,72],{},"Number of non-NULL values in ",[50,70,71],{},"col",".",[47,74,75,76,72],{},"Skips NULLs in ",[50,77,71],{},[29,79,80,85,88],{},[47,81,82],{},[50,83,84],{},"COUNT(DISTINCT col)",[47,86,87],{},"Number of distinct non-NULL values.",[47,89,90],{},"Skips NULLs.",[29,92,93,98,101],{},[47,94,95],{},[50,96,97],{},"SUM(col)",[47,99,100],{},"Sum of non-NULL values.",[47,102,103],{},"Skips NULLs; returns NULL on empty set.",[29,105,106,111,114],{},[47,107,108],{},[50,109,110],{},"AVG(col)",[47,112,113],{},"Average of non-NULL values.",[47,115,116],{},"Skips NULLs (does NOT treat as 0).",[29,118,119,128,131],{},[47,120,121,124,125],{},[50,122,123],{},"MIN(col)"," \u002F ",[50,126,127],{},"MAX(col)",[47,129,130],{},"Smallest\u002Flargest non-NULL value.",[47,132,133],{},"Works on text, dates, numbers.",[29,135,136,144,147],{},[47,137,138,124,141],{},[50,139,140],{},"bool_or(col)",[50,142,143],{},"bool_and(col)",[47,145,146],{},"TRUE if any\u002Fall values are TRUE (PostgreSQL).",[47,148,90],{},[29,150,151,156,159],{},[47,152,153],{},[50,154,155],{},"string_agg(col, ',')",[47,157,158],{},"Concatenate values (PostgreSQL).",[47,160,90],{},[29,162,163,168,171],{},[47,164,165],{},[50,166,167],{},"GROUP_CONCAT(col)",[47,169,170],{},"Concatenate (MySQL, SQLite).",[47,172,90],{},[174,175,177],"code-wrapper",{"language":176},"sql",[178,179,183],"pre",{"className":180,"code":181,"language":176,"meta":182,"style":182},"language-sql shiki shiki-themes github-light github-dark","SELECT\n  COUNT(*) AS total_orders,                    -- all rows, including any with NULL amounts\n  COUNT(DISTINCT customer_id) AS unique_customers,\n  SUM(amount) AS revenue,                      -- NULLs skipped; NULL if no rows\n  AVG(amount) AS avg_order,                    -- NULLs skipped — not treated as 0\n  MIN(amount) AS smallest,\n  MAX(amount) AS largest\nFROM orders;\n--  total_orders | unique_customers | revenue | avg_order | smallest | largest\n--  -------------+------------------+---------+----------+----------+---------\n--           4   |        3         |  568.75 |  142.19  |   7.25   |  450.00\n","",[50,184,185,194,221,239,256,272,285,298,307,313,319],{"__ignoreMap":182},[186,187,190],"span",{"class":188,"line":189},"line",1,[186,191,193],{"class":192},"svdQ7","SELECT\n",[186,195,197,201,205,208,211,214,217],{"class":188,"line":196},2,[186,198,200],{"class":199},"snvgF","  COUNT",[186,202,204],{"class":203},"ssxIu","(",[186,206,207],{"class":192},"*",[186,209,210],{"class":203},") ",[186,212,213],{"class":192},"AS",[186,215,216],{"class":203}," total_orders,                    ",[186,218,220],{"class":219},"sdCPZ","-- all rows, including any with NULL amounts\n",[186,222,224,226,228,231,234,236],{"class":188,"line":223},3,[186,225,200],{"class":199},[186,227,204],{"class":203},[186,229,230],{"class":192},"DISTINCT",[186,232,233],{"class":203}," customer_id) ",[186,235,213],{"class":192},[186,237,238],{"class":203}," unique_customers,\n",[186,240,242,245,248,250,253],{"class":188,"line":241},4,[186,243,244],{"class":199},"  SUM",[186,246,247],{"class":203},"(amount) ",[186,249,213],{"class":192},[186,251,252],{"class":203}," revenue,                      ",[186,254,255],{"class":219},"-- NULLs skipped; NULL if no rows\n",[186,257,259,262,264,266,269],{"class":188,"line":258},5,[186,260,261],{"class":199},"  AVG",[186,263,247],{"class":203},[186,265,213],{"class":192},[186,267,268],{"class":203}," avg_order,                    ",[186,270,271],{"class":219},"-- NULLs skipped — not treated as 0\n",[186,273,275,278,280,282],{"class":188,"line":274},6,[186,276,277],{"class":199},"  MIN",[186,279,247],{"class":203},[186,281,213],{"class":192},[186,283,284],{"class":203}," smallest,\n",[186,286,288,291,293,295],{"class":188,"line":287},7,[186,289,290],{"class":199},"  MAX",[186,292,247],{"class":203},[186,294,213],{"class":192},[186,296,297],{"class":203}," largest\n",[186,299,301,304],{"class":188,"line":300},8,[186,302,303],{"class":192},"FROM",[186,305,306],{"class":203}," orders;\n",[186,308,310],{"class":188,"line":309},9,[186,311,312],{"class":219},"--  total_orders | unique_customers | revenue | avg_order | smallest | largest\n",[186,314,316],{"class":188,"line":315},10,[186,317,318],{"class":219},"--  -------------+------------------+---------+----------+----------+---------\n",[186,320,322],{"class":188,"line":321},11,[186,323,324],{"class":219},"--           4   |        3         |  568.75 |  142.19  |   7.25   |  450.00\n",[326,327,329,331,332,331,334],"h3",{"id":328},"count-vs-countcol-vs-count1",[50,330,52],{}," vs ",[50,333,65],{},[50,335,336],{},"COUNT(1)",[174,338,339],{"language":176},[178,340,342],{"className":180,"code":341,"language":176,"meta":182,"style":182},"-- COUNT(*): counts ALL rows, including those where every column is NULL\n-- COUNT(col): counts rows where col IS NOT NULL — skips NULLs in that column\n-- COUNT(1): the constant 1 is never NULL, so it behaves like COUNT(*)\n--   (The myth that COUNT(1) is faster is false — the planner optimizes both identically.)\nSELECT\n  COUNT(*) AS total,           -- 3 (all customers)\n  COUNT(city) AS with_city,    -- 2 (Carol has city = NULL, skipped)\n  COUNT(1) AS count_one        -- 3 (same as COUNT(*))\nFROM customers;\n--  total | with_city | count_one\n--  ------+-----------+----------\n--     3  |     2     |    3\n",[50,343,344,349,354,359,364,368,386,401,420,427,432,437],{"__ignoreMap":182},[186,345,346],{"class":188,"line":189},[186,347,348],{"class":219},"-- COUNT(*): counts ALL rows, including those where every column is NULL\n",[186,350,351],{"class":188,"line":196},[186,352,353],{"class":219},"-- COUNT(col): counts rows where col IS NOT NULL — skips NULLs in that column\n",[186,355,356],{"class":188,"line":223},[186,357,358],{"class":219},"-- COUNT(1): the constant 1 is never NULL, so it behaves like COUNT(*)\n",[186,360,361],{"class":188,"line":241},[186,362,363],{"class":219},"--   (The myth that COUNT(1) is faster is false — the planner optimizes both identically.)\n",[186,365,366],{"class":188,"line":258},[186,367,193],{"class":192},[186,369,370,372,374,376,378,380,383],{"class":188,"line":274},[186,371,200],{"class":199},[186,373,204],{"class":203},[186,375,207],{"class":192},[186,377,210],{"class":203},[186,379,213],{"class":192},[186,381,382],{"class":203}," total,           ",[186,384,385],{"class":219},"-- 3 (all customers)\n",[186,387,388,390,393,395,398],{"class":188,"line":287},[186,389,200],{"class":199},[186,391,392],{"class":203},"(city) ",[186,394,213],{"class":192},[186,396,397],{"class":203}," with_city,    ",[186,399,400],{"class":219},"-- 2 (Carol has city = NULL, skipped)\n",[186,402,403,405,407,410,412,414,417],{"class":188,"line":300},[186,404,200],{"class":199},[186,406,204],{"class":203},[186,408,409],{"class":199},"1",[186,411,210],{"class":203},[186,413,213],{"class":192},[186,415,416],{"class":203}," count_one        ",[186,418,419],{"class":219},"-- 3 (same as COUNT(*))\n",[186,421,422,424],{"class":188,"line":309},[186,423,303],{"class":192},[186,425,426],{"class":203}," customers;\n",[186,428,429],{"class":188,"line":315},[186,430,431],{"class":219},"--  total | with_city | count_one\n",[186,433,434],{"class":188,"line":321},[186,435,436],{"class":219},"--  ------+-----------+----------\n",[186,438,440],{"class":188,"line":439},12,[186,441,442],{"class":219},"--     3  |     2     |    3\n",[18,444,446],{"id":445},"group-by","GROUP BY",[14,448,449,451,452,456],{},[50,450,446],{}," splits rows into groups, then applies aggregates ",[453,454,455],"strong",{},"per group",". Each group becomes one row in the output.",[174,458,459],{"language":176},[178,460,462],{"className":180,"code":461,"language":176,"meta":182,"style":182},"-- Total spend per customer — one output row per customer_id group\nSELECT customer_id, SUM(amount) AS total_spent\nFROM orders\nGROUP BY customer_id\nORDER BY total_spent DESC;\n\n-- Order count and average per city — groups are defined by c.city\nSELECT c.city, COUNT(*) AS order_count, AVG(o.amount) AS avg_amount\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nGROUP BY c.city;\n",[50,463,464,469,487,494,501,515,521,526,575,582,611],{"__ignoreMap":182},[186,465,466],{"class":188,"line":189},[186,467,468],{"class":219},"-- Total spend per customer — one output row per customer_id group\n",[186,470,471,474,477,480,482,484],{"class":188,"line":196},[186,472,473],{"class":192},"SELECT",[186,475,476],{"class":203}," customer_id, ",[186,478,479],{"class":199},"SUM",[186,481,247],{"class":203},[186,483,213],{"class":192},[186,485,486],{"class":203}," total_spent\n",[186,488,489,491],{"class":188,"line":223},[186,490,303],{"class":192},[186,492,493],{"class":203}," orders\n",[186,495,496,498],{"class":188,"line":241},[186,497,446],{"class":192},[186,499,500],{"class":203}," customer_id\n",[186,502,503,506,509,512],{"class":188,"line":258},[186,504,505],{"class":192},"ORDER BY",[186,507,508],{"class":203}," total_spent ",[186,510,511],{"class":192},"DESC",[186,513,514],{"class":203},";\n",[186,516,517],{"class":188,"line":274},[186,518,520],{"emptyLinePlaceholder":519},true,"\n",[186,522,523],{"class":188,"line":287},[186,524,525],{"class":219},"-- Order count and average per city — groups are defined by c.city\n",[186,527,528,530,533,535,538,541,544,546,548,550,552,555,558,560,563,565,568,570,572],{"class":188,"line":300},[186,529,473],{"class":192},[186,531,532],{"class":199}," c",[186,534,72],{"class":203},[186,536,537],{"class":199},"city",[186,539,540],{"class":203},", ",[186,542,543],{"class":199},"COUNT",[186,545,204],{"class":203},[186,547,207],{"class":192},[186,549,210],{"class":203},[186,551,213],{"class":192},[186,553,554],{"class":203}," order_count, ",[186,556,557],{"class":199},"AVG",[186,559,204],{"class":203},[186,561,562],{"class":199},"o",[186,564,72],{"class":203},[186,566,567],{"class":199},"amount",[186,569,210],{"class":203},[186,571,213],{"class":192},[186,573,574],{"class":203}," avg_amount\n",[186,576,577,579],{"class":188,"line":309},[186,578,303],{"class":192},[186,580,581],{"class":203}," customers c\n",[186,583,584,587,590,593,595,597,600,603,606,608],{"class":188,"line":315},[186,585,586],{"class":192},"JOIN",[186,588,589],{"class":203}," orders o ",[186,591,592],{"class":192},"ON",[186,594,532],{"class":199},[186,596,72],{"class":203},[186,598,599],{"class":199},"id",[186,601,602],{"class":192}," =",[186,604,605],{"class":199}," o",[186,607,72],{"class":203},[186,609,610],{"class":199},"customer_id\n",[186,612,613,615,617,619,621],{"class":188,"line":321},[186,614,446],{"class":192},[186,616,532],{"class":199},[186,618,72],{"class":203},[186,620,537],{"class":199},[186,622,514],{"class":203},[326,624,626],{"id":625},"the-golden-rule-of-group-by","The Golden Rule of GROUP BY",[14,628,629,630,632],{},"Every column in the ",[50,631,473],{}," list must be either:",[634,635,636,645],"ol",{},[637,638,639,644],"li",{},[453,640,641,642],{},"Listed in ",[50,643,446],{},", or",[637,646,647,72],{},[453,648,649],{},"Wrapped in an aggregate function",[174,651,652],{"language":176},[178,653,655],{"className":180,"code":654,"language":176,"meta":182,"style":182},"-- ❌ ERROR: column \"o.amount\" must appear in GROUP BY or be used in an aggregate\n-- When rows collapse into groups, which amount should appear for a 5-row group? Undefined.\nSELECT customer_id, amount FROM orders GROUP BY customer_id;\n\n-- ✅ amount is aggregated — one value per group (the sum)\nSELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id;\n\n-- ✅ amount is in GROUP BY — gives per-(customer, amount) groups (usually not intended)\nSELECT customer_id, amount FROM orders GROUP BY customer_id, amount;\n",[50,656,657,662,667,684,688,693,711,715,720],{"__ignoreMap":182},[186,658,659],{"class":188,"line":189},[186,660,661],{"class":219},"-- ❌ ERROR: column \"o.amount\" must appear in GROUP BY or be used in an aggregate\n",[186,663,664],{"class":188,"line":196},[186,665,666],{"class":219},"-- When rows collapse into groups, which amount should appear for a 5-row group? Undefined.\n",[186,668,669,671,674,676,679,681],{"class":188,"line":223},[186,670,473],{"class":192},[186,672,673],{"class":203}," customer_id, amount ",[186,675,303],{"class":192},[186,677,678],{"class":203}," orders ",[186,680,446],{"class":192},[186,682,683],{"class":203}," customer_id;\n",[186,685,686],{"class":188,"line":241},[186,687,520],{"emptyLinePlaceholder":519},[186,689,690],{"class":188,"line":258},[186,691,692],{"class":219},"-- ✅ amount is aggregated — one value per group (the sum)\n",[186,694,695,697,699,701,703,705,707,709],{"class":188,"line":274},[186,696,473],{"class":192},[186,698,476],{"class":203},[186,700,479],{"class":199},[186,702,247],{"class":203},[186,704,303],{"class":192},[186,706,678],{"class":203},[186,708,446],{"class":192},[186,710,683],{"class":203},[186,712,713],{"class":188,"line":287},[186,714,520],{"emptyLinePlaceholder":519},[186,716,717],{"class":188,"line":300},[186,718,719],{"class":219},"-- ✅ amount is in GROUP BY — gives per-(customer, amount) groups (usually not intended)\n",[186,721,722,724,726,728,730,732],{"class":188,"line":309},[186,723,473],{"class":192},[186,725,673],{"class":203},[186,727,303],{"class":192},[186,729,678],{"class":203},[186,731,446],{"class":192},[186,733,734],{"class":203}," customer_id, amount;\n",[326,736,738],{"id":737},"functional-dependency-exception-postgresql","Functional Dependency Exception (PostgreSQL)",[174,740,741],{"language":176},[178,742,744],{"className":180,"code":743,"language":176,"meta":182,"style":182},"-- OK in PostgreSQL: c.id is the PK of customers, so c.name is functionally dependent on c.id\n-- Standard SQL feature T301 — but not universally supported (MySQL's ONLY_FULL_GROUP_BY enforces it)\nSELECT c.id, c.name, COUNT(o.id) AS order_count\nFROM customers c\nLEFT JOIN orders o ON c.id = o.customer_id\nGROUP BY c.id;   -- c.name not listed, but allowed because c.id is the PK → c.name is determined\n",[50,745,746,751,756,795,801,824],{"__ignoreMap":182},[186,747,748],{"class":188,"line":189},[186,749,750],{"class":219},"-- OK in PostgreSQL: c.id is the PK of customers, so c.name is functionally dependent on c.id\n",[186,752,753],{"class":188,"line":196},[186,754,755],{"class":219},"-- Standard SQL feature T301 — but not universally supported (MySQL's ONLY_FULL_GROUP_BY enforces it)\n",[186,757,758,760,762,764,766,768,771,773,776,778,780,782,784,786,788,790,792],{"class":188,"line":223},[186,759,473],{"class":192},[186,761,532],{"class":199},[186,763,72],{"class":203},[186,765,599],{"class":199},[186,767,540],{"class":203},[186,769,770],{"class":199},"c",[186,772,72],{"class":203},[186,774,775],{"class":199},"name",[186,777,540],{"class":203},[186,779,543],{"class":199},[186,781,204],{"class":203},[186,783,562],{"class":199},[186,785,72],{"class":203},[186,787,599],{"class":199},[186,789,210],{"class":203},[186,791,213],{"class":192},[186,793,794],{"class":203}," order_count\n",[186,796,797,799],{"class":188,"line":241},[186,798,303],{"class":192},[186,800,581],{"class":203},[186,802,803,806,808,810,812,814,816,818,820,822],{"class":188,"line":258},[186,804,805],{"class":192},"LEFT JOIN",[186,807,589],{"class":203},[186,809,592],{"class":192},[186,811,532],{"class":199},[186,813,72],{"class":203},[186,815,599],{"class":199},[186,817,602],{"class":192},[186,819,605],{"class":199},[186,821,72],{"class":203},[186,823,610],{"class":199},[186,825,826,828,830,832,834,837],{"class":188,"line":274},[186,827,446],{"class":192},[186,829,532],{"class":199},[186,831,72],{"class":203},[186,833,599],{"class":199},[186,835,836],{"class":203},";   ",[186,838,839],{"class":219},"-- c.name not listed, but allowed because c.id is the PK → c.name is determined\n",[14,841,842],{},"Don't rely on the exception for portability; group by all non-aggregated columns.",[18,844,846],{"id":845},"having-filtering-groups","HAVING — Filtering Groups",[14,848,849,852,853,856,857,852,860,863,864,866,867,869],{},[50,850,851],{},"WHERE"," filters ",[453,854,855],{},"input rows"," (before grouping). ",[50,858,859],{},"HAVING",[453,861,862],{},"output groups"," (after grouping). Conditions in ",[50,865,859],{}," can reference aggregates; conditions in ",[50,868,851],{}," cannot.",[174,871,872],{"language":176},[178,873,875],{"className":180,"code":874,"language":176,"meta":182,"style":182},"-- Customers who spent more than $100 total — HAVING filters on the aggregate\nSELECT customer_id, SUM(amount) AS total_spent\nFROM orders\nGROUP BY customer_id\nHAVING SUM(amount) > 100;\n\n-- Customers with at least 3 orders, placed after 2024-01-01\nSELECT customer_id, COUNT(*) AS n\nFROM orders\nWHERE ordered_on >= '2024-01-01'   -- row filter (before grouping — shrinks input)\nGROUP BY customer_id\nHAVING COUNT(*) >= 3;              -- group filter (after grouping — filters output groups)\n",[50,876,877,882,896,902,908,925,929,934,953,959,976,982],{"__ignoreMap":182},[186,878,879],{"class":188,"line":189},[186,880,881],{"class":219},"-- Customers who spent more than $100 total — HAVING filters on the aggregate\n",[186,883,884,886,888,890,892,894],{"class":188,"line":196},[186,885,473],{"class":192},[186,887,476],{"class":203},[186,889,479],{"class":199},[186,891,247],{"class":203},[186,893,213],{"class":192},[186,895,486],{"class":203},[186,897,898,900],{"class":188,"line":223},[186,899,303],{"class":192},[186,901,493],{"class":203},[186,903,904,906],{"class":188,"line":241},[186,905,446],{"class":192},[186,907,500],{"class":203},[186,909,910,912,915,917,920,923],{"class":188,"line":258},[186,911,859],{"class":192},[186,913,914],{"class":199}," SUM",[186,916,247],{"class":203},[186,918,919],{"class":192},">",[186,921,922],{"class":199}," 100",[186,924,514],{"class":203},[186,926,927],{"class":188,"line":274},[186,928,520],{"emptyLinePlaceholder":519},[186,930,931],{"class":188,"line":287},[186,932,933],{"class":219},"-- Customers with at least 3 orders, placed after 2024-01-01\n",[186,935,936,938,940,942,944,946,948,950],{"class":188,"line":300},[186,937,473],{"class":192},[186,939,476],{"class":203},[186,941,543],{"class":199},[186,943,204],{"class":203},[186,945,207],{"class":192},[186,947,210],{"class":203},[186,949,213],{"class":192},[186,951,952],{"class":203}," n\n",[186,954,955,957],{"class":188,"line":309},[186,956,303],{"class":192},[186,958,493],{"class":203},[186,960,961,963,966,969,973],{"class":188,"line":315},[186,962,851],{"class":192},[186,964,965],{"class":203}," ordered_on ",[186,967,968],{"class":192},">=",[186,970,972],{"class":971},"sJ6F3"," '2024-01-01'",[186,974,975],{"class":219},"   -- row filter (before grouping — shrinks input)\n",[186,977,978,980],{"class":188,"line":321},[186,979,446],{"class":192},[186,981,500],{"class":203},[186,983,984,986,989,991,993,995,997,1000,1003],{"class":188,"line":439},[186,985,859],{"class":192},[186,987,988],{"class":199}," COUNT",[186,990,204],{"class":203},[186,992,207],{"class":192},[186,994,210],{"class":203},[186,996,968],{"class":192},[186,998,999],{"class":199}," 3",[186,1001,1002],{"class":203},";              ",[186,1004,1005],{"class":219},"-- group filter (after grouping — filters output groups)\n",[326,1007,1009],{"id":1008},"where-vs-having-when-to-use-which","WHERE vs HAVING — when to use which",[23,1011,1012,1025],{},[26,1013,1014],{},[29,1015,1016,1019,1022],{},[32,1017,1018],{},"Filter on",[32,1020,1021],{},"Clause",[32,1023,1024],{},"Why",[42,1026,1027,1043,1058],{},[29,1028,1029,1036,1040],{},[47,1030,1031,1032,1035],{},"A raw column value (",[50,1033,1034],{},"amount > 100",")",[47,1037,1038],{},[50,1039,851],{},[47,1041,1042],{},"Eliminates rows before grouping → less work.",[29,1044,1045,1051,1055],{},[47,1046,1047,1048,1035],{},"An aggregate (",[50,1049,1050],{},"SUM(amount) > 100",[47,1052,1053],{},[50,1054,859],{},[47,1056,1057],{},"Aggregate isn't computed until after grouping.",[29,1059,1060,1063,1070],{},[47,1061,1062],{},"Both",[47,1064,1065,1067,1068],{},[50,1066,851],{}," + ",[50,1069,859],{},[47,1071,1072],{},"Push raw filters to WHERE, aggregate filters to HAVING.",[14,1074,1075,1076,1078,1079,1081,1082,1086,1087,1089],{},"Putting a raw-column filter in ",[50,1077,859],{}," works but is slower — ",[50,1080,851],{}," eliminates rows ",[1083,1084,1085],"em",{},"before"," grouping, shrinking the work; ",[50,1088,859],{}," groups first, then filters. Push filters as early as possible.",[18,1091,1093],{"id":1092},"group-by-multiple-columns","GROUP BY Multiple Columns",[174,1095,1096],{"language":176},[178,1097,1099],{"className":180,"code":1098,"language":176,"meta":182,"style":182},"-- Orders per customer per year — group for each unique (customer_id, yr) combination\nSELECT\n  customer_id,\n  EXTRACT(YEAR FROM ordered_on) AS yr,\n  COUNT(*) AS n\nFROM orders\nGROUP BY customer_id, EXTRACT(YEAR FROM ordered_on)\nORDER BY customer_id, yr;\n",[50,1100,1101,1106,1110,1115,1134,1148,1154,1168],{"__ignoreMap":182},[186,1102,1103],{"class":188,"line":189},[186,1104,1105],{"class":219},"-- Orders per customer per year — group for each unique (customer_id, yr) combination\n",[186,1107,1108],{"class":188,"line":196},[186,1109,193],{"class":192},[186,1111,1112],{"class":188,"line":223},[186,1113,1114],{"class":203},"  customer_id,\n",[186,1116,1117,1120,1123,1126,1129,1131],{"class":188,"line":241},[186,1118,1119],{"class":203},"  EXTRACT(",[186,1121,1122],{"class":192},"YEAR",[186,1124,1125],{"class":192}," FROM",[186,1127,1128],{"class":203}," ordered_on) ",[186,1130,213],{"class":192},[186,1132,1133],{"class":203}," yr,\n",[186,1135,1136,1138,1140,1142,1144,1146],{"class":188,"line":258},[186,1137,200],{"class":199},[186,1139,204],{"class":203},[186,1141,207],{"class":192},[186,1143,210],{"class":203},[186,1145,213],{"class":192},[186,1147,952],{"class":203},[186,1149,1150,1152],{"class":188,"line":274},[186,1151,303],{"class":192},[186,1153,493],{"class":203},[186,1155,1156,1158,1161,1163,1165],{"class":188,"line":287},[186,1157,446],{"class":192},[186,1159,1160],{"class":203}," customer_id, EXTRACT(",[186,1162,1122],{"class":192},[186,1164,1125],{"class":192},[186,1166,1167],{"class":203}," ordered_on)\n",[186,1169,1170,1172],{"class":188,"line":300},[186,1171,505],{"class":192},[186,1173,1174],{"class":203}," customer_id, yr;\n",[14,1176,1177,1178,1180],{},"The order of columns in ",[50,1179,446],{}," doesn't affect the result (groups are unordered sets) — but it can affect the planner's choice of sort vs hash aggregation.",[18,1182,1184],{"id":1183},"rollup-cube-grouping-sets","ROLLUP \u002F CUBE \u002F GROUPING SETS",[14,1186,1187,1188,1191],{},"These produce ",[453,1189,1190],{},"multiple levels of aggregation"," in one query — subtotals and grand totals.",[326,1193,1195,1198],{"id":1194},"grouping-sets-specific-combinations",[50,1196,1197],{},"GROUPING SETS"," — specific combinations",[174,1200,1201],{"language":176},[178,1202,1204],{"className":180,"code":1203,"language":176,"meta":182,"style":182},"-- Sales by (city, year), plus subtotals by city, by year, and a grand total — all in one query\nSELECT\n  city,\n  EXTRACT(YEAR FROM ordered_on) AS yr,\n  SUM(amount) AS total\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nGROUP BY GROUPING SETS (\n  (city, yr),   -- each city-year combo\n  (city),       -- subtotal per city (across all years) — yr column is NULL\n  (yr),         -- subtotal per year (across all cities) — city column is NULL\n  ()            -- grand total — both city and yr are NULL\n);\n",[50,1205,1206,1211,1215,1220,1234,1245,1251,1273,1286,1294,1302,1310,1318],{"__ignoreMap":182},[186,1207,1208],{"class":188,"line":189},[186,1209,1210],{"class":219},"-- Sales by (city, year), plus subtotals by city, by year, and a grand total — all in one query\n",[186,1212,1213],{"class":188,"line":196},[186,1214,193],{"class":192},[186,1216,1217],{"class":188,"line":223},[186,1218,1219],{"class":203},"  city,\n",[186,1221,1222,1224,1226,1228,1230,1232],{"class":188,"line":241},[186,1223,1119],{"class":203},[186,1225,1122],{"class":192},[186,1227,1125],{"class":192},[186,1229,1128],{"class":203},[186,1231,213],{"class":192},[186,1233,1133],{"class":203},[186,1235,1236,1238,1240,1242],{"class":188,"line":258},[186,1237,244],{"class":199},[186,1239,247],{"class":203},[186,1241,213],{"class":192},[186,1243,1244],{"class":203}," total\n",[186,1246,1247,1249],{"class":188,"line":274},[186,1248,303],{"class":192},[186,1250,581],{"class":203},[186,1252,1253,1255,1257,1259,1261,1263,1265,1267,1269,1271],{"class":188,"line":287},[186,1254,586],{"class":192},[186,1256,589],{"class":203},[186,1258,592],{"class":192},[186,1260,532],{"class":199},[186,1262,72],{"class":203},[186,1264,599],{"class":199},[186,1266,602],{"class":192},[186,1268,605],{"class":199},[186,1270,72],{"class":203},[186,1272,610],{"class":199},[186,1274,1275,1277,1280,1283],{"class":188,"line":300},[186,1276,446],{"class":192},[186,1278,1279],{"class":203}," GROUPING ",[186,1281,1282],{"class":192},"SETS",[186,1284,1285],{"class":203}," (\n",[186,1287,1288,1291],{"class":188,"line":309},[186,1289,1290],{"class":203},"  (city, yr),   ",[186,1292,1293],{"class":219},"-- each city-year combo\n",[186,1295,1296,1299],{"class":188,"line":315},[186,1297,1298],{"class":203},"  (city),       ",[186,1300,1301],{"class":219},"-- subtotal per city (across all years) — yr column is NULL\n",[186,1303,1304,1307],{"class":188,"line":321},[186,1305,1306],{"class":203},"  (yr),         ",[186,1308,1309],{"class":219},"-- subtotal per year (across all cities) — city column is NULL\n",[186,1311,1312,1315],{"class":188,"line":439},[186,1313,1314],{"class":203},"  ()            ",[186,1316,1317],{"class":219},"-- grand total — both city and yr are NULL\n",[186,1319,1321],{"class":188,"line":1320},13,[186,1322,1323],{"class":203},");\n",[14,1325,1326,1327,1330],{},"NULL appears in the columns not being grouped for each subtotal row. Distinguish \"real NULL\" from \"subtotal marker\" with ",[50,1328,1329],{},"GROUPING(col)",":",[174,1332,1333],{"language":176},[178,1334,1336],{"className":180,"code":1335,"language":176,"meta":182,"style":182},"-- GROUPING(col) returns 1 when col is a subtotal (NULL due to grouping), 0 otherwise\nSELECT\n  CASE WHEN GROUPING(city) = 1 THEN 'ALL CITIES' ELSE city END AS city,\n  CASE WHEN GROUPING(yr)   = 1 THEN 'ALL YEARS'  ELSE yr::text END AS yr,\n  SUM(amount) AS total\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nGROUP BY GROUPING SETS ((city, yr), (city), (yr), ())\nORDER BY city, yr;\n",[50,1337,1338,1343,1347,1387,1423,1433,1439,1461,1472],{"__ignoreMap":182},[186,1339,1340],{"class":188,"line":189},[186,1341,1342],{"class":219},"-- GROUPING(col) returns 1 when col is a subtotal (NULL due to grouping), 0 otherwise\n",[186,1344,1345],{"class":188,"line":196},[186,1346,193],{"class":192},[186,1348,1349,1352,1355,1358,1360,1363,1366,1369,1372,1375,1378,1381,1384],{"class":188,"line":223},[186,1350,1351],{"class":192},"  CASE",[186,1353,1354],{"class":192}," WHEN",[186,1356,1357],{"class":199}," GROUPING",[186,1359,392],{"class":203},[186,1361,1362],{"class":192},"=",[186,1364,1365],{"class":199}," 1",[186,1367,1368],{"class":192}," THEN",[186,1370,1371],{"class":971}," 'ALL CITIES'",[186,1373,1374],{"class":192}," ELSE",[186,1376,1377],{"class":203}," city ",[186,1379,1380],{"class":192},"END",[186,1382,1383],{"class":192}," AS",[186,1385,1386],{"class":203}," city,\n",[186,1388,1389,1391,1393,1395,1398,1400,1402,1404,1407,1410,1413,1416,1419,1421],{"class":188,"line":241},[186,1390,1351],{"class":192},[186,1392,1354],{"class":192},[186,1394,1357],{"class":199},[186,1396,1397],{"class":203},"(yr)   ",[186,1399,1362],{"class":192},[186,1401,1365],{"class":199},[186,1403,1368],{"class":192},[186,1405,1406],{"class":971}," 'ALL YEARS'",[186,1408,1409],{"class":192},"  ELSE",[186,1411,1412],{"class":203}," yr::",[186,1414,1415],{"class":192},"text",[186,1417,1418],{"class":192}," END",[186,1420,1383],{"class":192},[186,1422,1133],{"class":203},[186,1424,1425,1427,1429,1431],{"class":188,"line":258},[186,1426,244],{"class":199},[186,1428,247],{"class":203},[186,1430,213],{"class":192},[186,1432,1244],{"class":203},[186,1434,1435,1437],{"class":188,"line":274},[186,1436,303],{"class":192},[186,1438,581],{"class":203},[186,1440,1441,1443,1445,1447,1449,1451,1453,1455,1457,1459],{"class":188,"line":287},[186,1442,586],{"class":192},[186,1444,589],{"class":203},[186,1446,592],{"class":192},[186,1448,532],{"class":199},[186,1450,72],{"class":203},[186,1452,599],{"class":199},[186,1454,602],{"class":192},[186,1456,605],{"class":199},[186,1458,72],{"class":203},[186,1460,610],{"class":199},[186,1462,1463,1465,1467,1469],{"class":188,"line":300},[186,1464,446],{"class":192},[186,1466,1279],{"class":203},[186,1468,1282],{"class":192},[186,1470,1471],{"class":203}," ((city, yr), (city), (yr), ())\n",[186,1473,1474,1476],{"class":188,"line":309},[186,1475,505],{"class":192},[186,1477,1478],{"class":203}," city, yr;\n",[326,1480,1482,1485],{"id":1481},"rollup-hierarchical-subtotals",[50,1483,1484],{},"ROLLUP"," — hierarchical subtotals",[174,1487,1488],{"language":176},[178,1489,1491],{"className":180,"code":1490,"language":176,"meta":182,"style":182},"-- ROLLUP (city, yr) = GROUPING SETS ((city, yr), (city), ())\n-- Produces subtotals at each level of the hierarchy: city+yr → city → grand total\nSELECT city, yr, SUM(amount) AS total\nFROM sales\nGROUP BY ROLLUP (city, yr);\n",[50,1492,1493,1498,1503,1518,1525],{"__ignoreMap":182},[186,1494,1495],{"class":188,"line":189},[186,1496,1497],{"class":219},"-- ROLLUP (city, yr) = GROUPING SETS ((city, yr), (city), ())\n",[186,1499,1500],{"class":188,"line":196},[186,1501,1502],{"class":219},"-- Produces subtotals at each level of the hierarchy: city+yr → city → grand total\n",[186,1504,1505,1507,1510,1512,1514,1516],{"class":188,"line":223},[186,1506,473],{"class":192},[186,1508,1509],{"class":203}," city, yr, ",[186,1511,479],{"class":199},[186,1513,247],{"class":203},[186,1515,213],{"class":192},[186,1517,1244],{"class":203},[186,1519,1520,1522],{"class":188,"line":241},[186,1521,303],{"class":192},[186,1523,1524],{"class":203}," sales\n",[186,1526,1527,1529,1532],{"class":188,"line":258},[186,1528,446],{"class":192},[186,1530,1531],{"class":192}," ROLLUP",[186,1533,1534],{"class":203}," (city, yr);\n",[326,1536,1538,1541],{"id":1537},"cube-all-combinations",[50,1539,1540],{},"CUBE"," — all combinations",[174,1543,1544],{"language":176},[178,1545,1547],{"className":180,"code":1546,"language":176,"meta":182,"style":182},"-- CUBE (city, yr) = GROUPING SETS ((city, yr), (city), (yr), ())\n-- Produces every combination of subtotals — a full cross-tab\nSELECT city, yr, SUM(amount) AS total\nFROM sales\nGROUP BY CUBE (city, yr);\n",[50,1548,1549,1554,1559,1573,1579],{"__ignoreMap":182},[186,1550,1551],{"class":188,"line":189},[186,1552,1553],{"class":219},"-- CUBE (city, yr) = GROUPING SETS ((city, yr), (city), (yr), ())\n",[186,1555,1556],{"class":188,"line":196},[186,1557,1558],{"class":219},"-- Produces every combination of subtotals — a full cross-tab\n",[186,1560,1561,1563,1565,1567,1569,1571],{"class":188,"line":223},[186,1562,473],{"class":192},[186,1564,1509],{"class":203},[186,1566,479],{"class":199},[186,1568,247],{"class":203},[186,1570,213],{"class":192},[186,1572,1244],{"class":203},[186,1574,1575,1577],{"class":188,"line":241},[186,1576,303],{"class":192},[186,1578,1524],{"class":203},[186,1580,1581,1583,1586],{"class":188,"line":258},[186,1582,446],{"class":192},[186,1584,1585],{"class":192}," CUBE",[186,1587,1534],{"class":203},[14,1589,1590,1591,1593,1594,1596],{},"PostgreSQL supports all three. MySQL supports ",[50,1592,1484],{}," (with a slightly different syntax). SQLite supports ",[50,1595,1197],{}," in 3.44+.",[18,1598,1600],{"id":1599},"complex-implementation-multi-level-sales-report","Complex Implementation: Multi-Level Sales Report",[174,1602,1603],{"language":176},[178,1604,1606],{"className":180,"code":1605,"language":176,"meta":182,"style":182},"-- A multi-level sales report with subtotals by city, by year, and a grand total.\n-- Uses GROUPING SETS to produce all levels in a single pass over the data.\nSELECT\n  CASE WHEN GROUPING(c.city) = 1 THEN '=== ALL CITIES ===' ELSE c.city END AS city,\n  CASE WHEN GROUPING(yr)     = 1 THEN '=== ALL YEARS ==='  ELSE yr::text   END AS yr,\n  COUNT(*) AS order_count,\n  SUM(o.amount) AS revenue,\n  AVG(o.amount) AS avg_order\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nCROSS JOIN LATERAL (SELECT EXTRACT(YEAR FROM o.ordered_on) AS yr) x\nGROUP BY GROUPING SETS (\n  (c.city, yr),    -- per city per year\n  (c.city),        -- per city (all years)\n  (yr),            -- per year (all cities)\n  ()               -- grand total\n)\nORDER BY\n  CASE WHEN GROUPING(c.city) = 1 THEN 1 ELSE 0 END,   -- city subtotals after detail rows\n  c.city NULLS LAST,\n  CASE WHEN GROUPING(yr)     = 1 THEN 1 ELSE 0 END,\n  yr NULLS LAST;\n",[50,1607,1608,1613,1618,1622,1663,1696,1711,1730,1749,1755,1777,1808,1818,1835,1852,1861,1870,1876,1882,1922,1941,1968],{"__ignoreMap":182},[186,1609,1610],{"class":188,"line":189},[186,1611,1612],{"class":219},"-- A multi-level sales report with subtotals by city, by year, and a grand total.\n",[186,1614,1615],{"class":188,"line":196},[186,1616,1617],{"class":219},"-- Uses GROUPING SETS to produce all levels in a single pass over the data.\n",[186,1619,1620],{"class":188,"line":223},[186,1621,193],{"class":192},[186,1623,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1646,1649,1651,1653,1655,1657,1659,1661],{"class":188,"line":241},[186,1625,1351],{"class":192},[186,1627,1354],{"class":192},[186,1629,1357],{"class":199},[186,1631,204],{"class":203},[186,1633,770],{"class":199},[186,1635,72],{"class":203},[186,1637,537],{"class":199},[186,1639,210],{"class":203},[186,1641,1362],{"class":192},[186,1643,1365],{"class":199},[186,1645,1368],{"class":192},[186,1647,1648],{"class":971}," '=== ALL CITIES ==='",[186,1650,1374],{"class":192},[186,1652,532],{"class":199},[186,1654,72],{"class":203},[186,1656,537],{"class":199},[186,1658,1418],{"class":192},[186,1660,1383],{"class":192},[186,1662,1386],{"class":203},[186,1664,1665,1667,1669,1671,1674,1676,1678,1680,1683,1685,1687,1689,1692,1694],{"class":188,"line":258},[186,1666,1351],{"class":192},[186,1668,1354],{"class":192},[186,1670,1357],{"class":199},[186,1672,1673],{"class":203},"(yr)     ",[186,1675,1362],{"class":192},[186,1677,1365],{"class":199},[186,1679,1368],{"class":192},[186,1681,1682],{"class":971}," '=== ALL YEARS ==='",[186,1684,1409],{"class":192},[186,1686,1412],{"class":203},[186,1688,1415],{"class":192},[186,1690,1691],{"class":192},"   END",[186,1693,1383],{"class":192},[186,1695,1133],{"class":203},[186,1697,1698,1700,1702,1704,1706,1708],{"class":188,"line":274},[186,1699,200],{"class":199},[186,1701,204],{"class":203},[186,1703,207],{"class":192},[186,1705,210],{"class":203},[186,1707,213],{"class":192},[186,1709,1710],{"class":203}," order_count,\n",[186,1712,1713,1715,1717,1719,1721,1723,1725,1727],{"class":188,"line":287},[186,1714,244],{"class":199},[186,1716,204],{"class":203},[186,1718,562],{"class":199},[186,1720,72],{"class":203},[186,1722,567],{"class":199},[186,1724,210],{"class":203},[186,1726,213],{"class":192},[186,1728,1729],{"class":203}," revenue,\n",[186,1731,1732,1734,1736,1738,1740,1742,1744,1746],{"class":188,"line":300},[186,1733,261],{"class":199},[186,1735,204],{"class":203},[186,1737,562],{"class":199},[186,1739,72],{"class":203},[186,1741,567],{"class":199},[186,1743,210],{"class":203},[186,1745,213],{"class":192},[186,1747,1748],{"class":203}," avg_order\n",[186,1750,1751,1753],{"class":188,"line":309},[186,1752,303],{"class":192},[186,1754,581],{"class":203},[186,1756,1757,1759,1761,1763,1765,1767,1769,1771,1773,1775],{"class":188,"line":315},[186,1758,586],{"class":192},[186,1760,589],{"class":203},[186,1762,592],{"class":192},[186,1764,532],{"class":199},[186,1766,72],{"class":203},[186,1768,599],{"class":199},[186,1770,602],{"class":192},[186,1772,605],{"class":199},[186,1774,72],{"class":203},[186,1776,610],{"class":199},[186,1778,1779,1782,1785,1787,1790,1792,1794,1796,1798,1801,1803,1805],{"class":188,"line":321},[186,1780,1781],{"class":192},"CROSS JOIN",[186,1783,1784],{"class":203}," LATERAL (",[186,1786,473],{"class":192},[186,1788,1789],{"class":203}," EXTRACT(",[186,1791,1122],{"class":192},[186,1793,1125],{"class":192},[186,1795,605],{"class":199},[186,1797,72],{"class":203},[186,1799,1800],{"class":199},"ordered_on",[186,1802,210],{"class":203},[186,1804,213],{"class":192},[186,1806,1807],{"class":203}," yr) x\n",[186,1809,1810,1812,1814,1816],{"class":188,"line":439},[186,1811,446],{"class":192},[186,1813,1279],{"class":203},[186,1815,1282],{"class":192},[186,1817,1285],{"class":203},[186,1819,1820,1823,1825,1827,1829,1832],{"class":188,"line":1320},[186,1821,1822],{"class":203},"  (",[186,1824,770],{"class":199},[186,1826,72],{"class":203},[186,1828,537],{"class":199},[186,1830,1831],{"class":203},", yr),    ",[186,1833,1834],{"class":219},"-- per city per year\n",[186,1836,1838,1840,1842,1844,1846,1849],{"class":188,"line":1837},14,[186,1839,1822],{"class":203},[186,1841,770],{"class":199},[186,1843,72],{"class":203},[186,1845,537],{"class":199},[186,1847,1848],{"class":203},"),        ",[186,1850,1851],{"class":219},"-- per city (all years)\n",[186,1853,1855,1858],{"class":188,"line":1854},15,[186,1856,1857],{"class":203},"  (yr),            ",[186,1859,1860],{"class":219},"-- per year (all cities)\n",[186,1862,1864,1867],{"class":188,"line":1863},16,[186,1865,1866],{"class":203},"  ()               ",[186,1868,1869],{"class":219},"-- grand total\n",[186,1871,1873],{"class":188,"line":1872},17,[186,1874,1875],{"class":203},")\n",[186,1877,1879],{"class":188,"line":1878},18,[186,1880,1881],{"class":192},"ORDER BY\n",[186,1883,1885,1887,1889,1891,1893,1895,1897,1899,1901,1903,1905,1907,1909,1911,1914,1916,1919],{"class":188,"line":1884},19,[186,1886,1351],{"class":192},[186,1888,1354],{"class":192},[186,1890,1357],{"class":199},[186,1892,204],{"class":203},[186,1894,770],{"class":199},[186,1896,72],{"class":203},[186,1898,537],{"class":199},[186,1900,210],{"class":203},[186,1902,1362],{"class":192},[186,1904,1365],{"class":199},[186,1906,1368],{"class":192},[186,1908,1365],{"class":199},[186,1910,1374],{"class":192},[186,1912,1913],{"class":199}," 0",[186,1915,1418],{"class":192},[186,1917,1918],{"class":203},",   ",[186,1920,1921],{"class":219},"-- city subtotals after detail rows\n",[186,1923,1925,1928,1930,1932,1935,1938],{"class":188,"line":1924},20,[186,1926,1927],{"class":199},"  c",[186,1929,72],{"class":203},[186,1931,537],{"class":199},[186,1933,1934],{"class":192}," NULLS",[186,1936,1937],{"class":192}," LAST",[186,1939,1940],{"class":203},",\n",[186,1942,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964,1966],{"class":188,"line":1943},21,[186,1945,1351],{"class":192},[186,1947,1354],{"class":192},[186,1949,1357],{"class":199},[186,1951,1673],{"class":203},[186,1953,1362],{"class":192},[186,1955,1365],{"class":199},[186,1957,1368],{"class":192},[186,1959,1365],{"class":199},[186,1961,1374],{"class":192},[186,1963,1913],{"class":199},[186,1965,1418],{"class":192},[186,1967,1940],{"class":203},[186,1969,1971,1974,1977,1979],{"class":188,"line":1970},22,[186,1972,1973],{"class":203},"  yr ",[186,1975,1976],{"class":192},"NULLS",[186,1978,1937],{"class":192},[186,1980,514],{"class":203},[18,1982,1984],{"id":1983},"filtering-with-aggregates-without-group-by","Filtering with Aggregates Without GROUP BY",[174,1986,1987],{"language":176},[178,1988,1990],{"className":180,"code":1989,"language":176,"meta":182,"style":182},"-- No GROUP BY → the entire table is one group → always returns exactly one row\nSELECT SUM(amount) FROM orders;   -- one row: the total of all orders\n\n-- Even on an empty table: returns one row with SUM = NULL (not zero rows!)\nSELECT SUM(amount) FROM orders WHERE FALSE;   -- returns NULL, not 0, not zero rows\n\n-- To get zero rows from an empty table, add a HAVING that's false:\nSELECT SUM(amount) FROM orders HAVING COUNT(*) > 0;   -- zero rows if table is empty\n",[50,1991,1992,1997,2013,2017,2022,2042,2046,2051],{"__ignoreMap":182},[186,1993,1994],{"class":188,"line":189},[186,1995,1996],{"class":219},"-- No GROUP BY → the entire table is one group → always returns exactly one row\n",[186,1998,1999,2001,2003,2005,2007,2010],{"class":188,"line":196},[186,2000,473],{"class":192},[186,2002,914],{"class":199},[186,2004,247],{"class":203},[186,2006,303],{"class":192},[186,2008,2009],{"class":203}," orders;   ",[186,2011,2012],{"class":219},"-- one row: the total of all orders\n",[186,2014,2015],{"class":188,"line":223},[186,2016,520],{"emptyLinePlaceholder":519},[186,2018,2019],{"class":188,"line":241},[186,2020,2021],{"class":219},"-- Even on an empty table: returns one row with SUM = NULL (not zero rows!)\n",[186,2023,2024,2026,2028,2030,2032,2034,2036,2039],{"class":188,"line":258},[186,2025,473],{"class":192},[186,2027,914],{"class":199},[186,2029,247],{"class":203},[186,2031,303],{"class":192},[186,2033,678],{"class":203},[186,2035,851],{"class":192},[186,2037,2038],{"class":203}," FALSE;   ",[186,2040,2041],{"class":219},"-- returns NULL, not 0, not zero rows\n",[186,2043,2044],{"class":188,"line":274},[186,2045,520],{"emptyLinePlaceholder":519},[186,2047,2048],{"class":188,"line":287},[186,2049,2050],{"class":219},"-- To get zero rows from an empty table, add a HAVING that's false:\n",[186,2052,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2073,2075,2077,2079],{"class":188,"line":300},[186,2054,473],{"class":192},[186,2056,914],{"class":199},[186,2058,247],{"class":203},[186,2060,303],{"class":192},[186,2062,678],{"class":203},[186,2064,859],{"class":192},[186,2066,988],{"class":199},[186,2068,204],{"class":203},[186,2070,207],{"class":192},[186,2072,210],{"class":203},[186,2074,919],{"class":192},[186,2076,1913],{"class":199},[186,2078,836],{"class":203},[186,2080,2081],{"class":219},"-- zero rows if table is empty\n",[18,2083,2085],{"id":2084},"anti-pattern-avg-over-a-join-per-row-vs-per-entity","Anti-Pattern: AVG Over a Join (Per-Row vs Per-Entity)",[174,2087,2088],{"language":176},[178,2089,2091],{"className":180,"code":2090,"language":176,"meta":182,"style":182},"-- ❌ WRONG: AVG over a join computes per-ORDER average, not per-CUSTOMER average\n-- If Alice (NYC) has 1 order of $100 and Bob (NYC) has 10 orders averaging $20,\n-- this reports NYC's average as (100 + 200) \u002F 11 = $27.27 (per-order)\n-- — NOT the average customer spend ($100 vs $200 → $150)\nSELECT c.city, AVG(o.amount) AS avg_amount\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nGROUP BY c.city;\n\n-- ✅ RIGHT: nest the aggregate — first compute per-customer totals, then average those\nSELECT c.city, AVG(customer_total) AS avg_customer_spend\nFROM (\n  SELECT c.city, c.id, SUM(o.amount) AS customer_total    -- level 1: per-customer total\n  FROM customers c\n  JOIN orders o ON c.id = o.customer_id\n  GROUP BY c.city, c.id\n) sub\nGROUP BY c.city;   -- level 2: average of per-customer totals\n",[50,2092,2093,2098,2103,2108,2113,2141,2147,2169,2181,2185,2190,2212,2218,2259,2266,2289,2309,2314],{"__ignoreMap":182},[186,2094,2095],{"class":188,"line":189},[186,2096,2097],{"class":219},"-- ❌ WRONG: AVG over a join computes per-ORDER average, not per-CUSTOMER average\n",[186,2099,2100],{"class":188,"line":196},[186,2101,2102],{"class":219},"-- If Alice (NYC) has 1 order of $100 and Bob (NYC) has 10 orders averaging $20,\n",[186,2104,2105],{"class":188,"line":223},[186,2106,2107],{"class":219},"-- this reports NYC's average as (100 + 200) \u002F 11 = $27.27 (per-order)\n",[186,2109,2110],{"class":188,"line":241},[186,2111,2112],{"class":219},"-- — NOT the average customer spend ($100 vs $200 → $150)\n",[186,2114,2115,2117,2119,2121,2123,2125,2127,2129,2131,2133,2135,2137,2139],{"class":188,"line":258},[186,2116,473],{"class":192},[186,2118,532],{"class":199},[186,2120,72],{"class":203},[186,2122,537],{"class":199},[186,2124,540],{"class":203},[186,2126,557],{"class":199},[186,2128,204],{"class":203},[186,2130,562],{"class":199},[186,2132,72],{"class":203},[186,2134,567],{"class":199},[186,2136,210],{"class":203},[186,2138,213],{"class":192},[186,2140,574],{"class":203},[186,2142,2143,2145],{"class":188,"line":274},[186,2144,303],{"class":192},[186,2146,581],{"class":203},[186,2148,2149,2151,2153,2155,2157,2159,2161,2163,2165,2167],{"class":188,"line":287},[186,2150,586],{"class":192},[186,2152,589],{"class":203},[186,2154,592],{"class":192},[186,2156,532],{"class":199},[186,2158,72],{"class":203},[186,2160,599],{"class":199},[186,2162,602],{"class":192},[186,2164,605],{"class":199},[186,2166,72],{"class":203},[186,2168,610],{"class":199},[186,2170,2171,2173,2175,2177,2179],{"class":188,"line":300},[186,2172,446],{"class":192},[186,2174,532],{"class":199},[186,2176,72],{"class":203},[186,2178,537],{"class":199},[186,2180,514],{"class":203},[186,2182,2183],{"class":188,"line":309},[186,2184,520],{"emptyLinePlaceholder":519},[186,2186,2187],{"class":188,"line":315},[186,2188,2189],{"class":219},"-- ✅ RIGHT: nest the aggregate — first compute per-customer totals, then average those\n",[186,2191,2192,2194,2196,2198,2200,2202,2204,2207,2209],{"class":188,"line":321},[186,2193,473],{"class":192},[186,2195,532],{"class":199},[186,2197,72],{"class":203},[186,2199,537],{"class":199},[186,2201,540],{"class":203},[186,2203,557],{"class":199},[186,2205,2206],{"class":203},"(customer_total) ",[186,2208,213],{"class":192},[186,2210,2211],{"class":203}," avg_customer_spend\n",[186,2213,2214,2216],{"class":188,"line":439},[186,2215,303],{"class":192},[186,2217,1285],{"class":203},[186,2219,2220,2223,2225,2227,2229,2231,2233,2235,2237,2239,2241,2243,2245,2247,2249,2251,2253,2256],{"class":188,"line":1320},[186,2221,2222],{"class":192},"  SELECT",[186,2224,532],{"class":199},[186,2226,72],{"class":203},[186,2228,537],{"class":199},[186,2230,540],{"class":203},[186,2232,770],{"class":199},[186,2234,72],{"class":203},[186,2236,599],{"class":199},[186,2238,540],{"class":203},[186,2240,479],{"class":199},[186,2242,204],{"class":203},[186,2244,562],{"class":199},[186,2246,72],{"class":203},[186,2248,567],{"class":199},[186,2250,210],{"class":203},[186,2252,213],{"class":192},[186,2254,2255],{"class":203}," customer_total    ",[186,2257,2258],{"class":219},"-- level 1: per-customer total\n",[186,2260,2261,2264],{"class":188,"line":1837},[186,2262,2263],{"class":192},"  FROM",[186,2265,581],{"class":203},[186,2267,2268,2271,2273,2275,2277,2279,2281,2283,2285,2287],{"class":188,"line":1854},[186,2269,2270],{"class":192},"  JOIN",[186,2272,589],{"class":203},[186,2274,592],{"class":192},[186,2276,532],{"class":199},[186,2278,72],{"class":203},[186,2280,599],{"class":199},[186,2282,602],{"class":192},[186,2284,605],{"class":199},[186,2286,72],{"class":203},[186,2288,610],{"class":199},[186,2290,2291,2294,2296,2298,2300,2302,2304,2306],{"class":188,"line":1863},[186,2292,2293],{"class":192},"  GROUP BY",[186,2295,532],{"class":199},[186,2297,72],{"class":203},[186,2299,537],{"class":199},[186,2301,540],{"class":203},[186,2303,770],{"class":199},[186,2305,72],{"class":203},[186,2307,2308],{"class":199},"id\n",[186,2310,2311],{"class":188,"line":1872},[186,2312,2313],{"class":203},") sub\n",[186,2315,2316,2318,2320,2322,2324,2326],{"class":188,"line":1878},[186,2317,446],{"class":192},[186,2319,532],{"class":199},[186,2321,72],{"class":203},[186,2323,537],{"class":199},[186,2325,836],{"class":203},[186,2327,2328],{"class":219},"-- level 2: average of per-customer totals\n",[14,2330,2331,2332,2335],{},"SQL can't nest aggregates directly (",[50,2333,2334],{},"AVG(SUM(x))"," is illegal) — use a subquery\u002FCTE to aggregate at the entity level first, then average.",[18,2337,2339],{"id":2338},"tips-tricks","💡 Tips & Tricks",[2341,2342,2343,2365],"ul",{},[637,2344,2345,2348,2349,2352,2353,2355,2356,2358,2359,2361,2362,2364],{},[453,2346,2347],{},"Idiom",": use ",[50,2350,2351],{},"COUNT(o.id)"," (a non-nullable right-table column) instead of ",[50,2354,52],{}," when counting matches in a ",[50,2357,805],{}," — ",[50,2360,52],{}," counts the NULL-extended row for unmatched left rows as 1, inflating the count; ",[50,2363,2351],{}," skips NULLs and counts only real matches.",[637,2366,2367,2370,2371,2373,2374,2377,2378,2381],{},[453,2368,2369],{},"Performance",": ",[50,2372,84],{}," can be slow on large tables (it must sort or hash all values to deduplicate). For approximate distinct counts at scale, PostgreSQL has ",[50,2375,2376],{},"HyperLogLog"," via the ",[50,2379,2380],{},"hll"," extension — trading exactness for O(1) memory.",[174,2383,2384],{"language":176},[178,2385,2387],{"className":180,"code":2386,"language":176,"meta":182,"style":182},"-- Exact distinct count — O(N) memory, precise\nSELECT COUNT(DISTINCT customer_id) FROM orders;\n\n-- Approximate distinct count with HyperLogLog — O(1) memory, ~1-2% error\n-- Requires: CREATE EXTENSION hll;\nSELECT hll_cardinality(hll_agg(customer_id)) FROM orders;\n",[50,2388,2389,2394,2410,2414,2419,2424],{"__ignoreMap":182},[186,2390,2391],{"class":188,"line":189},[186,2392,2393],{"class":219},"-- Exact distinct count — O(N) memory, precise\n",[186,2395,2396,2398,2400,2402,2404,2406,2408],{"class":188,"line":196},[186,2397,473],{"class":192},[186,2399,988],{"class":199},[186,2401,204],{"class":203},[186,2403,230],{"class":192},[186,2405,233],{"class":203},[186,2407,303],{"class":192},[186,2409,306],{"class":203},[186,2411,2412],{"class":188,"line":223},[186,2413,520],{"emptyLinePlaceholder":519},[186,2415,2416],{"class":188,"line":241},[186,2417,2418],{"class":219},"-- Approximate distinct count with HyperLogLog — O(1) memory, ~1-2% error\n",[186,2420,2421],{"class":188,"line":258},[186,2422,2423],{"class":219},"-- Requires: CREATE EXTENSION hll;\n",[186,2425,2426,2428,2431,2433],{"class":188,"line":274},[186,2427,473],{"class":192},[186,2429,2430],{"class":203}," hll_cardinality(hll_agg(customer_id)) ",[186,2432,303],{"class":192},[186,2434,306],{"class":203},[2341,2436,2437],{},[637,2438,2439,2441,2442,2445,2446,2449],{},[453,2440,2347],{},": prefer ",[50,2443,2444],{},"COUNT(*) FILTER (WHERE condition)"," (PostgreSQL) or ",[50,2447,2448],{},"SUM(CASE WHEN condition THEN 1 ELSE 0 END)"," (portable) over multiple subqueries for conditional counts — one pass over the data, multiple metrics per row.",[174,2451,2452],{"language":176},[178,2453,2455],{"className":180,"code":2454,"language":176,"meta":182,"style":182},"-- Conditional aggregation with FILTER (PostgreSQL 9.4+) — clean and fast\nSELECT\n  customer_id,\n  COUNT(*) AS total_orders,\n  COUNT(*) FILTER (WHERE status = 'shipped') AS shipped,\n  COUNT(*) FILTER (WHERE status = 'cancelled') AS cancelled,\n  SUM(amount) FILTER (WHERE status = 'shipped') AS shipped_revenue\nFROM orders\nGROUP BY customer_id;\n\n-- Portable equivalent using CASE\nSELECT\n  customer_id,\n  COUNT(*) AS total_orders,\n  SUM(CASE WHEN status = 'shipped' THEN 1 ELSE 0 END) AS shipped,\n  SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled\nFROM orders\nGROUP BY customer_id;\n",[50,2456,2457,2462,2466,2470,2485,2518,2548,2573,2579,2585,2589,2594,2598,2602,2616,2649,2682,2688],{"__ignoreMap":182},[186,2458,2459],{"class":188,"line":189},[186,2460,2461],{"class":219},"-- Conditional aggregation with FILTER (PostgreSQL 9.4+) — clean and fast\n",[186,2463,2464],{"class":188,"line":196},[186,2465,193],{"class":192},[186,2467,2468],{"class":188,"line":223},[186,2469,1114],{"class":203},[186,2471,2472,2474,2476,2478,2480,2482],{"class":188,"line":241},[186,2473,200],{"class":199},[186,2475,204],{"class":203},[186,2477,207],{"class":192},[186,2479,210],{"class":203},[186,2481,213],{"class":192},[186,2483,2484],{"class":203}," total_orders,\n",[186,2486,2487,2489,2491,2493,2495,2498,2501,2503,2506,2508,2511,2513,2515],{"class":188,"line":258},[186,2488,200],{"class":199},[186,2490,204],{"class":203},[186,2492,207],{"class":192},[186,2494,210],{"class":203},[186,2496,2497],{"class":192},"FILTER",[186,2499,2500],{"class":203}," (",[186,2502,851],{"class":192},[186,2504,2505],{"class":192}," status",[186,2507,602],{"class":192},[186,2509,2510],{"class":971}," 'shipped'",[186,2512,210],{"class":203},[186,2514,213],{"class":192},[186,2516,2517],{"class":203}," shipped,\n",[186,2519,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2541,2543,2545],{"class":188,"line":274},[186,2521,200],{"class":199},[186,2523,204],{"class":203},[186,2525,207],{"class":192},[186,2527,210],{"class":203},[186,2529,2497],{"class":192},[186,2531,2500],{"class":203},[186,2533,851],{"class":192},[186,2535,2505],{"class":192},[186,2537,602],{"class":192},[186,2539,2540],{"class":971}," 'cancelled'",[186,2542,210],{"class":203},[186,2544,213],{"class":192},[186,2546,2547],{"class":203}," cancelled,\n",[186,2549,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568,2570],{"class":188,"line":287},[186,2551,244],{"class":199},[186,2553,247],{"class":203},[186,2555,2497],{"class":192},[186,2557,2500],{"class":203},[186,2559,851],{"class":192},[186,2561,2505],{"class":192},[186,2563,602],{"class":192},[186,2565,2510],{"class":971},[186,2567,210],{"class":203},[186,2569,213],{"class":192},[186,2571,2572],{"class":203}," shipped_revenue\n",[186,2574,2575,2577],{"class":188,"line":300},[186,2576,303],{"class":192},[186,2578,493],{"class":203},[186,2580,2581,2583],{"class":188,"line":309},[186,2582,446],{"class":192},[186,2584,683],{"class":203},[186,2586,2587],{"class":188,"line":315},[186,2588,520],{"emptyLinePlaceholder":519},[186,2590,2591],{"class":188,"line":321},[186,2592,2593],{"class":219},"-- Portable equivalent using CASE\n",[186,2595,2596],{"class":188,"line":439},[186,2597,193],{"class":192},[186,2599,2600],{"class":188,"line":1320},[186,2601,1114],{"class":203},[186,2603,2604,2606,2608,2610,2612,2614],{"class":188,"line":1837},[186,2605,200],{"class":199},[186,2607,204],{"class":203},[186,2609,207],{"class":192},[186,2611,210],{"class":203},[186,2613,213],{"class":192},[186,2615,2484],{"class":203},[186,2617,2618,2620,2622,2625,2627,2629,2631,2633,2635,2637,2639,2641,2643,2645,2647],{"class":188,"line":1854},[186,2619,244],{"class":199},[186,2621,204],{"class":203},[186,2623,2624],{"class":192},"CASE",[186,2626,1354],{"class":192},[186,2628,2505],{"class":192},[186,2630,602],{"class":192},[186,2632,2510],{"class":971},[186,2634,1368],{"class":192},[186,2636,1365],{"class":199},[186,2638,1374],{"class":192},[186,2640,1913],{"class":199},[186,2642,1418],{"class":192},[186,2644,210],{"class":203},[186,2646,213],{"class":192},[186,2648,2517],{"class":203},[186,2650,2651,2653,2655,2657,2659,2661,2663,2665,2667,2669,2671,2673,2675,2677,2679],{"class":188,"line":1863},[186,2652,244],{"class":199},[186,2654,204],{"class":203},[186,2656,2624],{"class":192},[186,2658,1354],{"class":192},[186,2660,2505],{"class":192},[186,2662,602],{"class":192},[186,2664,2540],{"class":971},[186,2666,1368],{"class":192},[186,2668,1365],{"class":199},[186,2670,1374],{"class":192},[186,2672,1913],{"class":199},[186,2674,1418],{"class":192},[186,2676,210],{"class":203},[186,2678,213],{"class":192},[186,2680,2681],{"class":203}," cancelled\n",[186,2683,2684,2686],{"class":188,"line":1872},[186,2685,303],{"class":192},[186,2687,493],{"class":203},[186,2689,2690,2692],{"class":188,"line":1878},[186,2691,446],{"class":192},[186,2693,683],{"class":203},[2341,2695,2696,2716],{},[637,2697,2698,2701,2702,2704,2705,2708,2709,2712,2713,2715],{},[453,2699,2700],{},"Debug",": if a ",[50,2703,446],{}," query returns ",[1083,2706,2707],{},"fewer"," groups than expected, check whether the grouping column has NULLs — all NULLs collapse into a single \"NULL group.\" If that's not desired, ",[50,2710,2711],{},"COALESCE(col, 'unknown')"," in the ",[50,2714,446],{}," makes the NULLs an explicit bucket.",[637,2717,2718,2370,2721,2724,2725,2728,2729,2732],{},[453,2719,2720],{},"Portability",[50,2722,2723],{},"string_agg(col, ',' ORDER BY col)"," (PostgreSQL) is ",[50,2726,2727],{},"GROUP_CONCAT(col ORDER BY col SEPARATOR ',')"," (MySQL) or ",[50,2730,2731],{},"LISTAGG(col, ',')"," (Oracle\u002FSQL Server) — string aggregation is one of the least portable areas of SQL.",[18,2734,2736],{"id":2735},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2341,2738,2739,2766,2781,2795,2817,2853,2867,2887,2901],{},[637,2740,2741,2370,2746,2749,2750,2753,2754,2757,2758,2761,2762,2765],{},[453,2742,2743,2745],{},[50,2744,557],{}," ignores NULLs, doesn't treat them as 0",[50,2747,2748],{},"AVG(amount)"," over rows ",[50,2751,2752],{},"[10, 20, NULL]"," is ",[50,2755,2756],{},"15",", not ",[50,2759,2760],{},"10",". If you want NULL treated as 0, use ",[50,2763,2764],{},"AVG(COALESCE(amount, 0))"," — but ask whether that's semantically right (a missing value isn't necessarily zero).",[637,2767,2768,2370,2773,2776,2777,2780],{},[453,2769,2770,2772],{},[50,2771,479],{}," of an empty set is NULL, not 0",[50,2774,2775],{},"SELECT SUM(amount) FROM orders WHERE FALSE"," returns NULL. Use ",[50,2778,2779],{},"COALESCE(SUM(amount), 0)"," when you need 0 for \"no rows.\"",[637,2782,2783,2788,2789,2791,2792,2794],{},[453,2784,2785,2787],{},[50,2786,543],{}," on an empty group is 0, not NULL",": aggregates other than ",[50,2790,543],{}," return NULL on empty input; ",[50,2793,543],{}," returns 0. This asymmetry is a frequent source of confusion.",[637,2796,2797,2370,2805,2808,2809,2811,2812,2814,2815,72],{},[453,2798,2799,2801,2802],{},[50,2800,446],{}," and ",[50,2803,2804],{},"SELECT *",[50,2806,2807],{},"SELECT * ... GROUP BY x"," is almost always an error (columns not in ",[50,2810,446],{}," and not aggregated). Don't combine ",[50,2813,207],{}," with ",[50,2816,446],{},[637,2818,2819,2827,2828,2824,2831,2834,2835,2837,2838,2841,2842,2845,2846,2824,2849,2852],{},[453,2820,2821,2822,2824,2825],{},"Floating-point ",[50,2823,479],{},"\u002F",[50,2826,557],{},": summing ",[50,2829,2830],{},"REAL",[50,2832,2833],{},"DOUBLE PRECISION"," is subject to floating-point error — ",[50,2836,479],{}," of ",[50,2839,2840],{},"[0.1, 0.1, 0.1]"," may be ",[50,2843,2844],{},"0.30000000000000004",". Use ",[50,2847,2848],{},"NUMERIC",[50,2850,2851],{},"DECIMAL"," for money and exact arithmetic.",[637,2854,2855,2862,2863,2866],{},[453,2856,2857,2859,2860],{},[50,2858,859],{}," without ",[50,2861,446],{},": legal — treats the whole table as one group. ",[50,2864,2865],{},"SELECT COUNT(*) FROM orders HAVING COUNT(*) > 0"," returns one row if the table is non-empty, zero rows if empty.",[637,2868,2869,2875,2876,2879,2880,2882,2883,2886],{},[453,2870,2871,2872,2874],{},"Alias in ",[50,2873,859],{}," portability",": PostgreSQL allows ",[50,2877,2878],{},"HAVING total_spent > 100"," (using the ",[50,2881,473],{}," alias), but the SQL standard and most engines require ",[50,2884,2885],{},"HAVING SUM(amount) > 100"," (the raw expression). Use the raw expression for portability.",[637,2888,2889,2370,2894,2897,2898,2900],{},[453,2890,2891,2893],{},[50,2892,446],{}," ordinal fragility",[50,2895,2896],{},"GROUP BY 1, 2"," (group by first and second selected columns) is legal but fragile — reordering ",[50,2899,473],{}," columns silently changes the grouping. Prefer explicit column names.",[637,2902,2903,2370,2912,2915,2916,2918,2919,2922,2923,2926],{},[453,2904,2905,2824,2908,2911],{},[50,2906,2907],{},"MIN",[50,2909,2910],{},"MAX"," on text with collation",[50,2913,2914],{},"MIN(name)"," returns the lexicographically smallest string per the column's collation — collation affects the result, so ",[50,2917,2907],{}," on a case-insensitive collation may return ",[50,2920,2921],{},"'apple'"," before ",[50,2924,2925],{},"'Banana'"," differently than a case-sensitive one.",[18,2928,2930],{"id":2929},"spot-the-bug","🧠 Spot the Bug",[14,2932,2933],{},"This query is supposed to report the average order amount per city, but the averages look wrong for cities with few orders. What's the subtle issue?",[174,2935,2936],{"language":176},[178,2937,2939],{"className":180,"code":2938,"language":176,"meta":182,"style":182},"SELECT c.city, AVG(o.amount) AS avg_amount\nFROM customers c\nLEFT JOIN orders o ON c.id = o.customer_id\nGROUP BY c.city;\n",[50,2940,2941,2969,2975,2997],{"__ignoreMap":182},[186,2942,2943,2945,2947,2949,2951,2953,2955,2957,2959,2961,2963,2965,2967],{"class":188,"line":189},[186,2944,473],{"class":192},[186,2946,532],{"class":199},[186,2948,72],{"class":203},[186,2950,537],{"class":199},[186,2952,540],{"class":203},[186,2954,557],{"class":199},[186,2956,204],{"class":203},[186,2958,562],{"class":199},[186,2960,72],{"class":203},[186,2962,567],{"class":199},[186,2964,210],{"class":203},[186,2966,213],{"class":192},[186,2968,574],{"class":203},[186,2970,2971,2973],{"class":188,"line":196},[186,2972,303],{"class":192},[186,2974,581],{"class":203},[186,2976,2977,2979,2981,2983,2985,2987,2989,2991,2993,2995],{"class":188,"line":223},[186,2978,805],{"class":192},[186,2980,589],{"class":203},[186,2982,592],{"class":192},[186,2984,532],{"class":199},[186,2986,72],{"class":203},[186,2988,599],{"class":199},[186,2990,602],{"class":192},[186,2992,605],{"class":199},[186,2994,72],{"class":203},[186,2996,610],{"class":199},[186,2998,2999,3001,3003,3005,3007],{"class":188,"line":241},[186,3000,446],{"class":192},[186,3002,532],{"class":199},[186,3004,72],{"class":203},[186,3006,537],{"class":199},[186,3008,514],{"class":203},[3010,3011,3012,3016,3037,3044,3255],"details",{},[3013,3014,3015],"summary",{},"Answer",[14,3017,3018,3019,2757,3022,3025,3026,3028,3029,3032,3033,3036],{},"The bug: the average is ",[453,3020,3021],{},"per-order",[453,3023,3024],{},"per-customer",". ",[50,3027,557],{}," over a join computes the average over ",[1083,3030,3031],{},"rows"," (orders), not over ",[1083,3034,3035],{},"entities"," (customers).",[14,3038,3039,3040,3043],{},"If Alice (NYC) has 1 order of $100 and Bob (NYC) has 10 orders averaging $20, the query reports NYC's average as ",[50,3041,3042],{},"(100 + 200) \u002F 11 = $27.27"," — the per-order average. If the intent was \"average customer spend in NYC\" (Alice's $100 vs Bob's $200, average = $150), you need a nested aggregate: average the per-customer totals.",[174,3045,3046],{"language":176},[178,3047,3049],{"className":180,"code":3048,"language":176,"meta":182,"style":182},"-- Per-order average (what the original computes)\nSELECT c.city, AVG(o.amount)\nFROM customers c JOIN orders o ON c.id = o.customer_id\nGROUP BY c.city;\n\n-- Per-customer average (average of each customer's total)\nSELECT c.city, AVG(customer_total) AS avg_customer_spend\nFROM (\n  SELECT c.city, c.id, SUM(o.amount) AS customer_total   -- level 1: per-customer total\n  FROM customers c JOIN orders o ON c.id = o.customer_id\n  GROUP BY c.city, c.id\n) sub\nGROUP BY c.city;   -- level 2: average of per-customer totals\n",[50,3050,3051,3056,3080,3107,3119,3123,3128,3148,3154,3193,3219,3237,3241],{"__ignoreMap":182},[186,3052,3053],{"class":188,"line":189},[186,3054,3055],{"class":219},"-- Per-order average (what the original computes)\n",[186,3057,3058,3060,3062,3064,3066,3068,3070,3072,3074,3076,3078],{"class":188,"line":196},[186,3059,473],{"class":192},[186,3061,532],{"class":199},[186,3063,72],{"class":203},[186,3065,537],{"class":199},[186,3067,540],{"class":203},[186,3069,557],{"class":199},[186,3071,204],{"class":203},[186,3073,562],{"class":199},[186,3075,72],{"class":203},[186,3077,567],{"class":199},[186,3079,1875],{"class":203},[186,3081,3082,3084,3087,3089,3091,3093,3095,3097,3099,3101,3103,3105],{"class":188,"line":223},[186,3083,303],{"class":192},[186,3085,3086],{"class":203}," customers c ",[186,3088,586],{"class":192},[186,3090,589],{"class":203},[186,3092,592],{"class":192},[186,3094,532],{"class":199},[186,3096,72],{"class":203},[186,3098,599],{"class":199},[186,3100,602],{"class":192},[186,3102,605],{"class":199},[186,3104,72],{"class":203},[186,3106,610],{"class":199},[186,3108,3109,3111,3113,3115,3117],{"class":188,"line":241},[186,3110,446],{"class":192},[186,3112,532],{"class":199},[186,3114,72],{"class":203},[186,3116,537],{"class":199},[186,3118,514],{"class":203},[186,3120,3121],{"class":188,"line":258},[186,3122,520],{"emptyLinePlaceholder":519},[186,3124,3125],{"class":188,"line":274},[186,3126,3127],{"class":219},"-- Per-customer average (average of each customer's total)\n",[186,3129,3130,3132,3134,3136,3138,3140,3142,3144,3146],{"class":188,"line":287},[186,3131,473],{"class":192},[186,3133,532],{"class":199},[186,3135,72],{"class":203},[186,3137,537],{"class":199},[186,3139,540],{"class":203},[186,3141,557],{"class":199},[186,3143,2206],{"class":203},[186,3145,213],{"class":192},[186,3147,2211],{"class":203},[186,3149,3150,3152],{"class":188,"line":300},[186,3151,303],{"class":192},[186,3153,1285],{"class":203},[186,3155,3156,3158,3160,3162,3164,3166,3168,3170,3172,3174,3176,3178,3180,3182,3184,3186,3188,3191],{"class":188,"line":309},[186,3157,2222],{"class":192},[186,3159,532],{"class":199},[186,3161,72],{"class":203},[186,3163,537],{"class":199},[186,3165,540],{"class":203},[186,3167,770],{"class":199},[186,3169,72],{"class":203},[186,3171,599],{"class":199},[186,3173,540],{"class":203},[186,3175,479],{"class":199},[186,3177,204],{"class":203},[186,3179,562],{"class":199},[186,3181,72],{"class":203},[186,3183,567],{"class":199},[186,3185,210],{"class":203},[186,3187,213],{"class":192},[186,3189,3190],{"class":203}," customer_total   ",[186,3192,2258],{"class":219},[186,3194,3195,3197,3199,3201,3203,3205,3207,3209,3211,3213,3215,3217],{"class":188,"line":315},[186,3196,2263],{"class":192},[186,3198,3086],{"class":203},[186,3200,586],{"class":192},[186,3202,589],{"class":203},[186,3204,592],{"class":192},[186,3206,532],{"class":199},[186,3208,72],{"class":203},[186,3210,599],{"class":199},[186,3212,602],{"class":192},[186,3214,605],{"class":199},[186,3216,72],{"class":203},[186,3218,610],{"class":199},[186,3220,3221,3223,3225,3227,3229,3231,3233,3235],{"class":188,"line":321},[186,3222,2293],{"class":192},[186,3224,532],{"class":199},[186,3226,72],{"class":203},[186,3228,537],{"class":199},[186,3230,540],{"class":203},[186,3232,770],{"class":199},[186,3234,72],{"class":203},[186,3236,2308],{"class":199},[186,3238,3239],{"class":188,"line":439},[186,3240,2313],{"class":203},[186,3242,3243,3245,3247,3249,3251,3253],{"class":188,"line":1320},[186,3244,446],{"class":192},[186,3246,532],{"class":199},[186,3248,72],{"class":203},[186,3250,537],{"class":199},[186,3252,836],{"class":203},[186,3254,2328],{"class":219},[14,3256,3257,2370,3260,3028,3262,3032,3264,3266,3267,3269],{},[453,3258,3259],{},"The lesson",[50,3261,557],{},[1083,3263,3031],{},[1083,3265,3035],{}," (customers). To average per-entity, aggregate to the entity level first, then average those aggregates. SQL can't nest aggregates directly (",[50,3268,2334],{}," is illegal), so use a subquery\u002FCTE.",[18,3271,3272],{"id":3013},"Summary",[14,3274,3275,3276,2824,3278,2824,3280,2824,3282,2824,3284,3286,3287,3289,3290,3292,3293,2824,3295,2824,3297,3299],{},"You can now aggregate with ",[50,3277,543],{},[50,3279,479],{},[50,3281,557],{},[50,3283,2907],{},[50,3285,2910],{},", group rows with ",[50,3288,446],{},", filter groups with ",[50,3291,859],{},", and generate multi-level subtotals with ",[50,3294,1484],{},[50,3296,1540],{},[50,3298,1197],{}," — while understanding the NULL-handling quirks that make aggregates surprise the unwary. Next: subqueries, the compositional building block of complex queries.",[3301,3302,3303],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}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":182,"searchDepth":196,"depth":196,"links":3305},[3306,3310,3314,3317,3318,3326,3327,3328,3329,3330,3331,3332],{"id":20,"depth":196,"text":21,"children":3307},[3308],{"id":328,"depth":223,"text":3309},"COUNT(*) vs COUNT(col) vs COUNT(1)",{"id":445,"depth":196,"text":446,"children":3311},[3312,3313],{"id":625,"depth":223,"text":626},{"id":737,"depth":223,"text":738},{"id":845,"depth":196,"text":846,"children":3315},[3316],{"id":1008,"depth":223,"text":1009},{"id":1092,"depth":196,"text":1093},{"id":1183,"depth":196,"text":1184,"children":3319},[3320,3322,3324],{"id":1194,"depth":223,"text":3321},"GROUPING SETS — specific combinations",{"id":1481,"depth":223,"text":3323},"ROLLUP — hierarchical subtotals",{"id":1537,"depth":223,"text":3325},"CUBE — all combinations",{"id":1599,"depth":196,"text":1600},{"id":1983,"depth":196,"text":1984},{"id":2084,"depth":196,"text":2085},{"id":2338,"depth":196,"text":2339},{"id":2735,"depth":196,"text":2736},{"id":2929,"depth":196,"text":2930},{"id":3013,"depth":196,"text":3272},"md",{},"\u002Fsql\u002F05-aggregation-and-group-by",{"title":5,"description":16},"sql\u002F05-aggregation-and-group-by","_8KZukh0Q0wqqOfdPQKZ25r3rO1Ovyr4rrYTZtKUxcU",1789924654337]