Skip to content

Commit 9d69602

Browse files
authored
basic aggregate functions (piccolo-orm#146)
1 parent 982c6d0 commit 9d69602

File tree

7 files changed

+444
-23
lines changed

7 files changed

+444
-23
lines changed

docs/src/piccolo/query_types/count.rst

Lines changed: 0 additions & 19 deletions
This file was deleted.

docs/src/piccolo/query_types/index.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ typical ORM.
1212

1313
./select
1414
./objects
15-
./alter
16-
./count
15+
./alter
1716
./create_table
1817
./delete
1918
./exists

docs/src/piccolo/query_types/select.rst

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,99 @@ convenient.
7979
# For joins:
8080
Band.select('manager.name').run_sync()
8181
82+
83+
Aggregate functions
84+
-------------------
85+
86+
Count
87+
~~~~~
88+
89+
Returns the number of rows which match the query.
90+
91+
.. code-block:: python
92+
93+
>>> Band.count().where(Band.name == 'Pythonistas').run_sync()
94+
1
95+
96+
Avg
97+
~~~
98+
99+
Returns average of numeric rows which match the query.
100+
101+
.. code-block:: python
102+
103+
>>> from piccolo.query import Avg
104+
>>> response = Band.select(Avg(Band.popularity)).first().run_sync()
105+
>>> response["avg"]
106+
750.0
107+
108+
Sum
109+
~~~
110+
111+
Returns sum of numeric rows which match the query.
112+
113+
.. code-block:: python
114+
115+
>>> from piccolo.query import Sum
116+
>>> response = Band.select(Sum(Band.popularity)).first().run_sync()
117+
>>> response["sum"]
118+
1500
119+
120+
Max
121+
~~~
122+
123+
Returns maximum of rows which match the query.
124+
125+
.. code-block:: python
126+
127+
>>> from piccolo.query import Max
128+
>>> response = Band.select(Max(Band.popularity)).first().run_sync()
129+
>>> response["max"]
130+
1000
131+
132+
Min
133+
~~~
134+
135+
Returns minimum of rows which match the query.
136+
137+
.. code-block:: python
138+
139+
>>> from piccolo.query import Min
140+
>>> response = Band.select(Min(Band.popularity)).first().run_sync()
141+
>>> response["min"]
142+
500
143+
144+
Additional features
145+
~~~~~~~~~~~~~~~~~~~
146+
147+
You also can chain multiple different aggregate functions in one query,
148+
149+
.. code-block:: python
150+
151+
>>> from piccolo.query import Avg, Sum
152+
>>> response = Band.select(Avg(Band.popularity), Sum(Band.popularity)).first().run_sync()
153+
>>> response
154+
{"avg": 750.0, "sum": 1500}
155+
156+
use aliases for aggregate functions like this
157+
158+
.. code-block:: python
159+
160+
>>> from piccolo.query import Avg
161+
>>> response = Band.select(Avg(Band.popularity, alias="popularity_avg")).first().run_sync()
162+
>>> response["popularity_avg"]
163+
750.0
164+
165+
or use ``as_alias`` method for aggregate functions like this.
166+
167+
.. code-block:: python
168+
169+
>>> from piccolo.query import Avg
170+
>>> response = Band.select(Avg(Band.popularity).as_alias("popularity_avg")).first().run_sync()
171+
>>> response["popularity_avg"]
172+
750.0
173+
174+
82175
Query clauses
83176
-------------
84177

piccolo/query/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
from .base import Query
22
from .methods import (
33
Alter,
4+
Avg,
45
Count,
56
Create,
67
CreateIndex,
78
Delete,
89
DropIndex,
910
Exists,
1011
Insert,
12+
Max,
13+
Min,
1114
Objects,
1215
Raw,
1316
Select,
17+
Sum,
1418
TableExists,
1519
Update,
1620
)

piccolo/query/methods/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
from .insert import Insert
99
from .objects import Objects
1010
from .raw import Raw
11-
from .select import Select
11+
from .select import Avg, Max, Min, Select, Sum
1212
from .table_exists import TableExists
1313
from .update import Update

piccolo/query/methods/select.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import decimal
34
import typing as t
45
from collections import OrderedDict
56

@@ -24,6 +25,33 @@
2425
from piccolo.table import Table # noqa
2526

2627

28+
class Avg(Selectable):
29+
"""
30+
AVG() SQL function. Column type must be numeric to run the query.
31+
32+
await Band.select(Avg(Band.popularity)).run() or with aliases
33+
await Band.select(Avg(Band.popularity, alias="popularity_avg")).run()
34+
await Band.select(Avg(Band.popularity).as_alias("popularity_avg")).run()
35+
"""
36+
37+
def __init__(self, column: Column, alias: str = "avg"):
38+
self.column = column
39+
self.alias = alias
40+
41+
def as_alias(self, alias: str) -> Avg:
42+
self.alias = alias
43+
return self
44+
45+
def get_select_string(self, engine_type: str, just_alias=False) -> str:
46+
if self.column.value_type in (int, decimal.Decimal, float):
47+
column_name = self.column._meta.get_full_name(
48+
just_alias=just_alias
49+
)
50+
else:
51+
raise ValueError("Column type must be numeric to run the query.")
52+
return f"AVG({column_name}) AS {self.alias}"
53+
54+
2755
class Count(Selectable):
2856
"""
2957
Used in conjunction with the ``group_by`` clause in ``Select`` queries.
@@ -46,6 +74,77 @@ def get_select_string(self, engine_type: str, just_alias=False) -> str:
4674
return f"COUNT({column_name}) AS count"
4775

4876

77+
class Max(Selectable):
78+
"""
79+
MAX() SQL function.
80+
81+
await Band.select(Max(Band.popularity)).run() or with aliases
82+
await Band.select(Max(Band.popularity, alias="popularity_max")).run()
83+
await Band.select(Max(Band.popularity).as_alias("popularity_max")).run()
84+
"""
85+
86+
def __init__(self, column: Column, alias: str = "max"):
87+
self.column = column
88+
self.alias = alias
89+
90+
def as_alias(self, alias: str) -> Max:
91+
self.alias = alias
92+
return self
93+
94+
def get_select_string(self, engine_type: str, just_alias=False) -> str:
95+
column_name = self.column._meta.get_full_name(just_alias=just_alias)
96+
return f"MAX({column_name}) AS {self.alias}"
97+
98+
99+
class Min(Selectable):
100+
"""
101+
MIN() SQL function.
102+
103+
await Band.select(Min(Band.popularity)).run()
104+
await Band.select(Min(Band.popularity, alias="popularity_min")).run()
105+
await Band.select(Min(Band.popularity).as_alias("popularity_min")).run()
106+
"""
107+
108+
def __init__(self, column: Column, alias: str = "min"):
109+
self.column = column
110+
self.alias = alias
111+
112+
def as_alias(self, alias: str) -> Min:
113+
self.alias = alias
114+
return self
115+
116+
def get_select_string(self, engine_type: str, just_alias=False) -> str:
117+
column_name = self.column._meta.get_full_name(just_alias=just_alias)
118+
return f"MIN({column_name}) AS {self.alias}"
119+
120+
121+
class Sum(Selectable):
122+
"""
123+
SUM() SQL function. Column type must be numeric to run the query.
124+
125+
await Band.select(Sum(Band.popularity)).run()
126+
await Band.select(Sum(Band.popularity, alias="popularity_sum")).run()
127+
await Band.select(Sum(Band.popularity).as_alias("popularity_sum")).run()
128+
"""
129+
130+
def __init__(self, column: Column, alias: str = "sum"):
131+
self.column = column
132+
self.alias = alias
133+
134+
def as_alias(self, alias: str) -> Sum:
135+
self.alias = alias
136+
return self
137+
138+
def get_select_string(self, engine_type: str, just_alias=False) -> str:
139+
if self.column.value_type in (int, decimal.Decimal, float):
140+
column_name = self.column._meta.get_full_name(
141+
just_alias=just_alias
142+
)
143+
else:
144+
raise ValueError("Column type must be numeric to run the query.")
145+
return f"SUM({column_name}) AS {self.alias}"
146+
147+
49148
class Select(Query):
50149

51150
__slots__ = (

0 commit comments

Comments
 (0)