Skip to content

Order by a @OneToMany or @ElementCollection field via a subquery - #71

Merged
BalusC merged 1 commit into
developfrom
order-by-collection
Nov 3, 2026
Merged

BalusC merged 1 commit into
developfrom
order-by-collection

Conversation

@BalusC

@BalusC BalusC commented Nov 3, 2026

Copy link
Copy Markdown
Member

Fixes #61

Ordering on a field of a @OneToMany was left out of the SQL whenever PostponedFetchRoot was used, and PostponedFetchRoot sorted only the entities of the current page in memory. Ordering on a field of an @ElementCollection joined the collection, which returned an entity once per element, so the same entity could show up on two pages.

BaseEntityService#buildOrderBy() now orders the entities by the smallest value of the field among the elements when ascending, or else by the largest one:

ORDER BY COALESCE((SELECT MIN(element) FROM ... WHERE <correlated to the root> AND <required criteria on the collection>), NULL), ID
  • The required criteria on the collection are applied inside the subquery, with multiple values and a negation per element. Optional criteria are not.
  • The ID is added as tiebreaker, so that the page order is stable when elements tie.
  • The subquery is wrapped in COALESCE with a typed NULL fallback. EclipseLink's criteria API casts every order expression to ExpressionImpl, which a subquery is not. SQL Server does not accept COALESCE with only one argument.
  • When the collection is fetched by a real fetch join, the order is on the fetched path, which is selected.
  • In a query which is distinct because it joins a collection, the order is skipped with a warning.
  • Value based paging does not apply to such an ordering. The Page javadoc says so.
  • PostponedFetchRoot no longer re-sorts the entities in memory. It still sorts the elements of the fetched collections.
  • The query is no longer distinct merely because it has optional criteria, only when it joins a collection.

New tests in OmniPersistenceIT read two pages and assert the order across them: @OneToMany ascending and descending with and without fetch, with required criteria, with a list and a negated criteria, with optional criteria, an @ElementCollection, and an @ElementCollection nested in a @OneToMany.

Known limitations:

  • An order expression other than a path is only specified since JPA 3.2. It works on Hibernate, EclipseLink and OpenJPA, but is tested on H2 only.
  • The correlated subquery is evaluated per root entity before LIMIT, which costs more than a plain order on a large table.

🤖 Generated with Claude Opus 5.5

Ordering on a field of a @onetomany was left out of the SQL whenever
PostponedFetchRoot was used, and PostponedFetchRoot sorted only the
entities of the current page in memory. Ordering on a field of an
@ElementCollection joined the collection, which returned an entity
once per element, so the same entity could show up on two pages.

The entities are now ordered by the smallest value of the field among
the elements when ascending, or else by the largest one, via a
correlated subquery which also applies the required criteria on the
collection. The ID is added as tiebreaker. The subquery is wrapped in
COALESCE, as EclipseLink does not accept a subquery as order
expression. In a query which is distinct because it joins a collection,
the order is skipped with a warning, unless the collection is fetched.
Value based paging does not apply to such an ordering.

The query is no longer distinct merely because it has optional
criteria, only when it joins a collection.

Fixes #61

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BalusC
BalusC merged commit 0568e5c into develop Nov 3, 2026
3 checks passed
@BalusC
BalusC deleted the order-by-collection branch November 3, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant