#8749·turso

An expression index under a LEFT JOIN reads NULL, so a GROUP BY gives one row

Author: RyanLin5967Created Sep 5, 2026Updated Sep 18, 2026
Labelsbugcompatibilityoptimizercorrectnessindexesjoins

Currently, a GROUP BY or ORDER BY on an indexed expression reads it as NULL for every row of a LEFT JOIN, so the groups collapse into one row with a NULL key. An inner join over the same index is unaffected.

sql
CREATE TABLE t(id INTEGER PRIMARY KEY, x TEXT);
CREATE TABLE u(k INTEGER);
CREATE TABLE v(g TEXT);
INSERT INTO t VALUES(1,'a'),(2,'b');
INSERT INTO u VALUES(1),(2);
CREATE INDEX i ON t(lower(x));

SELECT lower(t.x), count(*) FROM t LEFT JOIN u ON u.k=t.id GROUP BY lower(t.x);
-- Turso:  |2
-- SQLite: a|1, then b|1
-- the two groups merge into one row whose key comes back empty, carrying the whole count

SELECT quote(lower(t.x)) FROM t LEFT JOIN u ON u.k=t.id ORDER BY lower(t.x);
-- Turso:  NULL, then NULL
-- SQLite: 'a', then 'b'
-- ordering on the same expression reads it as NULL for every row, with no grouping at all

INSERT INTO v SELECT lower(t.x) FROM t LEFT JOIN u ON u.k=t.id GROUP BY lower(t.x);
SELECT count(*), quote(max(g)) FROM v;
-- Turso:  1|NULL
-- SQLite: 2|'b'
-- INSERT ... SELECT stores the collapsed row, so the NULL lands in a table

SELECT lower(t.x), count(*) FROM t INDEXED BY i JOIN u ON u.k=t.id GROUP BY lower(t.x);
-- both a|1, then b|1, so an inner join driven through the same index is unaffected

DROP INDEX i;
SELECT lower(t.x), count(*) FROM t LEFT JOIN u ON u.k=t.id GROUP BY lower(t.x);
-- both a|1, then b|1, so dropping the expression index restores the two groups

tested on main & SQLite 3.50.4