ONLY_FULL_GROUP_BY, ROLLUP and GROUP_CONCAT
Why MySQL rejects some GROUP BY queries and accepts others that look the same, how functional dependence decides it, and two MySQL aggregates with sharp edges.
After this lesson you can
- Explain error 1055 and what functional dependence on the GROUP BY means
- Choose between grouping by a key, ANY_VALUE and an aggregate
- Produce subtotals with WITH ROLLUP and tell them apart with GROUPING()
- Avoid GROUP_CONCAT's silent truncation
Before MySQL 5.7.5, SELECT city, name, SUM(x) ... GROUP BY city ran and
returned an arbitrary name from each city. Since
5.7.5 the default sql_mode includes ONLY_FULL_GROUP_BY, and the query
fails with error 1055 instead of silently returning an arbitrary value.
What the rule actually checks
Every expression in the select list, HAVING and ORDER BY must be one
of: in the GROUP BY, inside an aggregate, or functionally dependent on
the GROUP BY columns, meaning MySQL can prove it has exactly one value per
group. Grouping by a primary key makes every column of that table
dependent. So does an equality in the join that ties a grouped column to
that key.
CREATE TABLE customers (id INT PRIMARY KEY, name VARCHAR(20), city VARCHAR(20));CREATE TABLE orders (id INT PRIMARY KEY, customer_id INT, amount INT);INSERT INTO customers VALUES (1, 'Ann', 'Tbilisi'), (2, 'Bob', 'Batumi'), (3, 'Cy', 'Tbilisi');INSERT INTO orders VALUES (1, 1, 10), (2, 1, 20), (3, 2, 5), (4, 3, 7);-- A helper that runs a statement and records the error instead of stopping.CREATE TABLE attempts (n INT AUTO_INCREMENT PRIMARY KEY, statement TEXT, outcome TEXT);CREATE PROCEDURE try_sql(IN stmt TEXT)BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN GET DIAGNOSTICS CONDITION 1 @err_no = MYSQL_ERRNO, @err_msg = MESSAGE_TEXT; INSERT INTO attempts (statement, outcome) VALUES (stmt, CONCAT('error ', @err_no, ': ', REPLACE(@err_msg, CONCAT(DATABASE(), '.'), ''))); END; SET @sql = stmt; PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s; INSERT INTO attempts (statement, outcome) VALUES (stmt, 'ok');END;CALL try_sql('SELECT c.city, c.name, SUM(o.amount) FROM customers c JOIN orders o ON o.customer_id = c.id GROUP BY c.city');CALL try_sql('SELECT c.id, c.name, c.city, SUM(o.amount) FROM customers c JOIN orders o ON o.customer_id = c.id GROUP BY c.id');CALL try_sql('SELECT o.customer_id, c.name, SUM(o.amount) FROM customers c JOIN orders o ON o.customer_id = c.id GROUP BY o.customer_id');CALL try_sql('SELECT city, ANY_VALUE(name), COUNT(*) FROM customers GROUP BY city'); SELECT n, outcome FROM attempts ORDER BY n;- Attempt 1 fails: many customers share a city, so
namehas no single value percity. - Attempt 2 works: grouping by
c.id, the primary key, determinesnameandcity. - Attempt 3 works too: grouping by
o.customer_iddeterminesc.namebecause the join sayso.customer_id = c.id. - Attempt 4 uses
ANY_VALUE(name), which tells MySQL "any one is fine". It is honest only when you really do not care which value you get.
The fix is almost never to turn the mode off. Group by the key of the
thing you are reporting on, or decide which value you want (MIN, MAX,
the latest by date with a window function).
WITH ROLLUP
GROUP BY a, b WITH ROLLUP adds a subtotal row for each a and a grand
total, with NULL in the rolled-up columns. Because real data can also
contain NULL, use GROUPING(col), which is 1 only on a rollup row.
CREATE TABLE sales (region VARCHAR(10), product VARCHAR(10), amount INT);INSERT INTO sales VALUES ('east', 'pen', 10), ('east', 'ink', 5), ('west', 'pen', 7), ('west', 'pen', 3), ('west', 'pad', 1);SELECT region, product, SUM(amount) AS total, GROUPING(region) AS all_regions, GROUPING(product) AS all_productsFROM salesGROUP BY region, product WITH ROLLUPORDER BY GROUPING(region), region, GROUPING(product), product;GROUP_CONCAT truncates silently
GROUP_CONCAT(expr ORDER BY ... SEPARATOR ...) joins a group's values
into one string, and DISTINCT removes repeats. Its result is capped by
group_concat_max_len, 1024 bytes by default, and anything longer is
cut off with only a warning.
CREATE TABLE words (id INT PRIMARY KEY, w VARCHAR(20));INSERT INTO wordsWITH RECURSIVE seq (n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM seq WHERE n < 200)SELECT n, CONCAT('word', n) FROM seq;SET @default_len = (SELECT LENGTH(GROUP_CONCAT(w ORDER BY id)) FROM words); SET SESSION group_concat_max_len = 100000;SELECT 1024 AS default_limit, @default_len AS length_at_default, LENGTH(GROUP_CONCAT(w ORDER BY id)) AS length_after_raising, COUNT(*) AS wordsFROM words;The full list is 1491 bytes; the default cap returned exactly 1024. Raise
the session variable before running a query that builds long lists, or
aggregate into JSON with JSON_ARRAYAGG instead.
MySQL also lets HAVING refer to a select-list alias (HAVING total > 50),
which standard SQL does not; the exercise uses it.
Common mistakes
- Grouping by a name instead of a key. Two customers called "Ann" become one row. Group by the id and select the name; it is functionally dependent.
- Switching off
ONLY_FULL_GROUP_BYto make error 1055 go away. You get an arbitrary value back and no warning. - Treating
NULLin a rollup row as the total marker. UseGROUPING(); a realNULLgroup looks the same. - Building long lists with
GROUP_CONCATat the default limit. The 1024-byte cut in the example produced no error.
What interviewers ask
- What does
ONLY_FULL_GROUP_BYdo? Rejects non-aggregated select expressions that are not functionally dependent on theGROUP BY. - Why does
GROUP BY customers.idlet you selectcustomers.name? is the primary key, so each group has exactly one .
Exercise · Pro
Customer summary with a product list
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade