What are aggregate functions in SQL?
They turn many rows into one value:
COUNT,SUM,AVG,MINandMAX. WithGROUP BYthey return one value per group.SQLCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER, manager_id INTEGER); INSERT INTO employees VALUES (1, 'Asha', 'Engineering', 120000, NULL), (2, 'Ravi', 'Engineering', 90000, 1), (3, 'Meera', 'Engineering', 90000, 1), (4, 'John', 'Sales', 70000, 1), (5, 'Priya', 'Sales', 82000, 4), (6, 'Karan', 'HR', 60000, 1); SELECT dept, COUNT(*) AS people, SUM(salary) AS payroll, AVG(salary) AS average, MIN(salary) AS lowest, MAX(salary) AS highest FROM employees GROUP BY dept ORDER BY dept;Every aggregate except
COUNT(*)ignores NULLs. See aggregate functions.How does
AVGtreat NULL values?SQLCREATE TABLE bonuses (name TEXT, bonus INTEGER); INSERT INTO bonuses VALUES ('Asha', 100), ('Ravi', NULL), ('Meera', 200); SELECT AVG(bonus) FROM bonuses;AVGskips NULLs, so it averages 100 and 200: 150.0. If NULL means "no bonus", useAVG(COALESCE(bonus, 0)), which gives 100.0. In SQL Server,AVGof anINTcolumn returns anINT, so cast toDECIMALfirst.Why do you get "column must appear in the GROUP BY clause or be used in an aggregate function"?
Because a selected column has several values per group and you did not say which one to show: after
GROUP BY dept,namehas three values for Engineering.PostgreSQL, SQL Server, Oracle and MySQL with
ONLY_FULL_GROUP_BY(the default since 5.7.5) reject it. SQLite accepts it, and with a singleMAXorMINtakes the bare columns from that row:SQLCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER, manager_id INTEGER); INSERT INTO employees VALUES (1, 'Asha', 'Engineering', 120000, NULL), (2, 'Ravi', 'Engineering', 90000, 1), (3, 'Meera', 'Engineering', 90000, 1), (4, 'John', 'Sales', 70000, 1), (5, 'Priya', 'Sales', 82000, 4), (6, 'Karan', 'HR', 60000, 1); SELECT dept, name, MAX(salary) FROM employees GROUP BY dept ORDER BY dept;That works here but fails in PostgreSQL. The portable version uses
RANK() OVER (PARTITION BY dept ORDER BY salary DESC).Which filter runs first,
WHEREorHAVING?SQLCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER, manager_id INTEGER); INSERT INTO employees VALUES (1, 'Asha', 'Engineering', 120000, NULL), (2, 'Ravi', 'Engineering', 90000, 1), (3, 'Meera', 'Engineering', 90000, 1), (4, 'John', 'Sales', 70000, 1), (5, 'Priya', 'Sales', 82000, 4), (6, 'Karan', 'HR', 60000, 1); SELECT COUNT(*) FROM ( SELECT dept FROM employees WHERE salary > 75000 GROUP BY dept HAVING COUNT(*) > 1 ) AS t;WHERE salary > 75000runs first and leaves three Engineering rows and Priya from Sales.HAVING COUNT(*) > 1keeps only Engineering, so the outer query counts 1. Answering 2 forgets that John (70000) was filtered out before the count.How do you count only the rows that match a condition inside a GROUP BY?
Put a
CASEinside the aggregate:SUM(CASE WHEN condition THEN 1 ELSE 0 END). PostgreSQL and SQLite 3.30+ also have the standardFILTERclause; MySQL and SQL Server do not.SQLCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER, manager_id INTEGER); INSERT INTO employees VALUES (1, 'Asha', 'Engineering', 120000, NULL), (2, 'Ravi', 'Engineering', 90000, 1), (3, 'Meera', 'Engineering', 90000, 1), (4, 'John', 'Sales', 70000, 1), (5, 'Priya', 'Sales', 82000, 4), (6, 'Karan', 'HR', 60000, 1); SELECT dept, COUNT(*) AS people, SUM(CASE WHEN salary >= 85000 THEN 1 ELSE 0 END) AS high_paid, COUNT(*) FILTER (WHERE salary < 85000) AS others FROM employees GROUP BY dept ORDER BY dept;It computes several counts in one pass, and it is also how you pivot rows into columns.
How do you combine values from many rows into one comma-separated string?
Use the string aggregate:
GROUP_CONCATin MySQL and SQLite,STRING_AGGin PostgreSQL and SQL Server 2017+,LISTAGGin Oracle.SQLCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER, manager_id INTEGER); INSERT INTO employees VALUES (1, 'Asha', 'Engineering', 120000, NULL), (2, 'Ravi', 'Engineering', 90000, 1), (3, 'Meera', 'Engineering', 90000, 1), (4, 'John', 'Sales', 70000, 1), (5, 'Priya', 'Sales', 82000, 4), (6, 'Karan', 'HR', 60000, 1); SELECT dept, GROUP_CONCAT(name, ', ' ORDER BY name) AS people FROM employees GROUP BY dept ORDER BY dept;Without an
ORDER BYinside the aggregate the order is not guaranteed (SQLite accepts it from 3.44). MySQL cuts the result atgroup_concat_max_len, 1024 bytes by default.What do ROLLUP, CUBE and GROUPING SETS do?
They add subtotal rows in one query.
ROLLUP (a, b)groups by(a, b), then(a), then the grand total;CUBEgroups by every combination;GROUPING SETSlists exactly the groupings you want.-- PostgreSQL, SQL Server, Oracle SELECT dept, SUM(salary) FROM employees GROUP BY ROLLUP (dept); -- MySQL (only ROLLUP; no CUBE or GROUPING SETS) SELECT dept, SUM(salary) FROM employees GROUP BY dept WITH ROLLUP;GROUPING(dept)returns 1 on a subtotal row, telling it apart from a real NULL. SQLite has none of these, so useUNION ALL:SQLCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER, manager_id INTEGER); INSERT INTO employees VALUES (1, 'Asha', 'Engineering', 120000, NULL), (2, 'Ravi', 'Engineering', 90000, 1), (3, 'Meera', 'Engineering', 90000, 1), (4, 'John', 'Sales', 70000, 1), (5, 'Priya', 'Sales', 82000, 4), (6, 'Karan', 'HR', 60000, 1); SELECT dept, total FROM ( SELECT 0 AS k, dept, SUM(salary) AS total FROM employees GROUP BY dept UNION ALL SELECT 1, 'All departments', SUM(salary) FROM employees ) AS t ORDER BY k, dept;The last row is the grand total, 512000.
What is the difference between DISTINCT and GROUP BY?
Without aggregates they return the same rows and usually get the same plan.
GROUP BYexists to compute aggregates per group;DISTINCTonly removes duplicate result rows.SQLCREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER, manager_id INTEGER); INSERT INTO employees VALUES (1, 'Asha', 'Engineering', 120000, NULL), (2, 'Ravi', 'Engineering', 90000, 1), (3, 'Meera', 'Engineering', 90000, 1), (4, 'John', 'Sales', 70000, 1), (5, 'Priya', 'Sales', 82000, 4), (6, 'Karan', 'HR', 60000, 1); SELECT (SELECT COUNT(*) FROM (SELECT DISTINCT dept FROM employees) AS d) AS distinct_rows, (SELECT COUNT(*) FROM (SELECT dept FROM employees GROUP BY dept) AS g) AS grouped_rows;Both return 3. A
DISTINCTadded to hide duplicates from a bad join is a smell; fix the join.How do you GROUP BY multiple columns?
List them all in
GROUP BY: the query returns one row per distinct combination of their values.SQLCREATE TABLE sales (region TEXT, product TEXT, amount INTEGER); INSERT INTO sales VALUES ('North', 'Pen', 100), ('North', 'Pen', 50), ('North', 'Book', 200), ('South', 'Pen', 80), ('South', 'Book', 120), ('South', 'Book', 30); SELECT region, product, COUNT(*) AS orders, SUM(amount) AS revenue FROM sales GROUP BY region, product ORDER BY region, product;North and South each sold pens and books, so there are four groups, and North's pens are 2 orders worth 150. The column order inside
GROUP BYdoes not change the groups;ORDER BYdecides the output order. Every selected column that is not aggregated must be in the list. See GROUP BY and HAVING.Does
GROUP BYput rows with a NULL key into one group or a group each?SQLCREATE TABLE employees (name TEXT, dept TEXT); INSERT INTO employees VALUES ('Asha', 'Sales'), ('Ravi', NULL), ('Meera', NULL), ('John', 'Sales'), ('Karan', 'HR'); SELECT COUNT(*) FROM (SELECT dept FROM employees GROUP BY dept) AS t;It prints
3: Sales, HR, and one group for both NULL departments.NULL = NULLis unknown in aWHERE, butGROUP BY,DISTINCTandUNIONtreat NULLs as equal, so they collapse into one group. In a report, label it withCOALESCE(dept, 'No department').
SQL GROUP BY and Aggregate Function Interview Questions
Aggregates, GROUP BY and HAVING, conditional counts, string aggregation and subtotals.
10 questions