cockroachdb / cockroachdb/cockroach
sql: GROUP BY ROW(...) is incorrectly flattened; GROUP BY ROW() becomes scalar aggregation
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
## Describe the problem
CockroachDB treats `ROW(...)` in a `GROUP BY` item exactly like a parenthesized
expression list `(...)`: it flattens the tuple's elements into individual
grouping columns. PostgreSQL only flattens *syntactic* paren lists — `ROW(...)`
constructs a single composite scalar value, and grouping by it groups by that
one value.
For the empty case, `ROW()` is a zero-field row constructor,
so flattening turns it into *zero* grouping columns, i.e. the empty grouping set
— which makes the query a scalar aggregation. In PostgreSQL `GROUP BY ROW()`
groups by a constant scalar, so it emits no rows when the input is empty, while
`GROUP BY ()` (the real empty grouping set) emits one.
This produces **silently incorrect results**: a query that should return zero
rows returns one.
The non-empty case diverges too, by accepting queries PostgreSQL rejects:
`GROUP BY ROW(a, b)` should make `a` and `b` individually unavailable to the
select list (only the composite is grouped), but CockroachDB permits them.
## To Reproduce
```sql
CREATE TABLE t (a INT, b INT);
INSERT INTO t VALUES (1, 10), (1, 20), (2, 30);
```
| Query | PostgreSQL 19devel | CockroachDB |
| --- | --- | --- |
| `SELECT count(*) FROM t WHERE a > 100 GROUP BY ROW();` | 0 rows | **1 row (`0`)** |
| `SELECT count(*) FROM t WHERE a > 100 GROUP BY ();` | 1 row (`0`) | 1 row (`0`) — matches |
| `SELECT count(*) FROM t WHERE false GROUP BY ROW() HAVING count(*) = 0;` | 0 rows | **1 row (`0`)** |
| `SELECT a FROM t GROUP BY ROW(a, b);` | `ERROR 42803: column "t.a" must appear in the GROUP BY clause…` | **3 rows (`1, 1, 2`)** |
| `SELECT b FROM t GROUP BY ROW(a, b);` | `ERROR 42803: column "t.b" must appear in the GROUP BY clause…` | **3 rows (`10, 20, 30`)** |
| `SELECT a FROM t GROUP BY ROW(a);` | `ERROR 42803: column "t.a" must appear in the GROUP BY clause…` | **2 rows (`1, 2`)** |
| `SELECT a FROM t GROUP BY (a, b);` | 3 rows | 3 rows — matches (paren list *should* flatten) |
The last row is the important contrast: flattening is correct for `(a, b)` and
incorrect for `ROW(a, b)`.
## Expected behavior
`ROW(...)` in a `GROUP BY` item should be treated as a single scalar grouping
key of composite type, not flattened:
* `GROUP BY ROW()` groups by a constant empty-tuple scalar — one output group
when the input is non-empty, **zero output rows when the input is empty**.
It should not be equivalent to `GROUP BY ()`.
* `GROUP BY ROW(a, b)` groups by the composite value; referencing `a` or `b`
bare in the select list should raise `42803`.
* `GROUP BY (a, b)` should keep flattening, as today.
## Environment
* CockroachDB v26.4.0-alpha (master, August 2026), single-node `cockroach demo`
* Compared against PostgreSQL 19devel
Jira issue: CRDB-66585
Contributor guide
Research direction
Start by running the reproduction queries in a single-node CockroachDB demo and compare the results with the expected PostgreSQL behavior. Trace how GROUP BY items distinguish ROW(...) from parenthesized expression lists, then add regression coverage for empty and non-empty ROW constructors; done means the listed results and 42803 errors match PostgreSQL while GROUP BY (a, b) still flattens.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- sql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100