Repository: ibis-project/ibis

Issue:
value_counts: sqlalchemy.exc.ProgrammingError: (duckdb.BinderException)
### What happened?

I'm trying to see if I can get the support for Ibis working in [hvPlot](https://hvplot.holoviz.org/) and [HoloViews](https://holoviews.org/reference/index.html). Right now for histograms.

Running some code witht the duckDB backend I get a `sqlalchemy.exc.ProgrammingError: (duckdb.BinderException) Binder Error`.

```python
import duckdb
import ibis
import pandas as pd

from pathlib import Path

DUCKDB_PATH = "DuckDB1.db"

if not Path(DUCKDB_PATH).exists():
    pandas_df = pd.DataFrame(
        {
            "forecast": [90, 160, 125, 150, 141, 141, 120],
        },
    )
    duckdb_con = duckdb.connect(DUCKDB_PATH)
    duckdb_con.execute("CREATE TABLE df AS SELECT * FROM pandas_df")

ibis.options.sql.default_limit = None

db = ibis.duckdb.connect(DUCKDB_PATH)

df = db.table("df")
buckets = [90.0, 113.33333333333333, 136.66666666666666, 160.0]
df["forecast"].bucket(buckets).value_counts().execute()
```

### What version of ibis are you using?

3.2.0

### What backend(s) are you using, if any?

DuckDB==0.6.0

### Relevant log output

```sh
$ python script3.py
C:\repos\private\hvplot\.venv\lib\site-packages\ibis\backends\postgres\registry.py:164: UserWarning: locale specific date formats (%c, %x, %X) are not yet implemented for Windows
  warnings.warn(
C:\repos\private\hvplot\.venv\lib\site-packages\duckdb_engine\__init__.py:229: DuckDBEngineWarning: duckdb-engine doesn't yet support reflection on indices
  warnings.warn(
Traceback (most recent call last):
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\base.py", line 1900, in _execute_context
    self.dialect.do_execute(
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\default.py", line 736, in do_execute
    cursor.execute(statement, parameters)
  File "C:\repos\private\hvplot\.venv\lib\site-packages\duckdb_engine\__init__.py", line 120, in execute
    self.c.execute(statement, parameters)
duckdb.BinderException: Binder Error: column "forecast" must appear in the GROUP BY clause or must be part of an aggregate function.
Either add it to the GROUP BY list, or use "ANY_VALUE(forecast)" if the exact value of "forecast" is not important.
LINE 1: ...ECT CASE WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast < CAST(? AS...
                                                  ^

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\repos\private\hvplot\script3.py", line 28, in <module>
    df["forecast"].bucket(buckets).value_counts().execute()
  File "C:\repos\private\hvplot\.venv\lib\site-packages\ibis\expr\types\core.py", line 291, in execute
    return self._find_backend(use_default=True).execute(
  File "C:\repos\private\hvplot\.venv\lib\site-packages\ibis\backends\base\sql\__init__.py", line 191, in execute
    with self._safe_raw_sql(sql, **kwargs) as cursor:
  File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python39_64\lib\contextlib.py", line 119, in __enter__
    return next(self.gen)
  File "C:\repos\private\hvplot\.venv\lib\site-packages\ibis\backends\base\sql\alchemy\__init__.py", line 124, in _safe_raw_sql
    yield con.execute(*args, **kwargs)
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\base.py", line 1380, in execute
    return meth(self, multiparams, params, _EMPTY_EXECUTION_OPTS)
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\sql\elements.py", line 334, in _execute_on_connection
    return connection._execute_clauseelement(
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\base.py", line 1572, in _execute_clauseelement
    ret = self._execute_context(
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\base.py", line 1943, in _execute_context
    self._handle_dbapi_exception(
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\base.py", line 2124, in _handle_dbapi_exception
    util.raise_(
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\util\compat.py", line 210, in raise_
    raise exception
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\base.py", line 1900, in _execute_context
    self.dialect.do_execute(
  File "C:\repos\private\hvplot\.venv\lib\site-packages\sqlalchemy\engine\default.py", line 736, in do_execute
    cursor.execute(statement, parameters)
  File "C:\repos\private\hvplot\.venv\lib\site-packages\duckdb_engine\__init__.py", line 120, in execute
    self.c.execute(statement, parameters)
sqlalchemy.exc.ProgrammingError: (duckdb.BinderException) Binder Error: column "forecast" must appear in the GROUP BY clause or must be part of an aggregate function.
Either add it to the GROUP BY list, or use "ANY_VALUE(forecast)" if the exact value of "forecast" is not important.
LINE 1: ...ECT CASE WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast < CAST(? AS...
                                                  ^
[SQL: SELECT CASE WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast < CAST(? AS FLOAT(53))) THEN CAST(? AS SMALLINT) WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast < CAST(? 
AS FLOAT(53))) THEN CAST(? AS SMALLINT) WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast <= CAST(? AS FLOAT(53))) THEN CAST(? AS SMALLINT) ELSE CAST(NULL AS SMALLINT) END AS unnamed, 
count(?) AS count
FROM df AS t0 GROUP BY CASE WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast < CAST(? AS FLOAT(53))) THEN CAST(? AS SMALLINT) WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast < CAST(? AS FLOAT(53))) THEN CAST(? AS SMALLINT) WHEN (CAST(? AS FLOAT(53)) <= t0.forecast AND t0.forecast <= CAST(? AS FLOAT(53))) THEN CAST(? AS SMALLINT) ELSE CAST(NULL AS SMALLINT) END]  
[parameters: (90.0, 113.33333333333333, 0, 113.33333333333333, 136.66666666666666, 1, 136.66666666666666, 160.0, 2, '*', 90.0, 113.33333333333333, 0, 113.33333333333333, 136.66666666666666, 1, 136.66666666666666, 160.0, 2)]
(Background on this error at: https://sqlalche.me/e/14/f405)
```


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
Hey @MarcSkovMadsen thanks for the report.

It looks like this might an issue with duckdb.

Repeating the grouping key expression in the `GROUP BY` clause should be valid, but it looks like DucKDB is conservative in its determination of whether columns in the grouping key expression are in the select set.

We'll report this upstream and for now you can do this instead:

```python
df = df.mutate(forceast=df.forecast.bucket(buckets)).forecast.value_counts()
```
Thanks a lot. Also, for providing a work around. Then I can move forward bringing Ibis and hvPlot, HoloViews, Datashader etc. closer to each.
Hi. First off, thank you very much to all the contributors to the Ibis Project! Your work is greatly appreciated. I am fairly new to the Ibis Project and was working through the Ibis Tutorial (replicated with the DuckDB backend and a DuckDB database file) and I am also experiencing the DuckDB backend `BinderException` error. 

Not sure if it is helpful, but I have recreated a portion of the Ibis Tutorial in Google Colab for easy reproduction of the error. 

https://colab.research.google.com/drive/1m0n4NSpWWq2EB-xw1MPH1gvdAhgUHXTQ?usp=sharing

Thanks again!

Resolving patch:
diff --git a/ibis/backends/base/sql/alchemy/query_builder.py b/ibis/backends/base/sql/alchemy/query_builder.py
--- a/ibis/backends/base/sql/alchemy/query_builder.py
+++ b/ibis/backends/base/sql/alchemy/query_builder.py
@@ -237,13 +237,18 @@ def _add_select(self, table_set):
 
     def _add_group_by(self, fragment):
         # GROUP BY and HAVING
-        if not len(self.group_by):
+        nkeys = len(self.group_by)
+        if not nkeys:
             return fragment
 
-        group_keys = [self._translate(arg) for arg in self.group_by]
+        if self.context.compiler.supports_indexed_grouping_keys:
+            group_keys = map(sa.literal_column, map(str, range(1, nkeys + 1)))
+        else:
+            group_keys = map(self._translate, self.group_by)
+
         fragment = fragment.group_by(*group_keys)
 
-        if len(self.having) > 0:
+        if self.having:
             having_args = [self._translate(arg) for arg in self.having]
             having_clause = functools.reduce(sql.and_, having_args)
             fragment = fragment.having(having_clause)
@@ -251,7 +256,7 @@ def _add_group_by(self, fragment):
         return fragment
 
     def _add_where(self, fragment):
-        if not len(self.where):
+        if not self.where:
             return fragment
 
         args = [self._translate(pred, permit_subquery=True) for pred in self.where]
@@ -259,7 +264,7 @@ def _add_where(self, fragment):
         return fragment.where(clause)
 
     def _add_order_by(self, fragment):
-        if not len(self.order_by):
+        if not self.order_by:
             return fragment
 
         clauses = []
@@ -344,6 +349,8 @@ class AlchemyCompiler(Compiler):
     intersect_class = AlchemyIntersection
     difference_class = AlchemyDifference
 
+    supports_indexed_grouping_keys = True
+
     @classmethod
     def to_sql(cls, expr, context=None, params=None, exists=False):
         if context is None:
diff --git a/ibis/backends/clickhouse/compiler/relations.py b/ibis/backends/clickhouse/compiler/relations.py
--- a/ibis/backends/clickhouse/compiler/relations.py
+++ b/ibis/backends/clickhouse/compiler/relations.py
@@ -78,7 +78,7 @@ def _aggregation(op: ops.Aggregation, *, table, **kw):
     sel = sg.select(*selections).from_(table)
 
     if by:
-        sel = sel.group_by(*by, dialect="clickhouse")
+        sel = sel.group_by(*map(str, range(1, len(by) + 1)), dialect="clickhouse")
 
     if predicates := op.predicates:
         sel = sel.where(*map(tr_val, predicates), dialect="clickhouse")
diff --git a/ibis/backends/mssql/compiler.py b/ibis/backends/mssql/compiler.py
--- a/ibis/backends/mssql/compiler.py
+++ b/ibis/backends/mssql/compiler.py
@@ -28,3 +28,5 @@ class MsSqlExprTranslator(AlchemyExprTranslator):
 
 class MsSqlCompiler(AlchemyCompiler):
     translator_class = MsSqlExprTranslator
+
+    supports_indexed_grouping_keys = False


Test patch:
diff --git a/ibis/backends/clickhouse/tests/snapshots/test_select/test_complex_array_expr_projection/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_select/test_complex_array_expr_projection/out.sql
--- a/ibis/backends/clickhouse/tests/snapshots/test_select/test_complex_array_expr_projection/out.sql
+++ b/ibis/backends/clickhouse/tests/snapshots/test_select/test_complex_array_expr_projection/out.sql
@@ -6,5 +6,5 @@ FROM (
     COUNT(*) AS count
   FROM functional_alltypes AS t0
   GROUP BY
-    t0.string_col
+    1
 ) AS t1
\ No newline at end of file
diff --git a/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out1.sql b/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out1.sql
--- a/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out1.sql
+++ b/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out1.sql
@@ -9,4 +9,4 @@ FROM t0 AS t0
 WHERE
   t0.value = 42
 GROUP BY
-  t0.key
\ No newline at end of file
+  1
\ No newline at end of file
diff --git a/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out2.sql b/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out2.sql
--- a/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out2.sql
+++ b/ibis/backends/clickhouse/tests/snapshots/test_select/test_named_from_filter_groupby/out2.sql
@@ -9,4 +9,4 @@ FROM t0 AS t0
 WHERE
   t0.value = 42
 GROUP BY
-  t0.key
\ No newline at end of file
+  1
\ No newline at end of file
diff --git a/ibis/backends/clickhouse/tests/snapshots/test_select/test_table_column_unbox/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_select/test_table_column_unbox/out.sql
--- a/ibis/backends/clickhouse/tests/snapshots/test_select/test_table_column_unbox/out.sql
+++ b/ibis/backends/clickhouse/tests/snapshots/test_select/test_table_column_unbox/out.sql
@@ -8,5 +8,5 @@ FROM (
   WHERE
     t0.int_col > 0
   GROUP BY
-    t0.string_col
+    1
 ) AS t1
\ No newline at end of file
diff --git a/ibis/backends/clickhouse/tests/snapshots/test_select/test_where_with_timestamp/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_select/test_where_with_timestamp/out.sql
--- a/ibis/backends/clickhouse/tests/snapshots/test_select/test_where_with_timestamp/out.sql
+++ b/ibis/backends/clickhouse/tests/snapshots/test_select/test_where_with_timestamp/out.sql
@@ -3,4 +3,4 @@ SELECT
   minIf(t0.ts, t0.search_level = 1) AS min_date
 FROM t AS t0
 GROUP BY
-  t0.uuid
\ No newline at end of file
+  1
\ No newline at end of file
diff --git a/ibis/backends/postgres/tests/snapshots/test_functions/test_union_cte/False/out.sql b/ibis/backends/postgres/tests/snapshots/test_functions/test_union_cte/False/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/postgres/tests/snapshots/test_functions/test_union_cte/False/out.sql
@@ -0,0 +1 @@
+WITH anon_1 AS (SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric FROM functional_alltypes AS t0 GROUP BY 1), anon_2 AS (SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric FROM functional_alltypes AS t0 GROUP BY 1), anon_3 AS (SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric FROM functional_alltypes AS t0 GROUP BY 1) SELECT anon_1.string_col, anon_1.metric FROM anon_1 UNION ALL SELECT anon_2.string_col, anon_2.metric FROM anon_2 UNION ALL SELECT anon_3.string_col, anon_3.metric FROM anon_3
\ No newline at end of file
diff --git a/ibis/backends/postgres/tests/snapshots/test_functions/test_union_cte/True/out.sql b/ibis/backends/postgres/tests/snapshots/test_functions/test_union_cte/True/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/postgres/tests/snapshots/test_functions/test_union_cte/True/out.sql
@@ -0,0 +1 @@
+WITH anon_1 AS (SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric FROM functional_alltypes AS t0 GROUP BY 1), anon_2 AS (SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric FROM functional_alltypes AS t0 GROUP BY 1), anon_3 AS (SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric FROM functional_alltypes AS t0 GROUP BY 1) SELECT anon_1.string_col, anon_1.metric FROM anon_1 UNION SELECT anon_2.string_col, anon_2.metric FROM anon_2 UNION SELECT anon_3.string_col, anon_3.metric FROM anon_3
\ No newline at end of file
diff --git a/ibis/backends/postgres/tests/test_functions.py b/ibis/backends/postgres/tests/test_functions.py
--- a/ibis/backends/postgres/tests/test_functions.py
+++ b/ibis/backends/postgres/tests/test_functions.py
@@ -526,11 +526,8 @@ def test_category_label(alltypes, df):
     tm.assert_series_equal(result, expected)
 
 
-@pytest.mark.parametrize(
-    ('distinct', 'union'),
-    [(True, 'UNION'), (False, 'UNION ALL')],
-)
-def test_union_cte(alltypes, distinct, union):
+@pytest.mark.parametrize("distinct", [True, False])
+def test_union_cte(alltypes, distinct, snapshot):
     t = alltypes
     expr1 = t.group_by(t.string_col).aggregate(metric=t.double_col.sum())
     expr2 = expr1.view()
@@ -542,22 +539,7 @@ def test_union_cte(alltypes, distinct, union):
             expr.compile().compile(compile_kwargs={'literal_binds': True})
         ).splitlines()
     )
-    expected = (
-        "WITH anon_1 AS "
-        "(SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric "
-        "FROM functional_alltypes AS t0 GROUP BY t0.string_col), "
-        "anon_2 AS "
-        "(SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric "
-        "FROM functional_alltypes AS t0 GROUP BY t0.string_col), "
-        "anon_3 AS "
-        "(SELECT t0.string_col AS string_col, sum(t0.double_col) AS metric "
-        "FROM functional_alltypes AS t0 GROUP BY t0.string_col) "
-        "SELECT anon_1.string_col, anon_1.metric "
-        f"FROM anon_1 {union} SELECT anon_2.string_col, anon_2.metric "
-        f"FROM anon_2 {union} SELECT anon_3.string_col, anon_3.metric "
-        "FROM anon_3"
-    )
-    assert str(result) == expected
+    snapshot.assert_match(result, "out.sql")
 
 
 @pytest.mark.parametrize(
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/bigquery/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/bigquery/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/bigquery/out.sql
@@ -0,0 +1,22 @@
+SELECT
+  CASE t0.`continent`
+    WHEN 'NA'
+    THEN 'North America'
+    WHEN 'SA'
+    THEN 'South America'
+    WHEN 'EU'
+    THEN 'Europe'
+    WHEN 'AF'
+    THEN 'Africa'
+    WHEN 'AS'
+    THEN 'Asia'
+    WHEN 'OC'
+    THEN 'Oceania'
+    WHEN 'AN'
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS `cont`,
+  SUM(t0.`population`) AS `total_pop`
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/clickhouse/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/clickhouse/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/clickhouse/out.sql
@@ -0,0 +1,22 @@
+SELECT
+  CASE t0.continent
+    WHEN 'NA'
+    THEN 'North America'
+    WHEN 'SA'
+    THEN 'South America'
+    WHEN 'EU'
+    THEN 'Europe'
+    WHEN 'AF'
+    THEN 'Africa'
+    WHEN 'AS'
+    THEN 'Asia'
+    WHEN 'OC'
+    THEN 'Oceania'
+    WHEN 'AN'
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS cont,
+  SUM(t0.population) AS total_pop
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/duckdb/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/duckdb/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/duckdb/out.sql
@@ -0,0 +1,36 @@
+SELECT
+  CASE
+    WHEN (
+      t0.continent = CAST('NA' AS TEXT)
+    )
+    THEN CAST('North America' AS TEXT)
+    WHEN (
+      t0.continent = CAST('SA' AS TEXT)
+    )
+    THEN CAST('South America' AS TEXT)
+    WHEN (
+      t0.continent = CAST('EU' AS TEXT)
+    )
+    THEN CAST('Europe' AS TEXT)
+    WHEN (
+      t0.continent = CAST('AF' AS TEXT)
+    )
+    THEN CAST('Africa' AS TEXT)
+    WHEN (
+      t0.continent = CAST('AS' AS TEXT)
+    )
+    THEN CAST('Asia' AS TEXT)
+    WHEN (
+      t0.continent = CAST('OC' AS TEXT)
+    )
+    THEN CAST('Oceania' AS TEXT)
+    WHEN (
+      t0.continent = CAST('AN' AS TEXT)
+    )
+    THEN CAST('Antarctica' AS TEXT)
+    ELSE CAST('Unknown continent' AS TEXT)
+  END AS cont,
+  SUM(t0.population) AS total_pop
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/impala/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/impala/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/impala/out.sql
@@ -0,0 +1,22 @@
+SELECT
+  CASE t0.`continent`
+    WHEN 'NA'
+    THEN 'North America'
+    WHEN 'SA'
+    THEN 'South America'
+    WHEN 'EU'
+    THEN 'Europe'
+    WHEN 'AF'
+    THEN 'Africa'
+    WHEN 'AS'
+    THEN 'Asia'
+    WHEN 'OC'
+    THEN 'Oceania'
+    WHEN 'AN'
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS `cont`,
+  SUM(t0.`population`) AS `total_pop`
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/mysql/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/mysql/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/mysql/out.sql
@@ -0,0 +1,36 @@
+SELECT
+  CASE
+    WHEN (
+      t0.continent = 'NA'
+    )
+    THEN 'North America'
+    WHEN (
+      t0.continent = 'SA'
+    )
+    THEN 'South America'
+    WHEN (
+      t0.continent = 'EU'
+    )
+    THEN 'Europe'
+    WHEN (
+      t0.continent = 'AF'
+    )
+    THEN 'Africa'
+    WHEN (
+      t0.continent = 'AS'
+    )
+    THEN 'Asia'
+    WHEN (
+      t0.continent = 'OC'
+    )
+    THEN 'Oceania'
+    WHEN (
+      t0.continent = 'AN'
+    )
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS cont,
+  SUM(t0.population) AS total_pop
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/postgres/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/postgres/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/postgres/out.sql
@@ -0,0 +1,36 @@
+SELECT
+  CASE
+    WHEN (
+      t0.continent = 'NA'
+    )
+    THEN 'North America'
+    WHEN (
+      t0.continent = 'SA'
+    )
+    THEN 'South America'
+    WHEN (
+      t0.continent = 'EU'
+    )
+    THEN 'Europe'
+    WHEN (
+      t0.continent = 'AF'
+    )
+    THEN 'Africa'
+    WHEN (
+      t0.continent = 'AS'
+    )
+    THEN 'Asia'
+    WHEN (
+      t0.continent = 'OC'
+    )
+    THEN 'Oceania'
+    WHEN (
+      t0.continent = 'AN'
+    )
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS cont,
+  SUM(t0.population) AS total_pop
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/snowflake/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/snowflake/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/snowflake/out.sql
@@ -0,0 +1,36 @@
+SELECT
+  CASE
+    WHEN (
+      t0.continent = 'NA'
+    )
+    THEN 'North America'
+    WHEN (
+      t0.continent = 'SA'
+    )
+    THEN 'South America'
+    WHEN (
+      t0.continent = 'EU'
+    )
+    THEN 'Europe'
+    WHEN (
+      t0.continent = 'AF'
+    )
+    THEN 'Africa'
+    WHEN (
+      t0.continent = 'AS'
+    )
+    THEN 'Asia'
+    WHEN (
+      t0.continent = 'OC'
+    )
+    THEN 'Oceania'
+    WHEN (
+      t0.continent = 'AN'
+    )
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS cont,
+  SUM(t0.population) AS total_pop
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/sqlite/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/sqlite/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/sqlite/out.sql
@@ -0,0 +1,36 @@
+SELECT
+  CASE
+    WHEN (
+      t0.continent = 'NA'
+    )
+    THEN 'North America'
+    WHEN (
+      t0.continent = 'SA'
+    )
+    THEN 'South America'
+    WHEN (
+      t0.continent = 'EU'
+    )
+    THEN 'Europe'
+    WHEN (
+      t0.continent = 'AF'
+    )
+    THEN 'Africa'
+    WHEN (
+      t0.continent = 'AS'
+    )
+    THEN 'Asia'
+    WHEN (
+      t0.continent = 'OC'
+    )
+    THEN 'Oceania'
+    WHEN (
+      t0.continent = 'AN'
+    )
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS cont,
+  SUM(t0.population) AS total_pop
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/trino/out.sql b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/trino/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/backends/tests/snapshots/test_sql/test_group_by_has_index/trino/out.sql
@@ -0,0 +1,36 @@
+SELECT
+  CASE
+    WHEN (
+      t0.continent = 'NA'
+    )
+    THEN 'North America'
+    WHEN (
+      t0.continent = 'SA'
+    )
+    THEN 'South America'
+    WHEN (
+      t0.continent = 'EU'
+    )
+    THEN 'Europe'
+    WHEN (
+      t0.continent = 'AF'
+    )
+    THEN 'Africa'
+    WHEN (
+      t0.continent = 'AS'
+    )
+    THEN 'Asia'
+    WHEN (
+      t0.continent = 'OC'
+    )
+    THEN 'Oceania'
+    WHEN (
+      t0.continent = 'AN'
+    )
+    THEN 'Antarctica'
+    ELSE 'Unknown continent'
+  END AS cont,
+  SUM(t0.population) AS total_pop
+FROM countries AS t0
+GROUP BY
+  1
\ No newline at end of file
diff --git a/ibis/backends/tests/test_sql.py b/ibis/backends/tests/test_sql.py
--- a/ibis/backends/tests/test_sql.py
+++ b/ibis/backends/tests/test_sql.py
@@ -88,3 +88,29 @@ def test_table(con):
 @no_sql_extraction
 def test_literal(backend, expr):
     assert ibis.to_sql(expr, dialect=backend.name())
+
+
+@pytest.mark.never(
+    ["pandas", "dask", "datafusion", "polars", "pyspark"], reason="not SQL"
+)
+@pytest.mark.notyet(["mssql"], reason="sqlglot doesn't support an mssql dialect")
+def test_group_by_has_index(backend, snapshot):
+    countries = ibis.table(
+        dict(continent="string", population="int64"), name="countries"
+    )
+    expr = countries.group_by(
+        cont=(
+            _.continent.case()
+            .when("NA", "North America")
+            .when("SA", "South America")
+            .when("EU", "Europe")
+            .when("AF", "Africa")
+            .when("AS", "Asia")
+            .when("OC", "Oceania")
+            .when("AN", "Antarctica")
+            .else_("Unknown continent")
+            .end()
+        )
+    ).agg(total_pop=_.population.sum())
+    sql = str(ibis.to_sql(expr, dialect=backend.name()))
+    snapshot.assert_match(sql, "out.sql")
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/having_count/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/having_count/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/having_count/out.sql
@@ -0,0 +1,3 @@
+SELECT t0.foo_id, sum(t0.f) AS total 
+FROM star1 AS t0 GROUP BY 1 
+HAVING count(*) > 100
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/having_sum/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/having_sum/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/having_sum/out.sql
@@ -0,0 +1,3 @@
+SELECT t0.foo_id, sum(t0.f) AS total 
+FROM star1 AS t0 GROUP BY 1 
+HAVING sum(t0.f) > 10
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/single_key/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/single_key/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/single_key/out.sql
@@ -0,0 +1,2 @@
+SELECT t0.foo_id, sum(t0.f) AS total 
+FROM star1 AS t0 GROUP BY 1
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/two_keys/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/two_keys/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_aggregate/two_keys/out.sql
@@ -0,0 +1,2 @@
+SELECT t0.foo_id, t0.bar_id, sum(t0.f) AS total 
+FROM star1 AS t0 GROUP BY 1, 2
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_cte_factor_distinct_but_equal/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_cte_factor_distinct_but_equal/out.sql
--- a/ibis/tests/sql/snapshots/test_sqlalchemy/test_cte_factor_distinct_but_equal/out.sql
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_cte_factor_distinct_but_equal/out.sql
@@ -1,5 +1,5 @@
 WITH t0 AS 
 (SELECT t2.g AS g, sum(t2.f) AS metric 
-FROM alltypes AS t2 GROUP BY t2.g)
+FROM alltypes AS t2 GROUP BY 1)
  SELECT t0.g, t0.metric 
 FROM t0 JOIN t0 AS t1 ON t0.g = t1.g
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/count_distinct/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/count_distinct/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/count_distinct/out.sql
@@ -0,0 +1,2 @@
+SELECT count(DISTINCT t0.int_col) AS nunique 
+FROM functional_alltypes AS t0
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/group_by_count_distinct/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/group_by_count_distinct/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/group_by_count_distinct/out.sql
@@ -0,0 +1,2 @@
+SELECT t0.string_col, count(DISTINCT t0.int_col) AS nunique 
+FROM functional_alltypes AS t0 GROUP BY 1
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/projection_distinct/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/projection_distinct/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/projection_distinct/out.sql
@@ -0,0 +1,2 @@
+SELECT DISTINCT t0.string_col, t0.int_col 
+FROM functional_alltypes AS t0
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/single_column_projection_distinct/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/single_column_projection_distinct/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/single_column_projection_distinct/out.sql
@@ -0,0 +1,2 @@
+SELECT DISTINCT t0.string_col 
+FROM functional_alltypes AS t0
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/table_distinct/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/table_distinct/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_distinct/table_distinct/out.sql
@@ -0,0 +1,2 @@
+SELECT DISTINCT t0.id, t0.bool_col, t0.tinyint_col, t0.smallint_col, t0.int_col, t0.bigint_col, t0.float_col, t0.double_col, t0.date_string_col, t0.string_col, t0.timestamp_col, t0.year, t0.month 
+FROM functional_alltypes AS t0
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_filter_group_by_agg_with_same_name/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_filter_group_by_agg_with_same_name/out.sql
new file mode 100644
--- /dev/null
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_filter_group_by_agg_with_same_name/out.sql
@@ -0,0 +1,4 @@
+SELECT t0.int_col, t0.bigint_col 
+FROM (SELECT t1.int_col AS int_col, sum(t1.bigint_col) AS bigint_col 
+FROM t AS t1 GROUP BY 1) AS t0 
+WHERE t0.bigint_col = 60
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_lower_projection_sort_key/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_lower_projection_sort_key/out.sql
--- a/ibis/tests/sql/snapshots/test_sqlalchemy/test_lower_projection_sort_key/out.sql
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_lower_projection_sort_key/out.sql
@@ -2,5 +2,5 @@ SELECT t0.foo_id, t0.total, t0.value1
 FROM (SELECT t1.foo_id AS foo_id, t1.total AS total, t1.value1 AS value1 
 FROM (SELECT t2.foo_id AS foo_id, t2.total AS total, t3.value1 AS value1 
 FROM (SELECT t4.foo_id AS foo_id, sum(t4.f) AS total 
-FROM star1 AS t4 GROUP BY t4.foo_id) AS t2 JOIN star2 AS t3 ON t2.foo_id = t3.foo_id) AS t1 
+FROM star1 AS t4 GROUP BY 1) AS t2 JOIN star2 AS t3 ON t2.foo_id = t3.foo_id) AS t1 
 WHERE t1.total > 100) AS t0 ORDER BY t0.total DESC
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_no_cart_join/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_no_cart_join/out.sql
--- a/ibis/tests/sql/snapshots/test_sqlalchemy/test_no_cart_join/out.sql
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_no_cart_join/out.sql
@@ -1,3 +1,3 @@
 SELECT t1.ancestor_node_sort_order, 1 AS n 
 FROM facts AS t0 JOIN (SELECT t2.ancestor_level_name AS ancestor_level_name, t2.ancestor_level_number AS ancestor_level_number, t2.ancestor_node_sort_order AS ancestor_node_sort_order, t2.descendant_node_natural_key AS descendant_node_natural_key, concat(lpad('-', (t2.ancestor_level_number - 1) * 7, '-'), t2.ancestor_level_name) AS product_level_name 
-FROM products AS t2) AS t1 ON t0.product_id = t1.descendant_node_natural_key GROUP BY t1.ancestor_node_sort_order ORDER BY t1.ancestor_node_sort_order ASC
\ No newline at end of file
+FROM products AS t2) AS t1 ON t0.product_id = t1.descendant_node_natural_key GROUP BY 1 ORDER BY t1.ancestor_node_sort_order ASC
\ No newline at end of file
diff --git a/ibis/tests/sql/snapshots/test_sqlalchemy/test_subquery_aliased/out.sql b/ibis/tests/sql/snapshots/test_sqlalchemy/test_subquery_aliased/out.sql
--- a/ibis/tests/sql/snapshots/test_sqlalchemy/test_subquery_aliased/out.sql
+++ b/ibis/tests/sql/snapshots/test_sqlalchemy/test_subquery_aliased/out.sql
@@ -1,3 +1,3 @@
 SELECT t0.foo_id, t0.total, t1.value1 
 FROM (SELECT t2.foo_id AS foo_id, sum(t2.f) AS total 
-FROM star1 AS t2 GROUP BY t2.foo_id) AS t0 JOIN star2 AS t1 ON t0.foo_id = t1.foo_id
\ No newline at end of file
+FROM star1 AS t2 GROUP BY 1) AS t0 JOIN star2 AS t1 ON t0.foo_id = t1.foo_id
\ No newline at end of file
diff --git a/ibis/tests/sql/test_sqlalchemy.py b/ibis/tests/sql/test_sqlalchemy.py
--- a/ibis/tests/sql/test_sqlalchemy.py
+++ b/ibis/tests/sql/test_sqlalchemy.py
@@ -358,51 +358,33 @@ def test_where_simple_comparisons(sa_star1, star1, snapshot):
 
 
 @pytest.mark.parametrize(
-    ("expr_fn", "expected_fn"),
+    "expr_fn",
     [
-        (
-            lambda t: t.aggregate([t['f'].sum().name('total')], [t['foo_id']]),
-            lambda st: sa.select(st.c.foo_id, F.sum(st.c.f).label('total')).group_by(
-                st.c.foo_id
-            ),
+        param(
+            lambda t: t.agg([t['f'].sum().name('total')], [t['foo_id']]),
+            id="single_key",
         ),
-        (
-            lambda t: t.aggregate([t['f'].sum().name('total')], ['foo_id', 'bar_id']),
-            lambda st: sa.select(
-                st.c.foo_id, st.c.bar_id, F.sum(st.c.f).label('total')
-            ).group_by(st.c.foo_id, st.c.bar_id),
+        param(
+            lambda t: t.agg([t['f'].sum().name('total')], ['foo_id', 'bar_id']),
+            id="two_keys",
         ),
-        (
-            lambda t: t.aggregate(
-                [t.f.sum().name("total")],
-                by=["foo_id"],
-                having=[t.f.sum() > 10],
-            ),
-            lambda st: (
-                sa.select(st.c.foo_id, F.sum(st.c.f).label("total"))
-                .group_by(st.c.foo_id)
-                .having(F.sum(st.c.f).label("total") > L(10))
+        param(
+            lambda t: t.agg(
+                [t.f.sum().name("total")], by=["foo_id"], having=[t.f.sum() > 10]
             ),
+            id="having_sum",
         ),
-        (
-            lambda t: t.aggregate(
-                [t.f.sum().name("total")],
-                by=["foo_id"],
-                having=[t.count() > 100],
-            ),
-            lambda st: (
-                sa.select(st.c.foo_id, F.sum(st.c.f).label("total"))
-                .group_by(st.c.foo_id)
-                .having(F.count() > L(100))
+        param(
+            lambda t: t.agg(
+                [t.f.sum().name("total")], by=["foo_id"], having=[t.count() > 100]
             ),
+            id="having_count",
         ),
     ],
 )
-def test_aggregate(con, star1, sa_star1, expr_fn, expected_fn):
-    st = sa_star1.alias('t0')
+def test_aggregate(star1, expr_fn, snapshot):
     expr = expr_fn(star1)
-    expected = expected_fn(st)
-    _check(expr, expected)
+    snapshot.assert_match(to_sql(expr), "out.sql")
 
 
 @pytest.mark.parametrize(
@@ -479,7 +461,11 @@ def test_cte_factor_distinct_but_equal(con, sa_alltypes, snapshot):
     #
 
     t2 = sa_alltypes.alias('t2')
-    t0 = sa.select(t2.c.g, F.sum(t2.c.f).label('metric')).group_by(t2.c.g).cte('t0')
+    t0 = (
+        sa.select(t2.c.g, F.sum(t2.c.f).label('metric'))
+        .group_by(sa.literal_column("1"))
+        .cte('t0')
+    )
 
     t1 = t0.alias('t1')
     table_set = t0.join(t1, t0.c.g == t1.c.g)
@@ -570,7 +556,7 @@ def test_subquery_aliased(con, star1, star2, snapshot):
 
     agged = (
         sa.select(s1.c.foo_id, F.sum(s1.c.f).label('total'))
-        .group_by(s1.c.foo_id)
+        .group_by(sa.literal_column("1"))
         .alias('t0')
     )
 
@@ -581,37 +567,14 @@ def test_subquery_aliased(con, star1, star2, snapshot):
     snapshot.assert_match(to_sql(expr), "out.sql")
 
 
-def test_lower_projection_sort_key(con, star1, star2, snapshot):
+def test_lower_projection_sort_key(star1, star2, snapshot):
     t1 = star1
     t2 = star2
 
     agged = t1.aggregate([t1.f.sum().name('total')], by=['foo_id'])
     expr = agged.inner_join(t2, [agged.foo_id == t2.foo_id])[agged, t2.value1]
-    #
-    t4 = con.meta.tables["star1"].alias("t4")
-    t3 = con.meta.tables["star2"].alias("t3")
-
-    t2 = (
-        sa.select(t4.c.foo_id, F.sum(t4.c.f).label('total'))
-        .group_by(t4.c.foo_id)
-        .alias('t2')
-    )
-    t1 = (
-        sa.select(t2.c.foo_id, t2.c.total, t3.c.value1)
-        .select_from(t2.join(t3, t2.c.foo_id == t3.c.foo_id))
-        .alias('t1')
-    )
-    t0 = (
-        sa.select(t1.c.foo_id, t1.c.total, t1.c.value1)
-        .where(t1.c.total > L(100))
-        .alias('t0')
-    )
-    expected = sa.select(t0.c.foo_id, t0.c.total, t0.c.value1).order_by(
-        t0.c.total.desc()
-    )
 
     expr2 = expr[expr.total > 100].order_by(ibis.desc('total'))
-    _check(expr2, expected)
     snapshot.assert_match(to_sql(expr2), "out.sql")
     assert_decompile_roundtrip(expr2, snapshot)
 
@@ -661,41 +624,27 @@ def test_not_exists(con, not_exists, snapshot):
 
 
 @pytest.mark.parametrize(
-    ("expr_fn", "expected_fn"),
+    "expr_fn",
     [
-        (lambda t: t.distinct(), lambda sat: sa.select(sat).distinct()),
-        (
-            lambda t: t['string_col', 'int_col'].distinct(),
-            lambda sat: sa.select(sat.c.string_col, sat.c.int_col).distinct(),
+        param(lambda t: t.distinct(), id="table_distinct"),
+        param(
+            lambda t: t['string_col', 'int_col'].distinct(), id="projection_distinct"
         ),
-        (
-            lambda t: t[t.string_col].distinct(),
-            lambda sat: sa.select(sat.c.string_col.distinct()),
-        ),
-        (
-            lambda t: t.int_col.nunique().name('nunique'),
-            lambda sat: sa.select(F.count(sat.c.int_col.distinct()).label('nunique')),
+        param(
+            lambda t: t[t.string_col].distinct(), id="single_column_projection_distinct"
         ),
-        (
+        param(lambda t: t.int_col.nunique().name('nunique'), id="count_distinct"),
+        param(
             lambda t: t.group_by('string_col').aggregate(
                 t.int_col.nunique().name('nunique')
             ),
-            lambda sat: sa.select(
-                sat.c.string_col,
-                F.count(sat.c.int_col.distinct()).label('nunique'),
-            ).group_by(sat.c.string_col),
+            id="group_by_count_distinct",
         ),
     ],
 )
-def test_distinct(
-    sa_functional_alltypes,
-    functional_alltypes,
-    expr_fn,
-    expected_fn,
-):
+def test_distinct(functional_alltypes, expr_fn, snapshot):
     expr = expr_fn(functional_alltypes)
-    expected = expected_fn(sa_functional_alltypes)
-    _check(expr, expected)
+    snapshot.assert_match(to_sql(expr), "out.sql")
 
 
 def test_sort_aggregation_translation_failure(
@@ -713,7 +662,7 @@ def test_sort_aggregation_translation_failure(
     sat = sa_functional_alltypes.alias("t1")
     base = (
         sa.select(sat.c.string_col, F.max(sat.c.double_col).label('foo')).group_by(
-            sat.c.string_col
+            sa.literal_column("1")
         )
     ).alias('t0')
 
@@ -824,7 +773,7 @@ def test_mutate_filter_join_no_cross_join():
     _check(expr, ex)
 
 
-def test_filter_group_by_agg_with_same_name():
+def test_filter_group_by_agg_with_same_name(snapshot):
     # GH 2907
     t = ibis.table([("int_col", "int32"), ("bigint_col", "int64")], name="t")
     expr = (
@@ -832,18 +781,7 @@ def test_filter_group_by_agg_with_same_name():
         .aggregate(bigint_col=lambda t: t.bigint_col.sum())
         .filter(lambda t: t.bigint_col == 60)
     )
-
-    t1 = sa.table("t", sa.column("int_col"), sa.column("bigint_col")).alias("t1")
-    t0 = (
-        sa.select(
-            t1.c.int_col.label("int_col"),
-            sa.func.sum(t1.c.bigint_col).label("bigint_col"),
-        )
-        .group_by(t1.c.int_col)
-        .alias("t0")
-    )
-    ex = sa.select(t0).where(t0.c.bigint_col == 60)
-    _check(expr, ex)
+    snapshot.assert_match(to_sql(expr), "out.sql")
 
 
 @pytest.fixture
@@ -1075,7 +1013,7 @@ def test_tpc_h11(h11):
             )
         )
         .where(t4.c.n_name == NATION)
-        .group_by(t2.c.ps_partkey)
+        .group_by(sa.literal_column("1"))
     ).alias("t1")
 
     anon_1 = (
@@ -1109,7 +1047,7 @@ def test_to_sqla_type_array_of_non_primitive():
     assert isinstance(result, ArrayType)
 
 
-def test_no_cart_join(con, snapshot):
+def test_no_cart_join(snapshot):
     facts = ibis.table(dict(product_id="!int32"), name="facts")
     products = ibis.table(
         dict(
@@ -1134,7 +1072,7 @@ def test_no_cart_join(con, snapshot):
     agg = gb.aggregate(n=ibis.literal(1))
     ob = agg.order_by(products.ancestor_node_sort_order)
 
-    out = str(con.compile(ob).compile(compile_kwargs=dict(literal_binds=True)))
+    out = to_sql(ob)
     snapshot.assert_match(out, "out.sql")