Window Functions
Rankings, running totals, row numbering — analytics queries that compute values across related rows without collapsing them.
What are window functions?
Window functions are like aggregates (SUM, COUNT, AVG) but they don't collapse rows into groups. Instead, they compute a value for each row based on a "window" of related rows. This makes them perfect for rankings, running totals, and numbering.
GROUP BY (aggregates)
- ·Collapses rows into groups
- ·One output row per group
- ·Can't see individual records
- ·SUM, COUNT, AVG
Window functions
- ·Keeps all rows intact
- ·Adds computed columns to each row
- ·See individual records + analytics
- ·ROW_NUMBER, RANK, SUM OVER
Available functions
TS
10 linesimport { rowNumber, // sequential numbering within a partition rank, // ranking with gaps (1, 2, 2, 4) denseRank, // ranking without gaps (1, 2, 2, 3) sumOver, // running sum within a partition avgOver, // running average countOver, // running count minOver, // running minimum maxOver, // running maximum} from 'zanith';Example: Rank posts by views
Which posts have the most views? Rank gives each row a position:
TS
14 linesconst ranked = await db.post.query() .select(({ post }) => ({ title: post.title, viewCount: post.viewCount, viewRank: rank() .orderBy(post.viewCount.desc()), // rank by views, highest first })) .execute(); // [// { title: "Viral Post", viewCount: 10000, viewRank: 1 },// { title: "Popular Post", viewCount: 5000, viewRank: 2 },// { title: "Average Post", viewCount: 500, viewRank: 3 },// ]Example: Running total per author
Track cumulative views for each author, ordered by post date:
TS
15 linesconst timeline = await db.post.query() .with({ author: true }) .select(({ post, author }) => ({ title: post.title, authorEmail: author.email, // Number posts per author chronologically postNumber: rowNumber() .partitionBy(post.authorId) // restart numbering per author .orderBy(post.createdAt.asc()), // order by date // Running total of views per author runningViews: sumOver(post.viewCount) .partitionBy(post.authorId) .orderBy(post.createdAt.asc()), })) .execute();partitionBy vs orderBy
Window functions have two configuration methods:
| Method | What it does | SQL equivalent |
|---|---|---|
.partitionBy(fields) | Defines groups — the window function restarts for each group | PARTITION BY field |
.orderBy(fields) | Defines order within each group | ORDER BY field ASC/DESC |
TS
9 lines// Without partition: rank ALL posts globallyrank().orderBy(post.viewCount.desc())// → RANK() OVER (ORDER BY "view_count" DESC) // With partition: rank posts WITHIN each authorrank() .partitionBy(post.authorId) .orderBy(post.viewCount.desc())// → RANK() OVER (PARTITION BY "author_id" ORDER BY "view_count" DESC)Function reference
| Function | What it computes | Common use |
|---|---|---|
rowNumber() | Sequential 1, 2, 3, 4... | Pagination, numbering |
rank() | Ranking with gaps (1, 2, 2, 4) | Leaderboards |
denseRank() | Ranking without gaps (1, 2, 2, 3) | Dense rankings |
sumOver(field) | Running sum | Cumulative totals |
avgOver(field) | Running average | Moving averages |
countOver() | Running count | Progressive counts |
minOver(field) | Running minimum | Track all-time lows |
maxOver(field) | Running maximum | Track all-time highs |