This commit is contained in:
collerek
2021-03-15 18:45:46 +01:00
parent 03e6ac6c02
commit 67904980ce
8 changed files with 449 additions and 5 deletions

View File

@ -1,15 +1,23 @@
# Aggregation functions
Currently 2 aggregation functions are supported.
Currently 6 aggregation functions are supported.
* `count() -> int`
* `exists() -> bool`
* `sum(columns) -> Any`
* `avg(columns) -> Any`
* `min(columns) -> Any`
* `max(columns) -> Any`
* `QuerysetProxy`
* `QuerysetProxy.count()` method
* `QuerysetProxy.exists()` method
* `QuerysetProxy.sum(columns)` method
* `QuerysetProxy.avg(columns)` method
* `QuerysetProxy.min(column)` method
* `QuerysetProxy.max(columns)` method
## count
@ -68,6 +76,209 @@ class Book(ormar.Model):
has_sample = await Book.objects.filter(title='Sample').exists()
```
## sum
`sum(columns) -> Any`
Returns sum value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
You can pass one or many column names including related columns.
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
you can have `sum(col1, col2)` and later add 2 returned sums in python)
You cannot `sum` non numeric columns.
If you aggregate on one column, the single value is directly returned as a result
If you aggregate on multiple columns a dictionary with column: result pairs is returned
Given models like follows
```Python
--8<-- "../docs_src/aggregations/docs001.py"
```
A sample usage might look like following
```python
author = await Author(name="Author 1").save()
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
assert await Book.objects.sum("year") == 5773
result = await Book.objects.sum(["year", "ranking"])
assert result == dict(year=5773, ranking=9)
try:
# cannot sum string column
await Book.objects.sum("title")
except ormar.QueryDefinitionError:
pass
assert await Author.objects.select_related("books").sum("books__year") == 5773
result = await Author.objects.select_related("books").sum(
["books__year", "books__ranking"]
)
assert result == dict(books__year=5773, books__ranking=9)
assert (
await Author.objects.select_related("books")
.filter(books__year__lt=1925)
.sum("books__year")
== 3843
)
```
## avg
`avg(columns) -> Any`
Returns avg value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
You can pass one or many column names including related columns.
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
you can have `sum(col1, col2)` and later add 2 returned sums in python)
You cannot `avg` non numeric columns.
If you aggregate on one column, the single value is directly returned as a result
If you aggregate on multiple columns a dictionary with column: result pairs is returned
```Python
--8<-- "../docs_src/aggregations/docs001.py"
```
A sample usage might look like following
```python
author = await Author(name="Author 1").save()
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
assert round(float(await Book.objects.avg("year")), 2) == 1924.33
result = await Book.objects.avg(["year", "ranking"])
assert round(float(result.get("year")), 2) == 1924.33
assert result.get("ranking") == 3.0
try:
# cannot avg string column
await Book.objects.avg("title")
except ormar.QueryDefinitionError:
pass
result = await Author.objects.select_related("books").avg("books__year")
assert round(float(result), 2) == 1924.33
result = await Author.objects.select_related("books").avg(
["books__year", "books__ranking"]
)
assert round(float(result.get("books__year")), 2) == 1924.33
assert result.get("books__ranking") == 3.0
assert (
await Author.objects.select_related("books")
.filter(books__year__lt=1925)
.avg("books__year")
== 1921.5
)
```
## min
`min(columns) -> Any`
Returns min value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
You can pass one or many column names including related columns.
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
you can have `sum(col1, col2)` and later add 2 returned sums in python)
If you aggregate on one column, the single value is directly returned as a result
If you aggregate on multiple columns a dictionary with column: result pairs is returned
```Python
--8<-- "../docs_src/aggregations/docs001.py"
```
A sample usage might look like following
```python
author = await Author(name="Author 1").save()
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
assert await Book.objects.min("year") == 1920
result = await Book.objects.min(["year", "ranking"])
assert result == dict(year=1920, ranking=1)
assert await Book.objects.min("title") == "Book 1"
assert await Author.objects.select_related("books").min("books__year") == 1920
result = await Author.objects.select_related("books").min(
["books__year", "books__ranking"]
)
assert result == dict(books__year=1920, books__ranking=1)
assert (
await Author.objects.select_related("books")
.filter(books__year__gt=1925)
.min("books__year")
== 1930
)
```
## max
`max(columns) -> Any`
Returns max value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
Returns min value of columns for rows matching the given criteria (applied with `filter` and `exclude` if set before).
You can pass one or many column names including related columns.
As of now each column passed is aggregated separately (so `sum(col1+col2)` is not possible,
you can have `sum(col1, col2)` and later add 2 returned sums in python)
If you aggregate on one column, the single value is directly returned as a result
If you aggregate on multiple columns a dictionary with column: result pairs is returned
```Python
--8<-- "../docs_src/aggregations/docs001.py"
```
A sample usage might look like following
```python
author = await Author(name="Author 1").save()
await Book(title="Book 1", year=1920, ranking=3, author=author).save()
await Book(title="Book 2", year=1930, ranking=1, author=author).save()
await Book(title="Book 3", year=1923, ranking=5, author=author).save()
assert await Book.objects.max("year") == 1930
result = await Book.objects.max(["year", "ranking"])
assert result == dict(year=1930, ranking=5)
assert await Book.objects.max("title") == "Book 3"
assert await Author.objects.select_related("books").max("books__year") == 1930
result = await Author.objects.select_related("books").max(
["books__year", "books__ranking"]
)
assert result == dict(books__year=1930, books__ranking=5)
assert (
await Author.objects.select_related("books")
.filter(books__year__lt=1925)
.max("books__year")
== 1923
)
```
## QuerysetProxy methods
When access directly the related `ManyToMany` field as well as `ReverseForeignKey`
@ -89,6 +300,26 @@ objects from other side of the relation.
Works exactly the same as [exists](./#exists) function above but allows you to select columns from related
objects from other side of the relation.
### sum
Works exactly the same as [sum](./#sum) function above but allows you to sum columns from related
objects from other side of the relation.
### avg
Works exactly the same as [avg](./#avg) function above but allows you to average columns from related
objects from other side of the relation.
### min
Works exactly the same as [min](./#min) function above but allows you to select minimum of columns from related
objects from other side of the relation.
### max
Works exactly the same as [max](./#max) function above but allows you to select maximum of columns from related
objects from other side of the relation.
!!!tip
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section

View File

@ -289,7 +289,7 @@ books = (
```
If you want or need to you can nest deeper conditions as deep as you want, in example to
acheive a query like this:
achieve a query like this:
sql:
```
@ -564,6 +564,38 @@ assert owner.toys[1].name == "Toy 1"
Something like `Track.object.select_related("album").filter(album__name="Malibu").offset(1).limit(1).all()`
### Default sorting in ormar
Since order of rows in a database is not guaranteed, `ormar` **always** issues an `order by` sql clause to each (part of) query even if you do not provide order yourself.
When querying the database with given model by default the `Model` is ordered by the `primary_key`
column ascending. If you wish to change the default behaviour you can do it by providing `orders_by`
parameter to model `Meta` class.
!!!tip
To read more about models sort order visit [models](../models/index.md#model-sort-order) section of documentation
By default the relations follow the same ordering, but you can modify the order in which related models are loaded during query by providing `orders_by` and `related_orders_by`
parameters to relations.
!!!tip
To read more about models sort order visit [relations](../relations/index.md#relationship-default-sort-order) section of documentation
Order in which order_by clauses are applied is as follows:
* Explicitly passed `order_by()` calls in query
* Relation passed `orders_by` and `related_orders_by` if exists
* Model `Meta` class `orders_by`
* Model `primary_key` column ascending (fallback, used if none of above provided)
**Order from only one source is applied to each `Model` (so that you can always overwrite it in a single query).**
That means that if you provide explicit `order_by` for a model in a query, the `Relation` and `Model` sort orders are skipped.
If you provide a `Relation` one, the `Model` sort is skipped.
Finally, if you provide one for `Model` the default one by `primary_key` is skipped.
### QuerysetProxy methods
When access directly the related `ManyToMany` field as well as `ReverseForeignKey`