Schema

One cell, five columns

An address reads as one thing and stores as five. Display groups give you the single cell without giving up the columns underneath it — the query still sorts, filters and exports the parts.

SchemaStack team24 Aug 2026Verified working · 24 Aug 2026Docs

Every spreadsheet eventually grows a column called Address with 12 Mill Lane, Cambridge, CB1 2AB, UK in it. It reads well. It is also the moment you lose the ability to answer "how many customers are in Cambridge", because the city is now a substring.

The database answer is five columns, and then the grid is unreadable — five narrow cells you scroll past to read one address. Display groups are the way out of that trade: one cell to read, five columns to query.

street12 Mill LanecityCambridgepostcodeCB1 2ABcountryUKfour real columns · still typed · still indexablejoined after the queryone cell in the grid12 Mill Lane, Cambridge, CB1 2AB, UKwhat the database is actually askedSELECT street, city, postcode, country … ORDER BY street, city, postcodeThe cell is a reading. Nothing downstream — sort, filter, export, API — inherits the presentation choice.
The joined value is assembled after the rows come back, so it never exists in SQL. Each column is selected on its own, sorting a group orders by every member in turn, and filtering still targets a real typed column rather than a substring of a sentence.

The group is a reading, not a column

Pick the columns, pick a separator, and they render as a single cell in position order. What matters is what happens underneath: the group never exists in SQL. The SELECT lists each member column individually and the join happens after the rows come back.

That one decision is why nothing is lost:

  • Sorting a group orders by every member, in position order. Sort a Name group of first and last and you get first-then-last, which is what you wanted and what sorting a concatenated string would not have given you.
  • Filtering happens on the columns, because the columns are still there and still typed. city = Cambridge is an indexable equality on a real column, not a LIKE against a sentence.
  • Exports and the API see the parts. Nothing downstream inherits the presentation choice.

Each part keeps its own formatting

The members are formatted before they are joined, each with its own display options. A SELECT column contributes its label rather than its stored key. A boolean contributes Yes or No. A number keeps its thousands separator and its decimal places; a date keeps its format.

So a group of Status · Amount · Due Date reads as Overdue · 1,240.00 · 14 Mar 2026 and not as 2 · 1240 · 2026-03-14T00:00:00Z. Formatting you configured once on the column follows the value into the group.

Two smaller behaviours in the same spirit. Nulls are skipped, so a missing middle name does not leave you Jane Smith with a hole in it or a dangling separator. And hidden members are skipped, so you can keep a column in the group but out of the reading.

The group behaves like the sum of its parts

Its capabilities are derived rather than declared, and the direction of each default is deliberate:

PropertyRule
Sortabletrue if any member is sortable
Read-onlytrue only if all members are read-only
Hiddentrue only if all members are hidden

Sortable is generous because one sortable member is enough to make the click useful. Read-only and hidden are strict because a group that hid an editable column, or hid a visible one, would be a group that lost you access to your own data.

Editing edits the parts

Opening a grouped cell gives you a form with one field per column, each with its own type, widget and validation. You are never handed the joined string to re-parse, which is the failure mode of the single-text-column approach — the point at which somebody's comma breaks your address.

What it doesn't do (yet)

  • Assembled per row, per query. The joined value is computed after the rows return, so there is nothing to index and nothing to filter against as a whole. Filter the members.
  • One separator per group. You cannot have a comma between the street and the city and a newline before the postcode. Real addresses want that; today you get one character or string, used everywhere in the group.
  • A skipped null is silent. Because nulls collapse cleanly, a group is not the place to notice that a value is missing — it looks the same as a value that was never part of the group. If the presence of a field matters, keep it visible on its own.
  • No nesting. A group cannot contain a group.
  • Position order only. The reading order is the column order; there is no separate ordering for the group.

Verified 24 Aug 2026: 36 tests exercising display groups specifically, all green 2026-08-24, spread across the suites that own the behaviour rather than a dedicated class — InlineDataEditTest (20, editing the members of a group), ViewDataServiceMappingTest (10, how a group maps into rows), ColumnSyncAsyncUpdateTest (3), WorkspaceDatabaseServiceSearchTest (2) and MetadataQueryBuilderTest (1). The SQL claims, each member selected individually and ORDER BY emitting every member in position order, were read from WorkspaceDatabaseService, and the post-query assembly from ValueTransformService.transformDisplayGroup..