Repository: blaze/blaze

Issue:
by function then count_values creates wrong result.
I'm using the lahman2013.sqlite file to learn blaze.   I believe the following is producing the wrong result.

```
import blaze as bz
_db = bz.Data('sqlite:///data/lahman2013.sqlite')

_sy=bz.by(_db.Teams.name, start_year=_db.Teams.yearID.min())
print(str(bz.compute(_sy, post_compute=False)))
```

produces

```
SELECT "Teams".name, min("Teams"."yearID") AS start_year 
FROM "Teams" GROUP BY "Teams".name
```

which seems correct to me.

I want to now use this result (the first year a team was in the MLB) to see how many teams entered the MLB for each year.  I believe the following should produce that..

```
print(list(_sy.start_year.count_values())
```

but it produces incorrect counts.  When I look at the sql produced I get the following:

```
print(str(bz.compute(_sy.start_year.count_values(), post_compute=False)))

SELECT anon_1.start_year, anon_1.count 
FROM (SELECT alias_1.start_year AS start_year, count(alias_2.start_year) AS count 
FROM (SELECT min("Teams"."yearID") AS start_year 
FROM "Teams" GROUP BY "Teams".name) AS alias_1, (SELECT alias_1.start_year AS start_year 
FROM (SELECT min("Teams"."yearID") AS start_year 
FROM "Teams" GROUP BY "Teams".name) AS alias_1) AS alias_2 GROUP BY alias_1.start_year) AS anon_1 ORDER BY anon_1.count DESC
```

which is incorrect.  It should produce something close to:

```
select start_year, count(*)
from
 (SELECT "Teams".name, min("Teams"."yearID") AS start_year 
FROM "Teams" GROUP BY "Teams".name)
group by start_year
```

I would love to take a stab at fixing this myself.  What I would like is for people to verify that I'm doing this correctly..

Thanks!

Billy



Discussion:
@earney Thanks for the report! I have some thoughts on this, but I'm going to be out for the rest of the night. I'll post them first thing tomorrow.


Resolving patch:
diff --git a/blaze/compute/sql.py b/blaze/compute/sql.py
--- a/blaze/compute/sql.py
+++ b/blaze/compute/sql.py
@@ -34,6 +34,7 @@
 import toolz
 
 from toolz import unique, concat, pipe
+from toolz.compatibility import zip
 from toolz.curried import map
 
 import numpy as np
@@ -418,7 +419,7 @@ def compute_up(t, s, **kwargs):
 
 @dispatch(count, Selectable)
 def compute_up(t, s, **kwargs):
-    return compute_up(t, select(s), **kwargs)
+    return s.count()
 
 
 @dispatch(count, sa.Table)
@@ -491,11 +492,8 @@ def compute_up(expr, data, **kwargs):
     grouper = get_inner_columns(compute(expr.grouper, data,
                                         post_compute=False))
     app = expr.apply
-    if isinstance(expr.apply, Reduction):
-        reductions = [compute(app, data, post_compute=False)]
-    elif isinstance(expr.apply, Summary):
-        reductions = [compute(val, data, post_compute=False).label(name)
-                      for val, name in zip(app.values, app.fields)]
+    reductions = [compute(val, data, post_compute=False).label(name)
+                  for val, name in zip(app.values, app.fields)]
 
     return sa.select(grouper + reductions).group_by(*grouper)
 
@@ -591,11 +589,10 @@ def compute_up(expr, data, **kwargs):
 
     s = alias_it(data)
 
-    # TODO: Do we need to be this restrictive here?
     if valid_reducer(expr.apply):
         reduction = compute(expr.apply, s, post_compute=False)
     else:
-        raise TypeError('apply must be a Summary or Reduction expression')
+        raise TypeError('apply must be a Summary expression')
 
     grouper = get_inner_columns(compute(expr.grouper, s, post_compute=False))
     reduction_columns = pipe(reduction.inner_columns,
@@ -606,7 +603,7 @@ def compute_up(expr, data, **kwargs):
             (hasattr(s, 'froms') and isinstance(s.froms[0],
                                                 sa.sql.selectable.Join))):
         assert len(s.froms) == 1, 'only a single FROM clause supported for now'
-        from_obj = s.froms[0]
+        from_obj, = s.froms
     else:
         from_obj = None
 
@@ -783,8 +780,11 @@ def compute_up(t, s, scope=None, **kwargs):
 
 @dispatch(Summary, ClauseElement)
 def compute_up(t, s, **kwargs):
-    return select([compute(value, {t._child: s}, post_compute=None).label(name)
-                   for value, name in zip(t.values, t.fields)])
+    scope = {t._child: s}
+    return sa.select(
+        compute(value, scope, post_compute=None).label(name)
+        for value, name in zip(t.values, t.fields)
+    )
 
 
 @dispatch(Like, Selectable)


Test patch:
diff --git a/blaze/compute/tests/test_sql_compute.py b/blaze/compute/tests/test_sql_compute.py
--- a/blaze/compute/tests/test_sql_compute.py
+++ b/blaze/compute/tests/test_sql_compute.py
@@ -10,7 +10,7 @@
 
 
 import datashape
-from odo import into, resource
+from odo import into, resource, discover
 from pandas import DataFrame
 from toolz import unique
 
@@ -21,7 +21,7 @@
     floor, cos, merge, nunique, mean, sum, count, exp, concat,
 )
 from blaze.compatibility import xfail
-from blaze.utils import tmpfile
+from blaze.utils import tmpfile, example
 
 
 names = ('tbl%d' % i for i in itertools.count())
@@ -1678,6 +1678,30 @@ def test_attribute_on_filter_transform_groupby():
     assert normalize(str(result)) == normalize(expected)
 
 
+def test_baseball_nested_by():
+    data = resource('sqlite:///%s' % example('teams.db'))
+    dshape = discover(data)
+    d = symbol('d', dshape)
+    expr = by(d.teams.name,
+              start_year=d.teams.yearID.min()).start_year.count_values()
+    result = compute(expr, data, post_compute=False)
+    expected = """SELECT
+        anon_1.start_year,
+        anon_1.count
+    FROM
+        (SELECT
+            alias.start_year as start_year,
+            count(alias.start_year) as count
+         FROM
+            (SELECT
+                min(teams.yearid) as start_year
+             FROM teams
+             GROUP BY teams.name) as alias
+         GROUP BY alias.start_year) as anon_1 ORDER BY anon_1.count DESC
+    """
+    assert normalize(str(result).replace('"', '')) == normalize(expected)
+
+
 def test_label_on_filter():
     expr = t[t.name == 'Alice'].amount.label('foo').head(2)
     result = compute(expr, s)