Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra DataGrid Server-driven data grid for Laravel
Elyra

Grouping

Multi-level grouping with drag-to-group, collapsible groups, per-level aggregates, and drill-into detail rows — all computed in the database.

Enabling grouping

const options = {
  /* ... */
  grouping: { rollup: true, aggregates: [{ field: 'revenue', fn: 'sum', as: 'sum_revenue' }] },
};

Livewire: set public bool $groupable = true; and (optionally) return aggregates() from the component.

Drag-to-group

When grouping is enabled a group panel appears above the grid. Drag a column header onto it to group by that column; drag several to nest levels. Each grouped column shows as a chip with a ✕ to remove it.

Programmatic (client core):

grid.groupBy('region');
grid.groupBy('category');   // second level
grid.ungroup('category');
grid.setGroups(['region']);

How it renders

Grouping produces a hierarchical, indented list of group header rows with a count and per-group aggregates, each collapsible via a chevron:

▾ Region: Nord — 1,666,667 rows · sum_revenue 3,350,158,788
    ▾ Category: Elektronikk — 833,333 rows · sum_revenue 1,675,214,197
    ▾ Category: Bok — 833,334 rows · sum_revenue 1,674,751,357
▾ Region: Sør — 1,666,667 rows · ...
grid.toggleGroup(path);       // collapse / expand a group node
grid.isGroupCollapsed(path);

How it works on the server

The server runs one GROUP BY query per level (GROUP BY g1, then GROUP BY g1, g2, ...), so each level's aggregates are computed directly over that level's rows. This is correct for non-additive functions (avg, median, percentile, countDistinct) — not just additive sum/count — and avoids relying on ROLLUP ordering. The deepest query also fixes the hierarchical ordering of the emitted headers.

// The client sends group[] with per-level aggregates; the server returns
// GridResponse.groups: [{ field, value, count, level, path, aggregates }]

Aggregate functions

sum, avg, min, max, count, countDistinct, median, percentile (with p). median/percentile require the ElyraSQL driver.

grouping: {
  rollup: true,
  aggregates: [
    { field: 'revenue', fn: 'sum', as: 'sum_revenue' },
    { field: 'revenue', fn: 'percentile', p: 0.95, as: 'p95' },
  ],
}

Per-level aggregates are correct for every function, including non-additive ones like group-level avg and percentile.

Detail rows (drill into a group)

Enable detailRows to let users expand an innermost group and reveal its actual data rows (lazily fetched, filtered by the group path):

// Vue / Svelte
grouping: { detailRows: true, detailLimit: 200, aggregates: [/* ... */] }
// Livewire
public bool $groupDetailRows = true;
public int $groupDetailLimit = 200;

The leaf group's chevron toggles its detail rows; expanding fetches up to detailLimit rows (WHERE groupField = value AND <current filter>, no grouping) and renders them indented under the header. Non-leaf group chevrons still collapse their subgroups.

Footer aggregates (whole result)

Independent of grouping, footer aggregates run over the entire filtered result and are correct for any function. They are cached per filter.

const options = {
  aggregates: [
    { field: 'revenue', fn: 'sum', as: 'sum_revenue' },
    { field: 'margin', fn: 'avg', as: 'avg_margin' },
  ],
};