SQL • AGGREGATION AND GROUPING

Avoiding Non-Aggregated Columns — Avoid selecting non-aggregated columns not in GROUP BY (conceptual)

Understanding why every selected column must be aggregated or grouped to produce deterministic query results.

Historical Context & Motivation

The question of how to handle non-aggregated columns in grouped queries has been a persistent source of debate since the earliest days of relational databases. When E. F. Codd formalized the relational model in 1970, he described operations over sets of tuples, and his algebra made clear that grouping and aggregation reduce many rows into fewer rows. The challenge arises when a query's SELECT list references a column that is neither part of the grouping key nor wrapped in an aggregate function — at that point, the database engine must decide which value from the group to return, and the answer is fundamentally indeterminate. Over the decades, SQL standards and database vendors have taken sharply different stances on whether to allow, warn about, or outright reject such queries.

1970
Codd's Relational Model
E. F. Codd publishes "A Relational Model of Data for Large Shared Data Banks," establishing the theoretical foundation for relational algebra. Grouping is defined as a set-level operation, implying that any column not part of the grouping key must be summarized by an aggregate function.
1986
SQL-86 (ANSI Standard)
The first ANSI SQL standard codifies the GROUP BY clause but leaves some ambiguities around column references. Early implementations vary in enforcement.
1992
SQL-92 Strict Semantics
SQL-92 tightens the rules: every column in the SELECT list of a grouped query must either appear in the GROUP BY clause or be an argument to an aggregate function. Databases like PostgreSQL, Oracle, and SQL Server enforce this strictly.
2004
MySQL's Permissive Default
MySQL allows non-aggregated columns not in GROUP BY, returning an arbitrary row's value. This behavior is convenient but produces nondeterministic results, becoming one of MySQL's most criticized 'features.'
2016
MySQL 5.7 Enables ONLY_FULL_GROUP_BY
MySQL 5.7 enables the ONLY_FULL_GROUP_BY SQL mode by default, finally aligning with the standard. Queries that reference non-aggregated, non-grouped columns are now rejected unless functional dependencies can be proven.

The historical progression reveals a convergence toward strict enforcement. The central question this lesson addresses is straightforward yet frequently misunderstood: when a query groups rows together, which single value should represent a column that was not part of the grouping criteria and was not aggregated? As we will see, the answer is that no principled choice exists, which is precisely why the SQL standard forbids it.

Core Principles & Definitions

To reason clearly about this topic, we need to establish a precise vocabulary. A grouped query is any SELECT statement containing a GROUP BY clause. Once GROUP BY is present, the database engine partitions the result set into groups — each group consisting of all rows that share the same combination of values in the grouping columns. An aggregate function (such as COUNT, SUM, AVG, MIN, MAX) collapses an entire group into a single scalar value. The rule is that every expression in the SELECT list must produce exactly one value per group. Grouping columns satisfy this naturally because they are constant within each group; aggregate functions satisfy it by design. A non-aggregated column not in GROUP BY may have multiple distinct values within a group, making it impossible to select a single deterministic value.

1

Grouping Column

A column listed in the GROUP BY clause. Within each group, this column has a single, well-defined value. Safe to include in SELECT.
2

Aggregate Expression

An expression that applies an aggregate function (SUM, COUNT, AVG, MIN, MAX) to collapse all rows in a group into one scalar. Safe to include in SELECT.
3

Non-Aggregated, Non-Grouped Column

A column in SELECT that is neither in GROUP BY nor inside an aggregate function. May have multiple distinct values within a group, producing indeterminate results. Forbidden by the SQL standard.
4

Functional Dependency

If column A uniquely determines column B (e.g., primary key → other columns), then B is functionally dependent on A. Some engines (MySQL 5.7+) allow B in SELECT if A is in GROUP BY.
KEY TAKEAWAY
Think of GROUP BY as sorting students into study groups by their major. If you then ask, "What is the student's name for each group?" the question is ill-defined — each group has many students with different names. You could ask for the count of students (an aggregate) or report the major itself (a grouping column), but asking for a single name without specifying which one is logically ambiguous. That ambiguity is exactly what the rule prevents.

Visual Explanation — From Rows to Groups

The following diagram illustrates the core problem. On the left, we see a table of orders with three columns: department, employee, and amount. When we GROUP BY department, rows are collapsed into groups. The department column has one value per group (it is the grouping key). The SUM(amount) aggregate reduces each group to one number. But the employee column has multiple distinct values within each group — selecting it produces an undefined result.

The diagram shows how rows are partitioned into groups by department. The SUM(amount) aggregate produces one value per group, but the employee column has multiple candidates within each group, yielding a '???' indeterminate result.

As the diagram makes clear, the Sales group contains three employees — Alice, Bob, and Carol — each with different amounts. Selecting employee alongside GROUP BY department forces the engine to pick one of those three names arbitrarily. Under MySQL's old permissive mode, you might get Alice on one execution and Bob on another, depending on the physical storage order and the query plan. This is not a minor inconvenience — it means your application's behavior is nondeterministic, which violates a fundamental contract of a relational database: the same query on the same data should always yield the same result.

How the Rule Works — The Single-Value Constraint

The mechanism behind the rule can be expressed formally. When a GROUP BY clause is present, the query engine logically partitions the relation R into equivalence classes based on the grouping columns. Each equivalence class (group) is then collapsed into a single output row. For any expression e in the SELECT list, the engine must be able to prove that e evaluates to exactly one value for every possible group. This is the single-value constraint.

SINGLE-VALUE CONSTRAINT
∀ group g ∈ Partition(R, G): |π_e(g)| = 1
Where R is the relation, G is the set of grouping columns, πe is the projection of expression e over group g. The constraint requires that the projection yields exactly one distinct value per group.

There are exactly three ways to satisfy this constraint for a given expression e in the SELECT list:

  1. e is a grouping column — by definition, all rows in a group share the same value for every column in G, so |πe(g)| = 1.
  2. e is an aggregate expression — functions like SUM, COUNT, AVG, MIN, MAX are defined to return exactly one scalar per group.
  3. e is functionally dependent on G — if the grouping columns include a candidate key of a table, then all other columns of that table are functionally determined. For example, if you GROUP BY student_id and student_id is the primary key of the students table, then student_name is uniquely determined and safe to select.
FUNCTIONAL DEPENDENCY EXCEPTION
G → e ⟹ |π_e(g)| = 1 for all g
If the grouping columns G functionally determine expression e, then e is guaranteed to have one value per group. SQL:1999 and MySQL 5.7+ recognize this exception.
💡 Compiler Analogy
You can think of the SQL standard's rule as a static type check. Just as a compiler rejects an expression that cannot be guaranteed type-safe at compile time, a SQL engine with strict GROUP BY enforcement rejects a query whose SELECT list cannot be statically proven to satisfy the single-value constraint. The engine does not run the query and then hope for the best — it rejects the query before execution.

How Different Databases Handle This Rule

Not all database management systems enforce the single-value constraint in the same way. Understanding these differences is essential for writing portable, correct SQL across different environments. The spectrum ranges from strict enforcement (reject the query outright) to permissive behavior (silently return an arbitrary value). The following diagram and table classify the major DBMS products.

The enforcement spectrum ranges from PostgreSQL, SQL Server, and Oracle (which always reject non-aggregated, non-grouped columns at parse time) through MySQL 5.7+ (strict by default but aware of functional dependencies) to SQLite (fully permissive, returning an arbitrary value without warning).
GROUP BY enforcement behavior across major database systems
DBMSDefault BehaviorError Code / NoteFunctional Dependency Aware?
PostgreSQLStrict — always rejectsERROR 42803Yes (since 9.1 for primary keys)
SQL ServerStrict — always rejectsMsg 8120No
OracleStrict — always rejectsORA-00979No
MySQL 5.7+Strict by default (ONLY_FULL_GROUP_BY)ERROR 1055Yes
SQLitePermissive — returns arbitrary valueNo error; silent nondeterminismNo
MySQL < 5.7Permissive by defaultNo error unless ONLY_FULL_GROUP_BY setNo

The practical implication is clear: if you write SQL that will run on multiple database backends — a common scenario in modern web development with ORM frameworks — you should always write standard-compliant GROUP BY queries. Even if your current database is permissive, relying on that permissiveness creates hidden bugs that surface only when migrating to a stricter engine.

Worked Example — Diagnosing and Fixing a Query

Consider a students table with columns student_id (PK), name, major, and gpa. A developer writes the following query to find the average GPA per major, along with a student name:

Fix a Non-Aggregated Column Error
1
Step 1 — Identify the Problematic QueryThe original query is: SELECT major, name, AVG(gpa) AS avg_gpa FROM students GROUP BY major; The column name appears in the SELECT list but is neither in the GROUP BY clause nor inside an aggregate function.
Violation: name is a non-aggregated, non-grouped column.
2
Step 2 — Understand the AmbiguityEach major group contains multiple students with different names. For example, the 'Computer Science' group might contain Alice (GPA 3.8), Bob (GPA 3.5), and Carol (GPA 3.9). The AVG(gpa) correctly collapses to 3.733, but which name should the engine return? There is no principled answer.
3
Step 3 — Determine the Actual IntentAsk: what does the developer actually want? There are typically three possibilities. (A) They want just the average GPA per major, and name was included accidentally. (B) They want the name of the student with the highest GPA in each major. (C) They want every student's name alongside their major's average GPA.
4
Step 4A — Fix: Remove the ColumnIf the intent is simply the average GPA per major, remove name from the SELECT list:
SELECT major, AVG(gpa) AS avg_gpa FROM students GROUP BY major;
5
Step 4B — Fix: Use a Subquery or Window FunctionIf the intent is to find the top student per major, use a window function or correlated subquery. For example, using a CTE with ROW_NUMBER():
WITH ranked AS (SELECT major, name, gpa, ROW_NUMBER() OVER (PARTITION BY major ORDER BY gpa DESC) AS rn FROM students) SELECT major, name, gpa FROM ranked WHERE rn = 1;
6
Step 4C — Fix: Use a Window Function Without GROUP BYIf the intent is to show every student alongside their major's average, use a window function instead of GROUP BY:
SELECT major, name, AVG(gpa) OVER (PARTITION BY major) AS major_avg_gpa FROM students;
🔧 General Repair Strategy
When you encounter a non-aggregated column error, follow this checklist: (1) Is the column needed at all? Remove it. (2) Should it be aggregated? Wrap it in MIN(), MAX(), or another appropriate function. (3) Should it be added to GROUP BY? Add it, but understand this changes the granularity of your result. (4) Do you need row-level detail alongside group-level aggregates? Switch to a window function.

Common Pitfalls and Their Corrections

Even experienced developers fall into recurring patterns that violate the non-aggregated column rule. The following table catalogs the most common pitfalls, explains why each is problematic, and provides the standard correction. Understanding these patterns builds the intuition needed to write correct grouped queries instinctively.

Common pitfalls when using GROUP BY with non-aggregated columns
Pitfall PatternWhy It's WrongCorrect Approach
Selecting a detail column (e.g., email) alongside GROUP BY on a non-key columnMultiple distinct emails may exist per group; the engine cannot choose one deterministically.Remove email from SELECT, or use a window function, or aggregate with MIN(email) / MAX(email) if an arbitrary but consistent choice is acceptable.
Using SELECT * with GROUP BYSELECT * includes every column in the table; most will not be in GROUP BY or aggregated.Explicitly list only the grouping columns and aggregate expressions. Never use SELECT * with GROUP BY.
Adding a column to GROUP BY to "fix" the errorAdding a high-cardinality column to GROUP BY creates more groups than intended, often producing one row per original row — defeating the purpose of grouping.Only add a column to GROUP BY if it represents a meaningful grouping dimension. Otherwise, aggregate it or use a window function.
Relying on MySQL's old permissive behaviorResults are nondeterministic. Different executions or index changes may return different rows' values silently.Enable ONLY_FULL_GROUP_BY. Rewrite queries to be standard-compliant.
Assuming ORDER BY controls which row's value is chosenORDER BY sorts the final result set; it does not determine which value from a group is selected for a non-aggregated column.Use an explicit aggregate (e.g., MIN, MAX) or a window function with ORDER BY in its OVER clause.
KEY TAKEAWAY
The most insidious pitfall is not the error itself — it's the absence of an error. In permissive databases, your query runs without complaint but returns subtly wrong results. This is analogous to a C program that compiles with undefined behavior: it might appear to work in your test environment, but the behavior can change with different data distributions, query optimizers, or database versions. Always prefer well-defined, deterministic queries over convenient but nondeterministic ones.

Connection to Window Functions and Advanced Grouping

The non-aggregated column problem is closely related to a broader set of SQL features that emerged to address its limitations. Window functions (introduced in SQL:2003) represent the most elegant solution to the fundamental tension between row-level detail and group-level aggregation. Where GROUP BY forces you to choose between detail and summary, window functions let you compute aggregates without collapsing rows. Additionally, advanced grouping extensions like GROUPING SETS, ROLLUP, and CUBE allow multiple grouping levels in a single query, each of which must still satisfy the single-value constraint.

GROUP BY vs. Window Functions for aggregation
FeatureGROUP BY (Standard)Window Functions
Row collapseYes — multiple rows become one per groupNo — all original rows are preserved
Detail + aggregate?Not possible in a single query levelYes — aggregate computed alongside each row
Non-aggregated column riskHigh — any detail column must be aggregated or groupedNone — no grouping occurs, so all columns remain accessible
SyntaxSELECT col, AGG(col2) ... GROUP BY colSELECT col, col2, AGG(col2) OVER (PARTITION BY col)
Use caseSummary reports, totals, countsRankings, running totals, row-level enrichment with aggregates

Looking ahead, understanding the single-value constraint prepares you for more advanced topics in query optimization and database theory. The concept of functional dependencies — central to database normalization — plays a direct role in determining which columns can safely appear in a grouped query. As you study normalization, you will find that well-normalized schemas naturally reduce the frequency of this error because they minimize redundant data storage, making it more likely that the grouping key functionally determines all other columns in the relevant table. The interplay between schema design and query correctness is one of the most rewarding areas of database theory to explore.

Practice Problems

PROBLEM 1CONCEPTUAL
Explain in your own words why the following query is problematic: SELECT department, employee_name, COUNT(*) FROM employees GROUP BY department; What fundamental property of the relational model does it violate?
PROBLEM 2BASIC CALCULATION
Given a table orders(order_id, customer_id, product, amount), rewrite the following query so that it is standard-compliant: SELECT customer_id, product, SUM(amount) FROM orders GROUP BY customer_id;
PROBLEM 3INTERMEDIATE
A developer adds order_id to the GROUP BY clause to silence the error: SELECT customer_id, order_id, SUM(amount) FROM orders GROUP BY customer_id, order_id; The query now runs without error. Explain why this 'fix' is likely incorrect and what semantic change it introduces.
PROBLEM 4APPLIED
You are building a dashboard for an e-commerce platform. For each product category, you need to display: (a) the total revenue, (b) the number of orders, and (c) the name of the best-selling product. The table is sales(sale_id, category, product_name, revenue). Write a standard-compliant query that returns all three pieces of information in one result set. Explain why a simple GROUP BY alone cannot achieve this.
PROBLEM 5CRITICAL THINKING
MySQL 5.7+ with ONLY_FULL_GROUP_BY enabled allows: SELECT student_id, name, major FROM students GROUP BY student_id; even though name and major are not in GROUP BY. SQL Server would reject this same query. Analyze: (a) Why does MySQL allow it? (b) Under what schema conditions is MySQL's reasoning sound? (c) Can you construct a scenario where MySQL's functional dependency detection might give a false sense of security?

Lesson Summary

When a SQL query uses GROUP BY, every expression in the SELECT list must satisfy the single-value constraint: it must produce exactly one deterministic value per group. This is achieved by ensuring that each selected column is either a grouping column (listed in GROUP BY), an aggregate expression (wrapped in SUM, COUNT, AVG, MIN, MAX, etc.), or functionally dependent on the grouping columns (supported by some engines like MySQL 5.7+ and PostgreSQL 9.1+). Selecting a non-aggregated, non-grouped column is forbidden by the SQL standard because the engine cannot deterministically choose among the multiple values that may exist within a group.

Databases like PostgreSQL, SQL Server, and Oracle enforce this rule strictly, while SQLite and older MySQL versions are permissive, silently returning arbitrary values. When you need row-level detail alongside group-level aggregates, window functions provide the standard-compliant solution by computing aggregates without collapsing rows. Always write queries that conform to the SQL standard to ensure portability, determinism, and correctness across all database platforms.

Varsity Tutors • SQL • Avoiding Non-Aggregated Columns