Aggregation & Grouping
Prisma Intermediate/Advanced
Chapter 2 ยท Aggregation & Grouping
So far every query has returned records. Real applications also need numbers about records: how many posts are published, the average view count, which author wrote the most. Fetching every row and adding it up in JavaScript works for ten posts and falls over at ten thousand. This chapter shows how to make the database do that arithmetic, using the same blog schema as before.
Counting: count()
count() accepts the same where filters as findMany, so everything from
Prisma Fundamentals, Chapter 6 carries over.
Summary Statistics: aggregate()
| Operator | Works on | Notes |
|---|---|---|
| _count | Any field, or _all | Counts non-null values; always a number, 0 when nothing matches |
| _sum, _avg | Numeric fields | _avg of an Int field can be a fraction |
| _min, _max | Numbers, dates, strings | Handy for "oldest" and "newest" |
where matches nothing, _sum, _avg, _min and
_max come back as null. Only _count is guaranteed to be a number. Handle it
explicitly — stats._sum.viewCount ?? 0 — rather than letting null reach a
template or a calculation.
Per-Group Figures: groupBy()
aggregate gives one set of numbers for everything matched. groupBy gives one set
per group — the Prisma equivalent of SQL's GROUP BY.
where vs. having
| Option | Filters | Example |
|---|---|---|
| where | Individual rows, before grouping | Only published posts |
| having | Whole groups, after grouping | Only authors with at least 5 posts |
Inside having you can only filter on aggregate values, or on fields listed in by.
skip or take with groupBy, you must also give an
orderBy. Without a defined order, "the first ten groups" doesn't mean anything, so Prisma refuses
the query.
groupBy returns IDs, not names
Groups contain only the by fields and the aggregates — groupBy can't
include relations. To show author names, fetch them in a second query and join in code:
Two queries, whatever the number of authors — not one query per author.
Counting Relations: _count
Often the simplest option is to ask for a count alongside each record, using _count inside
select or include:
_count when you're listing records anyway and want a number next to each. Use
groupBy when the numbers are the result. Note that orderBy on a relation count
sorts by all related posts; the where inside _count only affects the number
returned.
distinct
Prisma applies distinct by filtering results after they're fetched rather than with SQL's
SELECT DISTINCT. It's convenient, but on a large table it still reads every matching row — for
"how many of each," prefer groupBy.
A Stats Endpoint for the Blog API
Wrapping the four reads in $transaction([...]) from Chapter 1 means they all see the same snapshot
of the data, so the numbers are consistent with each other.
Hands-On Exercises
Write one query that returns the number of published posts, the total and average view count, and the date of the most recent published post. Then run it with a where that matches nothing and show safe handling of the null values.
Using groupBy, list authors with at least 3 published posts and an average view count above 50, most-viewed first, showing each author's name.
Add GET /tags to the blog API, returning every tag with its number of published posts, and add ?unused=true support that returns only tags with no posts at all. Explain which technique you used for each and why.
Chapter 2 Quick Reference
count({ where });select: { _all: true, field: true }counts rows and non-null valuesaggregatewith_count,_sum,_avg,_min,_max- Empty results:
_countis0, everything else isnull— use?? 0 groupBy({ by, where, having, orderBy });wherefilters rows,havingfilters groupsskip/takeongroupByrequireorderBy; groups hold IDs, so look names up separately- Relation counts:
_count: { select: { posts: { where } } }; sort withorderBy: { posts: { _count: "desc" } } distinctfilters in memory; prefergroupByfor counts on big tables