#aggregate component

6 messages · Page 1 of 1 (latest)

split yacht
#

how does aggregate component store data in aggregate tables? for ex, if i have a table that should contain the amount stored by the user and i want to get the largest amount and the total amount stored this month, how does it store this data and how will it givethe response

muted quarryBOT
#

Hi @split yacht. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

The knowledge sources don’t describe the exact internal document format of the Aggregate component, but they do explain the model clearly enough to answer your use case.

How data is stored conceptually

The Aggregate component keeps its own internal data structure in Convex, implemented as a balanced search tree (a B‑tree–style structure). This structure is a sorted key–value store with denormalized counts and sums so that operations like count, sum, min, max, at, indexOf are O(log n) instead of scanning all your documents. [How it works; What aggregates are for]

You don’t interact with those internal “aggregate tables” directly. Instead:

  1. You define a TableAggregate that says:

    • which Convex table to aggregate over,
    • how to compute the key (sortKey) used for ordering,
    • optionally how to compute the sum value (sumValue),
    • optionally a namespace to partition data. [Usage; What aggregates are for]
  2. On every write to your source table, you also call aggregate.insert, aggregate.replace, or aggregate.delete with the corresponding document, so the aggregate’s internal tree stays in sync. [Usage; Operations]

Mapping this to your example

“table that should contain the amount stored by the user and i want to get the largest amount and the total amount stored this month”

Assume you have a Convex table like:

  • deposits: { userId, amount, createdAt }
#

You might define a TableAggregate something like:

const depositsAgg = new TableAggregate<{
  Key: number;          // or [userId, createdAt], etc.
  DataModel: DataModel;
  TableName: "deposits";
}>(components.depositsAgg, {
  sortKey: (doc) => doc.amount,      // for max by amount
  sumValue: (doc) => doc.amount,     // for total amount
});

This tells the Aggregate component:

  • Key: amount (so the internal tree is ordered by amount).
  • Sum value: also amount (so .sum will give you the total of all amounts in whatever range you query). [Usage]

Then in your mutations:

// insert
const id = await ctx.db.insert("deposits", { userId, amount, createdAt });
const doc = await ctx.db.get("deposits", id);
await depositsAgg.insert(ctx, doc!);

// update
const oldDoc = await ctx.db.get("deposits", id);
await ctx.db.patch("deposits", id, { amount });
const newDoc = await ctx.db.get("deposits", id);
await depositsAgg.replace(ctx, oldDoc!, newDoc!);

// delete
const oldDoc = await ctx.db.get("deposits", id);
await ctx.db.delete("deposits", id);
await depositsAgg.delete(ctx, oldDoc!);

This keeps the internal aggregate tree up to date. [Usage]

How you query “largest amount” and “total this month”

Once the aggregate is populated, you don’t read from any “aggregate table” directly; you call methods on the TableAggregate instance:

  • Largest amount overall: use max:
const maxItem = await depositsAgg.max(ctx);
const largestAmount = maxItem?.sumValue; // or key, depending on how you set it up
#

This walks the internal tree to the maximum key in O(log n) time. [Other demos – max; Aggregate overview]

  • Total amount stored this month: use sum with bounds that represent “this month”.

The docs show the pattern for summing over a subset using bounds or prefixes; for example, summing scores for a single user: [Setup – user average]

const count = await aggregateScoreByUser.count(ctx, {
  bounds: { prefix: [args.name] },
});
const sum = await aggregateScoreByUser.sum(ctx, {
  bounds: { prefix: [args.name] },
});

For “this month”, you’d choose a key that encodes time (e.g. _creationTime or a [month, userId] tuple) and then call:

const totalThisMonth = await depositsAgg.sum(ctx, {
  bounds: {
    lower: { key: monthStart, inclusive: true },
    upper: { key: monthEnd, inclusive: false },
  },
});

The Aggregate component uses its internal B‑tree to:

  • find the range of keys between monthStart and monthEnd,
  • use its stored partial sums to compute the total in O(log n) time, without scanning all deposits. [What aggregates are for; How it works]

How the response looks

Each method returns plain values or small objects:

#

So in your case, you’d get:

  • a number for “total amount this month” from .sum,
  • an object from .max from which you read the amount (via key or sumValue, depending on your configuration).

If you want, I can sketch a concrete schema + aggregate definition tailored exactly to “per-user amounts this month” using the patterns from the docs.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: