Skip to content

Grouping

Orama supports groupBy operations. That allows you to group results in groups calculating an aggregation on the item that belongs to the same bucket.

const results = await search(db, {
term: "t-shirt",
groupBy: {
properties: ["design"], // required: property on which we want to group on
maxResult: 1, // optional: for every group, how many results we want
reduce: {
// optional: customize the aggregation logic
reducer: Function,
getInitialValue: Function,
},
},
});

By default, Orama doesn’t limit the number of items inside a group.

By default, Orama groups all the matched documents into an array.

Simple usage

If we consider the following schema:

const db = await create({
schema: {
id: "string",
type: "string",
design: "string",
color: "string",
rank: "number",
isPromoted: "boolean",
},
});
const ids = await insertMultiple(db, [
{
id: "0",
type: "t-shirt",
design: "A",
color: "blue",
rank: 3,
isPromoted: true,
},
{
id: "1",
type: "t-shirt",
design: "A",
color: "green",
rank: 5,
isPromoted: false,
},
{
id: "2",
type: "t-shirt",
design: "A",
color: "red",
rank: 4,
isPromoted: false,
},
{
id: "3",
type: "t-shirt",
design: "B",
color: "blue",
rank: 4,
isPromoted: false,
},
{
id: "4",
type: "t-shirt",
design: "B",
color: "green",
rank: 4,
isPromoted: true,
},
{
id: "5",
type: "t-shirt",
design: "B",
color: "white",
rank: 5,
isPromoted: false,
},
{
id: "6",
type: "t-shirt",
design: "B",
color: "gray",
rank: 5,
isPromoted: true,
},
{
id: "7",
type: "sweatshirt",
design: "A",
color: "yellow",
rank: 3,
isPromoted: true,
},
{
id: "8",
type: "sweatshirt",
design: "A",
color: "green",
rank: 4,
isPromoted: false,
},
]);

We will be able to have the documents per design ordered by rank:

const results = await search(db, {
term: "t-shirt",
groupBy: {
properties: ["design"], // property on which we want to group on
},
sortBy: {
property: "rank", // inside a group, the result is ordered following this property
order: "DESC", // with this order
},
});

If you want only the top-ranked document per design, you can specify the maxResult:

const results = await search(db, {
term: "t-shirt",
groupBy: {
properties: ["design"],
maxResult: 1, // for every group, how many results we want
},
sortBy: {
property: "rank",
order: "DESC",
},
});

The above query returns something like this:

{
groups: [
{
values: ['A'], // list of the values the group is referring to
result: [
{
id: '1',
score: 0,
document: { ... } // the doc with id '1'
}
]
},
{
values: ['B'], // list of the values the group is referring to
result: [
{
id: '5',
score: 0,
document: { ... } // the doc with id '5'
}
]
}
],
// The other common properties like `hits` and `elapsed`
}

You can group on multiple properties as follows:

const results = await search(db, {
term: "red t-shirt",
groupBy: {
properties: ["design", "rank", "isPromoted"], // group on the combination of the values
},
sortBy: {
property: "id",
order: "ASC",
},
});

Custom reducer

Orama supports custom aggregator as follows:

// The document interface
interface Doc extends Document {
type: string;
design: string;
rank: number;
color: string;
isPromoted: boolean;
}
// The aggregation interface
interface AggregationValue {
type: string;
design: string;
colors: string[];
ranks: number[];
isPromoted: boolean;
}
const results = await search(db, {
term: "red t-shirt",
groupBy: {
properties: ["type", "design"], // group on both properties
reduce: {
// the accumulator function
reducer: (
values: ScalarSearchableValue[],
acc: AggregationValue,
item: Result
) => {
const doc = item.document as Doc;
acc.type ||= doc.type;
acc.design ||= doc.design;
acc.isPromoted ||= doc.isPromoted;
acc.colors.push(doc.color);
acc.ranks.push(doc.rank);
return acc;
},
// The initial value: this is called for every group
getInitialValue: (): AggregationValue => ({
type: "",
design: "",
colors: [],
ranks: [],
isPromoted: false,
}),
},
},
sortBy: {
property: "rank",
order: "DESC",
},
});

Where the accumulator function receives the following parameters:

  1. the value of the current groups
  2. the accumulator returned by the previous invocation
  3. the item to accumulate

The reducer is called for every item for every group.