Repository: ibis-project/ibis

Issue:
bug: trino/starburst unexpected behavior with .sql
### What happened?

this is with the Trino backend connected to a demo Starburst Galaxy account using the sample dataset they provide, it should be relatively easily reproducible. in summary for some reason passing in SQL to `.sql()` doesn't work as expected; the equivalent Ibis is fine; the SQL versions aren't ordered as expected

```python
[ins] In [1]: import os # <1>
         ...: import ibis # <1>
         ...: from dotenv import load_dotenv # <1>
         ...:
         ...: # configure Ibis # <2>
         ...: ibis.options.interactive = True # <2>
         ...:
         ...: # load env variables # <3>
         ...: load_dotenv() # <3>
         ...: user = os.getenv("USERNAME") # <3>
         ...: password = os.getenv("PASSWORD") # <3>
         ...:
         ...: # variables # <4>
         ...: host = os.getenv("HOSTNAME") # <4>
         ...: port = os.getenv("PORTNUMBER") # <4>
         ...: catalog = "sample" # <4>
         ...: schema = "demo" # <4>
         ...:
         ...: # connect to database # <5>
         ...: con = ibis.trino.connect( # <5>
         ...:     user=user, password=password, host=host, port=port, dat
         ...: abase=catalog, schema=schema # <5>
         ...: ) # <5>
         ...: con
Out[1]: <ibis.backends.trino.Backend at 0x1060f5d90>

[ins] In [2]: astronauts = con.table("astronauts") # <1>
         ...: missions = con.table("missions") # <1>

[ins] In [3]: sql = f"""
         ...: SELECT
         ...:   name,
         ...:   count() AS nbr_missions
         ...: FROM
         ...:   sample.demo.astronauts
         ...: GROUP BY
         ...:   name
         ...: ORDER BY
         ...:   nbr_missions DESC;
         ...: """.strip().strip(";")
         ...: print(sql)
SELECT
  name,
  count() AS nbr_missions
FROM
  sample.demo.astronauts
GROUP BY
  name
ORDER BY
  nbr_missions DESC

[ins] In [5]: con.sql(sql)
Out[5]:
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ name                   ┃ nbr_missions ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ string                 │ int64        │
├────────────────────────┼──────────────┤
│ Gagarin, Yuri          │            1 │
│ Titov, Gherman         │            1 │
│ Tereshkova, Valentina  │            1 │
│ Feoktistov, Konstantin │            1 │
│ Leonov, Aleksei        │            2 │
│ Grissom, Virgil I.     │            1 │
│ McDivitt, James A.     │            2 │
│ White, Edward H., II   │            1 │
│ Borman, Frank          │            2 │
│ Lovell, James A., Jr.  │            4 │
│ …                      │            … │
└────────────────────────┴──────────────┘

[ins] In [6]: astronauts.group_by("name").agg(nbr_missions=ibis._.count()
         ...: ).order_by(
         ...:     ibis._["nbr_missions"].desc()
         ...: )
Out[6]:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ name                     ┃ nbr_missions ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ string                   │ int64        │
├──────────────────────────┼──────────────┤
│ Ross, Jerry L.           │            7 │
│ Chang-Diaz, Franklin R.  │            7 │
│ Wetherbee, James D.      │            6 │
│ Malenchenko, Yuri        │            6 │
│ Young, John W.           │            6 │
│ Musgrave, Franklin Story │            6 │
│ Brown, Curtis L., Jr.    │            6 │
│ Krikalev, Sergei         │            6 │
│ Foale, C. Michael        │            6 │
│ Ivins, Marsha S.         │            5 │
│ …                        │            … │
└──────────────────────────┴──────────────┘

[ins] In [7]: print(ibis.to_sql(astronauts.group_by("name").agg(nbr_missi
         ...: ons=ibis._.count()).order_by(
         ...:     ibis._["nbr_missions"].desc()
         ...: )))
SELECT
  t0.name,
  t0.nbr_missions
FROM (
  SELECT
    t1.name AS name,
    COUNT(*) AS nbr_missions
  FROM "sample".demo.astronauts AS t1
  GROUP BY
    1
) AS t0
ORDER BY
  t0.nbr_missions DESC

[nav] In [8]: con.sql(ibis.to_sql(astronauts.group_by("name").agg(nbr_mis
         ...: sions=ibis._.count()).order_by(
         ...:     ibis._["nbr_missions"].desc()
         ...: )))
Out[8]:
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ name                    ┃ nbr_missions ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ string                  │ int64        │
├─────────────────────────┼──────────────┤
│ Glenn, John H., Jr.     │            2 │
│ Carpenter, M. Scott     │            1 │
│ Nikolayev, Andriyan     │            2 │
│ Popovich, Pavel         │            2 │
│ Schirra, Walter M., Jr. │            3 │
│ Cooper, L. Gordon, Jr.  │            2 │
│ Bykovsky, Valery        │            3 │
│ Komarov, Vladimir       │            2 │
│ Yegorov, Boris          │            1 │
│ Belyayev, Pavel         │            1 │
│ …                       │            … │
└─────────────────────────┴──────────────┘
```

### What version of ibis are you using?

master

### What backend(s) are you using, if any?

trino

### Relevant log output

```sh
see above
```


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
I understand the motivation for the example, but it's quite long.

The long example is fine, but for the future would you mind showing a separate code block that contains **only** the failure? It's hard to see what's happening because of all the setup code.
Also you don't need to keep `strip().strip(";")`-ing. Are you seeing failures when you don't do that?
Another thing that would help cut the example down is making some variables of the expressions instead of duplicating them only to show their sql.
Sorry, I keep noticing things 😄 

`missions` is unused, which adds noise to understanding the problem

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
@@ -222,13 +222,14 @@ def _compile_table_set(self):
         if self.table_set is not None:
             helper = self.table_set_formatter_class(self, self.table_set)
             result = helper.get_result()
-            if isinstance(result, sql.selectable.Select):
-                return result.subquery()
             return result
         else:
             return None
 
     def _add_select(self, table_set):
+        if not self.select_set:
+            return table_set.element
+
         to_select = []
 
         context = self.context
@@ -374,6 +375,11 @@ class AlchemySelectBuilder(SelectBuilder):
     def _convert_group_by(self, exprs):
         return exprs
 
+    def _collect_SQLQueryResult(self, op, toplevel=False):
+        if toplevel:
+            self.table_set = op
+            self.select_set = []
+
 
 class AlchemySetOp(SetOp):
     def compile(self):
diff --git a/ibis/backends/conftest.py b/ibis/backends/conftest.py
--- a/ibis/backends/conftest.py
+++ b/ibis/backends/conftest.py
@@ -94,6 +94,34 @@
             "notes": "string",
         }
     ),
+    "astronauts": ibis.schema(
+        {
+            "id": "int64",
+            "number": "int64",
+            "nationwide_number": "int64",
+            "name": "string",
+            "original_name": "string",
+            "sex": "string",
+            "year_of_birth": "int64",
+            "nationality": "string",
+            "military_civilian": "string",
+            "selection": "string",
+            "year_of_selection": "int64",
+            "mission_number": "int64",
+            "total_number_of_missions": "int64",
+            "occupation": "string",
+            "year_of_mission": "int64",
+            "mission_title": "string",
+            "ascend_shuttle": "string",
+            "in_orbit": "string",
+            "descend_shuttle": "string",
+            "hours_mission": "float64",
+            "total_hrs_sum": "float64",
+            "field21": "int64",
+            "eva_hrs_mission": "float64",
+            "total_eva_hrs": "float64",
+        }
+    ),
 }
 
 # We want to check for exceptions in xfail tests for two reasons:


Test patch:
diff --git a/ibis/backends/flink/tests/test_ddl.py b/ibis/backends/flink/tests/test_ddl.py
--- a/ibis/backends/flink/tests/test_ddl.py
+++ b/ibis/backends/flink/tests/test_ddl.py
@@ -32,11 +32,8 @@ def awards_players_csv_connector_configs():
 
 
 def test_list_tables(con):
-    assert len(con.list_tables()) == 4
-    assert (
-        len(con.list_tables(catalog="default_catalog", database="default_database"))
-        == 4
-    )
+    assert len(con.list_tables())
+    assert con.list_tables(catalog="default_catalog", database="default_database")
 
 
 def test_create_table_from_schema(
@@ -47,38 +44,26 @@ def test_create_table_from_schema(
         schema=awards_players_schema,
         tbl_properties=awards_players_csv_connector_configs,
     )
-    assert len(con.list_tables()) == 5
     assert temp_table in con.list_tables()
     assert new_table.schema() == awards_players_schema
 
 
-def test_drop_table(
-    con, awards_players_schema, temp_table, awards_players_csv_connector_configs
+@pytest.mark.parametrize("temp", [True, False])
+def test_create_table(
+    con, awards_players_schema, temp_table, awards_players_csv_connector_configs, temp
 ):
     con.create_table(
         temp_table,
         schema=awards_players_schema,
         tbl_properties=awards_players_csv_connector_configs,
+        temp=temp,
     )
-    assert len(con.list_tables()) == 5
-    con.drop_table(temp_table)
-    assert len(con.list_tables()) == 4
-    assert temp_table not in con.list_tables()
+    assert temp_table in con.list_tables()
 
+    if temp:
+        with pytest.raises(Py4JJavaError):
+            con.drop_table(temp_table)
+
+    con.drop_table(temp_table, temp=temp)
 
-def test_temp_table(
-    con, awards_players_schema, temp_table, awards_players_csv_connector_configs
-):
-    con.create_table(
-        temp_table,
-        schema=awards_players_schema,
-        tbl_properties=awards_players_csv_connector_configs,
-        temp=True,
-    )
-    assert len(con.list_tables()) == 5
-    assert temp_table in con.list_tables()
-    with pytest.raises(Py4JJavaError):
-        con.drop_table(temp_table)
-    con.drop_table(temp_table, temp=True)
-    assert len(con.list_tables()) == 4
     assert temp_table not in con.list_tables()
diff --git a/ibis/backends/sqlite/tests/conftest.py b/ibis/backends/sqlite/tests/conftest.py
--- a/ibis/backends/sqlite/tests/conftest.py
+++ b/ibis/backends/sqlite/tests/conftest.py
@@ -2,6 +2,7 @@
 
 import contextlib
 import csv
+import io
 import sqlite3
 from typing import Any
 
@@ -34,8 +35,14 @@ def _load_data(self, **kw: Any) -> None:
         with self.connection.begin() as con:
             for table in TEST_TABLES:
                 basename = f"{table}.csv"
-                with self.data_dir.joinpath("csv", basename).open("r") as f:
-                    reader = csv.reader(f)
+                with self.data_dir.joinpath("csv", basename).open(
+                    "r", encoding="UTF-8"
+                ) as f:
+                    if basename == "astronauts.csv":
+                        input = io.StringIO(f.read().replace("\n ", " "))
+                    else:
+                        input = f
+                    reader = csv.reader(input)
                     header = next(reader)
                     assert header, f"empty header for table: `{table}`"
                     spec = ", ".join("?" * len(header))
diff --git a/ibis/backends/tests/base.py b/ibis/backends/tests/base.py
--- a/ibis/backends/tests/base.py
+++ b/ibis/backends/tests/base.py
@@ -214,6 +214,10 @@ def awards_players(self) -> ir.Table:
     def diamonds(self) -> ir.Table:
         return self.connection.table(self.default_identifier_case_fn("diamonds"))
 
+    @property
+    def astronauts(self) -> ir.Table:
+        return self.connection.table(self.default_identifier_case_fn("astronauts"))
+
     @property
     def geo(self) -> ir.Table | None:
         name = self.default_identifier_case_fn("geo")
diff --git a/ibis/backends/tests/test_dot_sql.py b/ibis/backends/tests/test_dot_sql.py
--- a/ibis/backends/tests/test_dot_sql.py
+++ b/ibis/backends/tests/test_dot_sql.py
@@ -292,3 +292,20 @@ def test_con_dot_sql_transpile(backend, con, dialect, df):
     result = expr.execute()
     expected = df.int_col.add(1).rename("x")
     backend.assert_series_equal(result.x, expected)
+
+
+@dot_sql_notimpl
+@dot_sql_notyet
+@dot_sql_never
+@pytest.mark.notimpl(["druid", "flink", "impala", "polars", "pyspark"])
+def test_order_by_no_projection(backend):
+    con = backend.connection
+    astronauts = con.table("astronauts")
+    expr = (
+        astronauts.group_by("name")
+        .agg(nbr_missions=_.count())
+        .order_by(_.nbr_missions.desc())
+    )
+
+    result = con.sql(ibis.to_sql(expr)).execute().name.iloc[:2]
+    assert set(result) == {"Ross, Jerry L.", "Chang-Diaz, Franklin R."}