Skip to content

Commit f1abca6

Browse files
author
Andres Pinto
committed
feat(usage): simplify the aggregation query
1 parent 81c5e7d commit f1abca6

File tree

5 files changed

+56
-133
lines changed

5 files changed

+56
-133
lines changed

src/modules/file/file.usecase.spec.ts

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,18 +1505,13 @@ describe('FileUseCases', () => {
15051505
it('When user has recent usage and is up to date, then it should not create new usage', async () => {
15061506
const mockUser = newUser();
15071507
const today = new Date('2024-01-02T00:00:00Z');
1508-
const mockAccumulatedUsage = {
1509-
totalMonthlyDelta: 1500,
1510-
totalYearlyDelta: 2500,
1511-
};
1508+
const mockAccumulatedUsage = 4000;
1509+
15121510
const mockUsage = newUsage({
15131511
attributes: { period: Time.dateWithDaysAdded(-1, today) },
15141512
});
15151513
const mockTodayUsage = 300;
1516-
const expectedTotal =
1517-
mockAccumulatedUsage.totalMonthlyDelta +
1518-
mockAccumulatedUsage.totalYearlyDelta +
1519-
mockTodayUsage;
1514+
const expectedTotal = mockAccumulatedUsage + mockTodayUsage;
15201515

15211516
// Set today to the next period start date according to mockUsage
15221517
jest.setSystemTime(today);
@@ -1547,16 +1542,11 @@ describe('FileUseCases', () => {
15471542
const mockUsage = newUsage({
15481543
attributes: { period: Time.dateWithDaysAdded(-3, today) },
15491544
});
1550-
const mockAccumulatedUsage = {
1551-
totalMonthlyDelta: 1500,
1552-
totalYearlyDelta: 2500,
1553-
};
1545+
const mockAccumulatedUsage = 4000;
1546+
15541547
const mockGapDelta = 500;
15551548
const mockTodayUsage = 300;
1556-
const expectedTotal =
1557-
mockAccumulatedUsage.totalMonthlyDelta +
1558-
mockAccumulatedUsage.totalYearlyDelta +
1559-
mockTodayUsage;
1549+
const expectedTotal = mockAccumulatedUsage + mockTodayUsage;
15601550

15611551
// Set today to a date after the next period start date according to mockUsage
15621552
jest.setSystemTime(today);
@@ -1600,15 +1590,10 @@ describe('FileUseCases', () => {
16001590
attributes: { period: Time.dateWithDaysAdded(-3, today) },
16011591
});
16021592
const mockGapDelta = 300;
1603-
const mockAccumulatedUsage = {
1604-
totalMonthlyDelta: 1500,
1605-
totalYearlyDelta: 2500,
1606-
};
1593+
const mockAccumulatedUsage = 4000;
1594+
16071595
const mockTodayUsage = 700;
1608-
const expectedTotal =
1609-
mockAccumulatedUsage.totalMonthlyDelta +
1610-
mockAccumulatedUsage.totalYearlyDelta +
1611-
mockTodayUsage;
1596+
const expectedTotal = mockAccumulatedUsage + mockTodayUsage;
16121597

16131598
jest.setSystemTime(today);
16141599
jest
@@ -1647,15 +1632,10 @@ describe('FileUseCases', () => {
16471632
const mockUsage = newUsage({
16481633
attributes: { period: Time.dateWithDaysAdded(-1, today) },
16491634
});
1650-
const mockAccumulatedUsage = {
1651-
totalMonthlyDelta: 1000,
1652-
totalYearlyDelta: 2000,
1653-
};
1635+
const mockAccumulatedUsage = 3000;
1636+
16541637
const mockTodayUsage = 500;
1655-
const expectedTotal =
1656-
mockAccumulatedUsage.totalMonthlyDelta +
1657-
mockAccumulatedUsage.totalYearlyDelta +
1658-
mockTodayUsage;
1638+
const expectedTotal = mockAccumulatedUsage + mockTodayUsage;
16591639

16601640
jest.setSystemTime(today);
16611641
jest

src/modules/file/file.usecase.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,7 @@ export class FileUseCases {
108108
today,
109109
);
110110

111-
return (
112-
accumulatedUsage.totalMonthlyDelta +
113-
accumulatedUsage.totalYearlyDelta +
114-
todayUsage
115-
);
111+
return accumulatedUsage + todayUsage;
116112
}
117113

118114
async handleUserFirstDeltaCreation(user: User) {

src/modules/usage/usage.repository.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,16 @@ describe('SequelizeUsageRepository', () => {
113113
describe('getUserUsage', () => {
114114
it('When called, then should execute query with expected arguments', async () => {
115115
const userUuid = v4();
116-
const expectedResult = {
117-
totalYearlyDelta: 5000,
118-
totalMonthlyDelta: 1000,
119-
};
116+
const yearlyTotal = 5000;
117+
const monthlyTotal = 1000;
118+
const expectedResult = yearlyTotal + monthlyTotal;
119+
120120
const mockSequelize = {
121121
query: jest.fn().mockResolvedValue([
122122
[
123123
{
124-
total_yearly_delta: expectedResult.totalYearlyDelta,
125-
total_monthly_delta: expectedResult.totalMonthlyDelta,
124+
previous_years_total: yearlyTotal,
125+
current_year_total: monthlyTotal,
126126
},
127127
],
128128
]),

src/modules/usage/usage.repository.ts

Lines changed: 36 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -93,101 +93,51 @@ export class SequelizeUsageRepository {
9393

9494
async getUserUsage(userUuid: string) {
9595
const query = `
96-
WITH yearly_sums AS (
97-
SELECT
98-
date_trunc('year', period) AS year,
99-
SUM(delta) AS total_delta
100-
FROM
101-
public.usages
102-
WHERE
103-
type = 'yearly'
104-
AND user_id = :userUuid
105-
GROUP BY
106-
date_trunc('year', period)
107-
),
108-
monthly_sums AS (
109-
SELECT
110-
date_trunc('year', period) AS year,
111-
date_trunc('month', period) AS month,
112-
SUM(delta) AS total_delta
113-
FROM
114-
public.usages
115-
WHERE
116-
type = 'monthly'
117-
AND user_id = :userUuid
118-
GROUP BY
119-
date_trunc('year', period), date_trunc('month', period)
120-
),
121-
daily_sums AS (
122-
SELECT
123-
date_trunc('year', period) AS year,
124-
date_trunc('month', period) AS month,
125-
SUM(delta) AS total_delta
126-
FROM
127-
public.usages
128-
WHERE
129-
type = 'daily'
130-
AND user_id = :userUuid
131-
GROUP BY
132-
date_trunc('year', period), date_trunc('month', period)
133-
),
134-
combined_monthly_and_daily AS (
135-
SELECT
136-
COALESCE(m.year, d.year) AS year,
137-
COALESCE(m.month, d.month) AS month,
138-
COALESCE(m.total_delta, 0) + COALESCE(d.total_delta, 0) AS total_delta
139-
FROM
140-
monthly_sums m
141-
FULL JOIN daily_sums d ON m.year = d.year AND m.month = d.month
142-
),
143-
combined_sums AS (
144-
SELECT
145-
y.year,
146-
NULL AS month,
147-
y.total_delta AS total_delta
148-
FROM
149-
yearly_sums y
150-
UNION ALL
151-
SELECT
152-
cmd.year,
153-
cmd.month,
154-
cmd.total_delta
155-
FROM
156-
combined_monthly_and_daily cmd
157-
LEFT JOIN yearly_sums ys ON cmd.year = ys.year
158-
WHERE
159-
ys.year IS NULL -- Exclude months and days where a yearly row exists
160-
)
161-
SELECT
162-
SUM(
163-
CASE
164-
WHEN year < date_trunc('year', CURRENT_DATE) THEN total_delta
165-
ELSE 0
166-
END
167-
) AS total_yearly_delta,
168-
SUM(
169-
CASE
170-
WHEN year = date_trunc('year', CURRENT_DATE) THEN total_delta
171-
ELSE 0
172-
END
173-
) AS total_monthly_delta
174-
FROM
175-
combined_sums;
96+
WITH years_with_yearly AS (
97+
SELECT DISTINCT date_trunc('year', period) AS year
98+
FROM public.usages
99+
WHERE type = 'yearly' AND user_id = :userUuid
100+
),
101+
aggregated_data AS (
102+
-- Aggregate yearly data where it exists
103+
SELECT
104+
date_trunc('year', period) AS year,
105+
SUM(delta) AS total_delta
106+
FROM public.usages
107+
WHERE type = 'yearly' AND user_id = :userUuid
108+
GROUP BY date_trunc('year', period)
109+
110+
UNION ALL
111+
112+
-- Aggregate monthly + daily data for years without yearly data
113+
SELECT
114+
date_trunc('year', period) AS year,
115+
SUM(delta) AS total_delta
116+
FROM public.usages
117+
WHERE type IN ('monthly', 'daily')
118+
AND user_id = :userUuid
119+
AND date_trunc('year', period) NOT IN (SELECT year FROM years_with_yearly)
120+
GROUP BY date_trunc('year', period)
121+
)
122+
SELECT
123+
SUM(CASE WHEN year < date_trunc('year', CURRENT_DATE) THEN total_delta ELSE 0 END) AS previous_years_total,
124+
SUM(CASE WHEN year = date_trunc('year', CURRENT_DATE) THEN total_delta ELSE 0 END) AS current_year_total
125+
FROM aggregated_data;
176126
`;
177127

178128
const [result] = (await this.usageModel.sequelize.query(query, {
179129
replacements: { userUuid },
180130
})) as unknown as [
181131
{
182-
total_yearly_delta: number;
183-
total_monthly_delta: number;
132+
previous_years_total: number;
133+
current_year_total: number;
184134
}[],
185135
];
186136

187-
return {
188-
totalYearlyDelta: Number(result[0].total_yearly_delta || 0),
189-
totalMonthlyDelta: Number(result[0].total_monthly_delta || 0),
190-
};
137+
return (
138+
Number(result[0].previous_years_total || 0) +
139+
Number(result[0].current_year_total || 0)
140+
);
191141
}
192142

193143
toDomain(model: UsageModel): Usage {

src/modules/usage/usage.service.spec.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,7 @@ describe('UsageService', () => {
259259
describe('getAccumulatedUsage', () => {
260260
it('When called, then it should return user usage from repository', async () => {
261261
const userUuid = v4();
262-
const expectedUsage = {
263-
totalYearlyDelta: 1000,
264-
totalMonthlyDelta: 500,
265-
};
262+
const expectedUsage = 1500;
266263

267264
jest
268265
.spyOn(usageRepository, 'getUserUsage')

0 commit comments

Comments
 (0)