zanith

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
import {
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
const 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
const 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:

MethodWhat it doesSQL equivalent
.partitionBy(fields)Defines groups — the window function restarts for each groupPARTITION BY field
.orderBy(fields)Defines order within each groupORDER BY field ASC/DESC
TS
// Without partition: rank ALL posts globally
rank().orderBy(post.viewCount.desc())
// → RANK() OVER (ORDER BY "view_count" DESC)
 
// With partition: rank posts WITHIN each author
rank()
.partitionBy(post.authorId)
.orderBy(post.viewCount.desc())
// → RANK() OVER (PARTITION BY "author_id" ORDER BY "view_count" DESC)

Function reference

FunctionWhat it computesCommon 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 sumCumulative totals
avgOver(field)Running averageMoving averages
countOver()Running countProgressive counts
minOver(field)Running minimumTrack all-time lows
maxOver(field)Running maximumTrack all-time highs