Skip to content

Commit 9e5de73

Browse files
committed
Doc: Show how to use sqlalchemy to sum repetitive data
1 parent 5d2d832 commit 9e5de73

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ This repository holds all the notes of the things I have learnt. Anyone else can
6464

6565
- [Getting started with the Windows Subsystem for Linux](non_technical_articles/wsl.md)
6666
- [Integer conversion to binary using Python](non_technical_articles/convert_integers_to_binary_using_python.md)
67+
- [Sum Repetitive Column Data in SQLAlchemy](sum_column_data_in_sql.md)
6768

6869
### Africa's Talking API
6970

sum_column_data_in_sql.md

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Sum Repetitive Data In SQLAlchemy
2+
3+
Consider this data:
4+
5+
| Customer Name | Contribution | Date |
6+
| ------------- | ------------ | ----------- |
7+
| Muthoni | 100 | Jan 1, 2023 |
8+
| Njeri | 1000 | Jan 2, 2023 |
9+
| Gitau | 750 | Jan 3, 2023 |
10+
| Muthoni | 300 | Jan 4, 2023 |
11+
| Gitau | 250 | Jan 5, 2023 |
12+
| Gitau | 700 | Jan 5, 2023 |
13+
| Njeri | 1200 | Jan 6, 2023 |
14+
15+
We have three customers Muthoni, Njeri, and Gitau. They repeatedly make contributions (let us assume it is a self-help group) within January. At the end of the month, we may want to find out how much each person contributed. This data will now look like this at the end of the month:
16+
17+
| Customer Name | Contribution |
18+
| ------------- | ------------ |
19+
| Muthoni | 400 |
20+
| Njeri | 2200 |
21+
| Gitau | 1700 |
22+
23+
In the aggregate contribution, I am not paying attention to the date. What I am interested in is the total contribution of each member. Now, in the context of SQLAlchemy, how can you achieve such a summation? How can we group the repetitive rows based on a customer's name and sum their contribution?
24+
25+
26+
## Simple Models
27+
28+
A simple database module that captures customer contributions can look like this (I will use Flask for illustration):
29+
30+
```python
31+
# models.py
32+
33+
from app import db
34+
from flask_login import UserMixin
35+
from datetime import datetime
36+
37+
38+
class Customer(UserMixin, db.Model):
39+
id = db.Column(db.Integer, primary_key=True)
40+
name = db.Column(db.String(64), index=True, unique=True nullable=False)
41+
registered_at = db.Column(db.DateTime, default=datetime.utcnow)
42+
contributions = db.relationship('Contribution', backref='customer', passive_deletes=True)
43+
44+
45+
46+
class Contribution(db.Model):
47+
id = db.Column(db.Integer, primary_key=True)
48+
contribution = db.Column(db.Integer, default=1, nullable=False)
49+
date = db.Column(db.DateTime, default=datetime.utcnow)
50+
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), on_delete='CASCADE')
51+
52+
```
53+
54+
In a one-to-many-relationship between the models, a customer can make multiple contributions. Looked another way, many contributions can belong to one customer in a many-to-one relationship. Alright, a customer has used the web app and we have the sample data shown above, how can we present it in a 'cleaner' manner?
55+
56+
## Render 'Clean' Customer Data
57+
58+
A view function that can be used to render this information can be created as follows:
59+
60+
61+
```python
62+
from app import app, db
63+
from app.models import Customer, Contribution
64+
from sqlalchemy import func
65+
66+
67+
@app.route('/customer-contribution')
68+
def customer_contribution():
69+
contributions = db.session.query(
70+
Contribution.name, func.sum(
71+
Contribution.contribution)).group_by(Contribution.name).all()
72+
return render_template('customer_data.html', contributions=contributions)
73+
```
74+
75+
SQLAchemy provides the `func` namespace that is used to invoke SQL functions. In our case above, we want to invoke the `sum` function and apply it to all customer contributions. So, what is going on here? If you remember from above, it is a customer's name that keeps appearing multiple times. This customer can, and certainly does, make multiple contributions.
76+
77+
We begin by specifying this `name` column from the `Contribution` model as `Contribution.name`. This is possible because both models are related as specified by the `relationship` method found in the `Customer` model. Using `func`, we invoke the `sum` method on the `contribution` column of the `Contribution` model. Intentionally, this adds all data in the `contribution` column. But before we can finish, we want to ensure that the addition applies only to the said customer whose name keeps appearing multiple times. Therefore, we combine his contribution by using `group_by(Contribution.name)`. What we have so far is the total contribution of a single customer. The `.all()` method is applied to return all customers in the database. In the end, we will have a list with customers' data such as `[('Gitau', 100), ('Njeri', 2200), ('Muthoni', 400)]`. This list is passed to the templates as a variable called `contributions`.
78+
79+
80+
## Display The Data
81+
82+
We can display an individual customer's data on an HTML template as follows:
83+
84+
```html
85+
<!-- customer.html -->
86+
87+
<div class="table-responsive">
88+
<table class="table table-striped">
89+
<thead>
90+
<tr>
91+
<th>Customer Name</th>
92+
<th>Contribution</th>
93+
</tr>
94+
</thead>
95+
<tbody>
96+
{% for contribution in contributions %}
97+
{% if contribution %}
98+
<tr>
99+
<td>{{ contribution.name }}</td>
100+
<td>{{ contribution[1] }}</td>
101+
</tr>
102+
{% endif %}
103+
{% endfor %}
104+
</tbody>
105+
</table>
106+
</div>
107+
```
108+
109+
We loop through the `contributions` list to access the individual data we want. The Jinja2 templating engine allows for the use of double curly braces `{{ }}` to pass in dynamic data (data that can change). Remember, the customer's name can be gotten from `{{ contribution.name }}`. His total contribution is not found in the database, so we can use an index to access the sum. The conditional `if` statement has been used to check if there is data in our query. If none exists, then nothing will be shown.

0 commit comments

Comments
 (0)