developer guide
Combine Several Grids Into One List
Several sources can be read as one list, each row remembering where it came from, so two feeds that share no key can still be ranked together. The combined grid filters, groups and totals like any other.
Developer guide › Sources and pushdown › Combine Several Grids Into One List
Combining several grids into one
A join matches two grids on a shared key. Some questions have no key to share at
all - "the worst performers across two regional datasets" when the two regions use
unrelated ids - and for those, from takes an array of sources instead of one
grid: every source is read, concatenated in the order you declared them, and only then does the
rest of the pipeline run, once, over the combined set.
Worst performers, two unrelated datasets
createGrid(right, {
columns: [{ id: 'title' }, { id: 'severity', type: 'number' }, { id: '__source' }],
source: {
mode: 'derived',
from: [
{ grid: eastIncidents, label: 'east' },
{ grid: westIncidents, label: 'west', map: (row) => ({ title: row.name, severity: row.rating }) },
],
sort: [{ col: 'severity', dir: 'desc' }],
limit: 10,
},
});
label defaults to the source's position in the array ('0',
'1', …), and follow is independent per source - filtering
one narrows only its own contribution, exactly as a lone from follows its grid
today. A source with differently-named fields uses map to project them into a
common shape before it joins the rest.
__source is not optional. Every combined row carries it - the entry's label, or its index when unlabelled - and it is an ordinary field
to where, groupBy and select. Leaving it out would mean a
combined list that cannot say where any row came from, which defeats most of the reason to
combine several sources in the first place.
The union of fields, never a merge. A field only one source has is
undefined on the others' rows, not fabricated and not type-coerced - two
sources disagreeing about what a field means is yours to resolve with map, not
something the union guesses at. And there is no dedup: two sources reporting the same fact both
appear as separate rows. There is no UNION-vs-UNION-ALL distinction to draw; reach for
join when rows should be matched on a key rather than stacked.
The key is namespaced, only where it needs to be. Two sources can easily
share row identifiers, so the derived __key is qualified by the source tag when
nothing is grouped. Grouped, __key is the group value exactly as it always has
been, and rows from different sources landing in the same group is what grouping a union is
for, not a collision.
An empty source is fine; a broken one is loud. A source with no rows contributes nothing. A source that throws while being read is named in a console warning and skipped for that pass - a silently missing source would make "worst across both" quietly wrong, so it is reported rather than swallowed.
A cycle is refused when the source is built. If a union's sources include the grid being derived, directly or through a chain of other derived grids, it is refused up front, naming the offending source, rather than being recursed into.
A union never patches - know the cost before you reach for one at scale.
A lone from maintains its grouping incrementally: an edit that names the rows it
touched re-reduces only the groups those rows belong to. A union does not do this for any of its
sources - every change on any parent re-reads and re-derives the whole
combined set from scratch. On a synthetic 200,000-row union across four sources, one row changed
on one parent cost on the order of 800 ms-3 s (machine-dependent;
run node bench/union-parents.mjs against your own shape), against a few
milliseconds for the equivalent patched change on a lone from over the same row
count. And because a union is watched by as many independent change streams as it has sources,
that full-rescan cost is paid once per source that moves, not once per union - four active parents each firing their own updates pay it four times over. Below a few tens of
thousands of combined rows this is unlikely to matter; above that, or with several sources each
updating on their own live feed, budget for it, keep refresh away from every tick
(idle, or a debounce), and prefer fewer, larger sources over many small ones where
the shape of your data allows it.
Not supported alongside a union. crossFilter has no single
target once there is more than one parent; profile and statistics
reduce one grid's own columns. All three are refused with a warning rather than guessed at, and
the top-level follow is ignored in favour of each source's own.