cockroachdb / cockroachdb/cockroach

sql: GROUP BY ROW(...) is incorrectly flattened; GROUP BY ROW() becomes scalar aggregation

Open
#173,264 1 comment 0 reactions 0 assignees View on GitHub
A-sql-pgcompat A-sql-semantics C-bug T-sql-queries
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.